update lead
This commit is contained in:
parent
5cff4678ad
commit
df148e03ad
@ -653,9 +653,7 @@ class LeadForm(forms.ModelForm):
|
||||
'email',
|
||||
'phone_number',
|
||||
'address',
|
||||
'id_car_make',
|
||||
'id_car_model',
|
||||
'year',
|
||||
'car',
|
||||
'source',
|
||||
'channel',
|
||||
'staff',
|
||||
|
||||
18
inventory/migrations/0008_schedule_status.py
Normal file
18
inventory/migrations/0008_schedule_status.py
Normal file
@ -0,0 +1,18 @@
|
||||
# Generated by Django 4.2.17 on 2025-02-05 09:43
|
||||
|
||||
from django.db import migrations, models
|
||||
|
||||
|
||||
class Migration(migrations.Migration):
|
||||
|
||||
dependencies = [
|
||||
('inventory', '0007_schedule_scheduled_type'),
|
||||
]
|
||||
|
||||
operations = [
|
||||
migrations.AddField(
|
||||
model_name='schedule',
|
||||
name='status',
|
||||
field=models.CharField(choices=[('Scheduled', 'Scheduled'), ('Completed', 'Completed'), ('Canceled', 'Canceled')], default='Scheduled', max_length=200),
|
||||
),
|
||||
]
|
||||
21
inventory/migrations/0009_alter_opportunity_customer.py
Normal file
21
inventory/migrations/0009_alter_opportunity_customer.py
Normal file
@ -0,0 +1,21 @@
|
||||
# Generated by Django 4.2.17 on 2025-02-05 10:00
|
||||
|
||||
from django.conf import settings
|
||||
from django.db import migrations, models
|
||||
import django.db.models.deletion
|
||||
|
||||
|
||||
class Migration(migrations.Migration):
|
||||
|
||||
dependencies = [
|
||||
migrations.swappable_dependency(settings.DJANGO_LEDGER_CUSTOMER_MODEL),
|
||||
('inventory', '0008_schedule_status'),
|
||||
]
|
||||
|
||||
operations = [
|
||||
migrations.AlterField(
|
||||
model_name='opportunity',
|
||||
name='customer',
|
||||
field=models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, related_name='opportunities', to=settings.DJANGO_LEDGER_CUSTOMER_MODEL),
|
||||
),
|
||||
]
|
||||
@ -0,0 +1,27 @@
|
||||
# Generated by Django 4.2.17 on 2025-02-05 10:05
|
||||
|
||||
from django.db import migrations, models
|
||||
import django.db.models.deletion
|
||||
|
||||
|
||||
class Migration(migrations.Migration):
|
||||
|
||||
dependencies = [
|
||||
('inventory', '0009_alter_opportunity_customer'),
|
||||
]
|
||||
|
||||
operations = [
|
||||
migrations.RemoveField(
|
||||
model_name='lead',
|
||||
name='id_car_make',
|
||||
),
|
||||
migrations.RemoveField(
|
||||
model_name='lead',
|
||||
name='id_car_model',
|
||||
),
|
||||
migrations.AddField(
|
||||
model_name='lead',
|
||||
name='car',
|
||||
field=models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.DO_NOTHING, to='inventory.car', verbose_name='Car'),
|
||||
),
|
||||
]
|
||||
@ -0,0 +1,25 @@
|
||||
# Generated by Django 4.2.17 on 2025-02-05 10:17
|
||||
|
||||
from django.conf import settings
|
||||
from django.db import migrations, models
|
||||
import django.db.models.deletion
|
||||
|
||||
|
||||
class Migration(migrations.Migration):
|
||||
|
||||
dependencies = [
|
||||
migrations.swappable_dependency(settings.DJANGO_LEDGER_CUSTOMER_MODEL),
|
||||
('inventory', '0010_remove_lead_id_car_make_remove_lead_id_car_model_and_more'),
|
||||
]
|
||||
|
||||
operations = [
|
||||
migrations.RemoveField(
|
||||
model_name='lead',
|
||||
name='year',
|
||||
),
|
||||
migrations.AlterField(
|
||||
model_name='schedule',
|
||||
name='customer',
|
||||
field=models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.CASCADE, related_name='schedules', to=settings.DJANGO_LEDGER_CUSTOMER_MODEL),
|
||||
),
|
||||
]
|
||||
@ -1096,23 +1096,26 @@ class Lead(models.Model):
|
||||
CustomerModel, on_delete=models.CASCADE, related_name="leads",
|
||||
null=True,blank=True
|
||||
)
|
||||
id_car_make = models.ForeignKey(
|
||||
CarMake,
|
||||
on_delete=models.DO_NOTHING,
|
||||
blank=True,
|
||||
null=True,
|
||||
verbose_name=_("Make"),
|
||||
)
|
||||
id_car_model = models.ForeignKey(
|
||||
CarModel,
|
||||
on_delete=models.DO_NOTHING,
|
||||
blank=True,
|
||||
null=True,
|
||||
verbose_name=_("Model"),
|
||||
)
|
||||
year = models.PositiveSmallIntegerField(
|
||||
verbose_name=_("Year"), blank=True, null=True
|
||||
car = models.ForeignKey(
|
||||
Car, on_delete=models.DO_NOTHING, blank=True, null=True, verbose_name=_("Car")
|
||||
)
|
||||
# id_car_make = models.ForeignKey(
|
||||
# CarMake,
|
||||
# on_delete=models.DO_NOTHING,
|
||||
# blank=True,
|
||||
# null=True,
|
||||
# verbose_name=_("Make"),
|
||||
# )
|
||||
# id_car_model = models.ForeignKey(
|
||||
# CarModel,
|
||||
# on_delete=models.DO_NOTHING,
|
||||
# blank=True,
|
||||
# null=True,
|
||||
# verbose_name=_("Model"),
|
||||
# )
|
||||
# year = models.PositiveSmallIntegerField(
|
||||
# verbose_name=_("Year"), blank=True, null=True
|
||||
# )
|
||||
source = models.CharField(
|
||||
max_length=50, choices=Sources.choices, verbose_name=_("Source")
|
||||
)
|
||||
@ -1164,16 +1167,14 @@ class Lead(models.Model):
|
||||
"email": str(self.email),
|
||||
"address": str(self.address),
|
||||
"phone_number": str(self.phone_number),
|
||||
"id_car_make": str(self.id_car_make.name),
|
||||
"id_car_model": str(self.id_car_model.name),
|
||||
"year": str(self.year),
|
||||
"car": self.car.to_dict(),
|
||||
"created_at": str(self.created.strftime("%Y-%m-%d")),
|
||||
}
|
||||
@property
|
||||
def full_name(self):
|
||||
return f"{self.first_name} {self.last_name}"
|
||||
def convert_to_customer(self,entity):
|
||||
if entity and not CustomerModel.objects.filter(email=self.email).exists():
|
||||
if entity and not entity.get_customers().filter(email=self.email).exists():
|
||||
customer = entity.create_customer(
|
||||
customer_model_kwargs={
|
||||
"customer_name": self.full_name,
|
||||
@ -1184,8 +1185,8 @@ class Lead(models.Model):
|
||||
)
|
||||
customer.additional_info = {}
|
||||
customer.additional_info.update({"info":self.to_dict()})
|
||||
self.customer = customer
|
||||
customer.save()
|
||||
self.customer = customer
|
||||
self.save()
|
||||
|
||||
def get_latest_schedule(self):
|
||||
@ -1205,19 +1206,25 @@ class Schedule(models.Model):
|
||||
('Meeting', 'Meeting'),
|
||||
('Email', 'Email'),
|
||||
]
|
||||
ScheduleStatusChoices = [
|
||||
('Scheduled', 'Scheduled'),
|
||||
('Completed', 'Completed'),
|
||||
('Canceled', 'Canceled'),
|
||||
]
|
||||
lead = models.ForeignKey(Lead, on_delete=models.CASCADE, related_name='schedules')
|
||||
customer = models.ForeignKey(CustomerModel, on_delete=models.CASCADE, related_name='schedules')
|
||||
customer = models.ForeignKey(CustomerModel, on_delete=models.CASCADE, related_name='schedules',null=True,blank=True)
|
||||
scheduled_by = models.ForeignKey(Staff, on_delete=models.CASCADE)
|
||||
purpose = models.CharField(max_length=200, choices=PURPOSE_CHOICES)
|
||||
scheduled_at = models.DateTimeField()
|
||||
scheduled_type = models.CharField(max_length=200, choices=ScheduledType,default='Call')
|
||||
notes = models.TextField(blank=True, null=True)
|
||||
status = models.CharField(max_length=200, choices=ScheduleStatusChoices, default='Scheduled')
|
||||
created_at = models.DateTimeField(auto_now_add=True)
|
||||
updated_at = models.DateTimeField(auto_now=True)
|
||||
|
||||
def __str__(self):
|
||||
return f"Scheduled {self.purpose} with {self.customer.customer_name} on {self.scheduled_at}"
|
||||
|
||||
|
||||
def schedule_past_date(self):
|
||||
if self.scheduled_at < timezone.now():
|
||||
return True
|
||||
@ -1260,7 +1267,7 @@ class Opportunity(models.Model):
|
||||
Dealer, on_delete=models.CASCADE, related_name="opportunities"
|
||||
)
|
||||
customer = models.ForeignKey(
|
||||
Customer, on_delete=models.CASCADE, related_name="opportunities"
|
||||
CustomerModel, on_delete=models.CASCADE, related_name="opportunities"
|
||||
)
|
||||
car = models.ForeignKey(
|
||||
Car, on_delete=models.SET_NULL, null=True, blank=True, verbose_name=_("Car")
|
||||
|
||||
@ -116,6 +116,11 @@ urlpatterns = [
|
||||
views.OpportunityCreateView.as_view(),
|
||||
name="opportunity_create",
|
||||
),
|
||||
path(
|
||||
"crm/opportunities/<int:pk>/create/",
|
||||
views.OpportunityCreateView.as_view(),
|
||||
name="opportunity_create",
|
||||
),
|
||||
path(
|
||||
"crm/opportunities/<int:pk>/",
|
||||
views.OpportunityDetailView.as_view(),
|
||||
|
||||
@ -2978,9 +2978,13 @@ def add_note_to_lead(request, pk):
|
||||
def lead_convert(request, pk):
|
||||
lead = get_object_or_404(models.Lead, pk=pk)
|
||||
dealer = get_user_type(request)
|
||||
if lead.is_converted:
|
||||
messages.error(request, "Lead is already converted to customer.")
|
||||
return redirect("opportunity_create",pk=lead.pk)
|
||||
|
||||
lead.convert_to_customer(dealer.entity)
|
||||
messages.success(request, "Lead converted to customer successfully!")
|
||||
return redirect("lead_list")
|
||||
return redirect("opportunity_create",pk=lead.pk)
|
||||
|
||||
def schedule_lead(request, pk):
|
||||
lead = get_object_or_404(models.Lead, pk=pk)
|
||||
@ -2988,8 +2992,7 @@ def schedule_lead(request, pk):
|
||||
form = forms.ScheduleForm(request.POST)
|
||||
if form.is_valid():
|
||||
instance = form.save(commit=False)
|
||||
instance.lead = lead
|
||||
instance.customer = lead.customer
|
||||
instance.lead = lead
|
||||
if hasattr(request.user, "staff"):
|
||||
instance.scheduled_by = request.user.staff
|
||||
instance.save()
|
||||
@ -3067,10 +3070,16 @@ class OpportunityCreateView(CreateView):
|
||||
def get_context_data(self, **kwargs):
|
||||
context = super().get_context_data(**kwargs)
|
||||
dealer = get_user_type(self.request)
|
||||
context["customer"] = models.Customer.objects.filter(dealer=dealer)
|
||||
context["cars"] = models.Car.objects.filter(dealer=dealer)
|
||||
return context
|
||||
|
||||
|
||||
def get_initial(self):
|
||||
initial = super().get_initial()
|
||||
if self.kwargs.get('pk',None):
|
||||
lead = models.Lead.objects.get(pk=self.kwargs.get('pk'))
|
||||
initial['customer'] = lead.customer
|
||||
initial['car'] = lead.car
|
||||
return initial
|
||||
|
||||
def form_valid(self, form):
|
||||
dealer = get_user_type(self.request)
|
||||
form.instance.dealer = dealer
|
||||
|
||||
@ -72,7 +72,7 @@
|
||||
<div class="d-flex align-items-center bg-info-subtle rounded me-2"><span class="text-info-dark" data-feather="database"></span></div>
|
||||
<span>{{ _("Source")|capfirst }}</span>
|
||||
</div>
|
||||
</th>``
|
||||
</th>
|
||||
<th class="align-middle white-space-nowrap text-uppercase" scope="col" style="width: 10%;">
|
||||
<div class="d-inline-flex flex-center">
|
||||
<div class="d-flex align-items-center bg-warning-subtle rounded me-2"><span class="text-warning-dark" data-feather="grid"></span></div>
|
||||
@ -135,19 +135,26 @@
|
||||
</div>
|
||||
</td>
|
||||
|
||||
<td class="align-middle white-space-nowrap fw-semibold"><a class="text-body-highlight" href="">{{ lead.id_car_make.get_local_name }} - {{ lead.id_car_model.get_local_name }} {{ lead.year }}</a></td>
|
||||
<td class="align-middle white-space-nowrap fw-semibold"><a class="text-body-highlight" href="">{{ lead.car.id_car_make.get_local_name }} - {{ lead.car.id_car_model.get_local_name }} {{ lead.year }}</a></td>
|
||||
<td class="align-middle white-space-nowrap fw-semibold"><a class="text-body-highlight" href="">{{ lead.email }}</a></td>
|
||||
<td class="align-middle white-space-nowrap fw-semibold"><a class="text-body-highlight" href="tel:{{ lead.phone_number }}">{{ lead.phone_number }}</a></td>
|
||||
<td class="align-middle white-space-nowrap fw-semibold">
|
||||
{% if lead.get_latest_schedule %}
|
||||
<span class="text-success" data-feather="calendar"></span> <span class="badge badge-phoenix badge-phoenix-primary text-success fw-semibold">{{ lead.get_latest_schedule.scheduled_type }}</span> <br>
|
||||
<span class="text-success" data-feather="clock"></span> <span class="badge badge-phoenix badge-phoenix-primary text-success fw-semibold">{{ lead.get_latest_schedule.scheduled_at }}</span>
|
||||
{% if lead.get_latest_schedule.scheduled_type == "Call" %}
|
||||
<span class="badge badge-phoenix badge-phoenix-primary text-primary fw-semibold"><span class="text-primary" data-feather="phone"></span>
|
||||
{{ lead.get_latest_schedule.scheduled_at }}</span>
|
||||
{% elif lead.get_latest_schedule.scheduled_type == "Meeting" %}
|
||||
<span class="badge badge-phoenix badge-phoenix-success text-success fw-semibold"><span class="text-success" data-feather="calendar"></span>
|
||||
{{ lead.get_latest_schedule.scheduled_at }}</span>
|
||||
{% elif lead.get_latest_schedule.scheduled_type == "Email" %}
|
||||
<span class="badge badge-phoenix badge-phoenix-warning text-warning fw-semibold"><span class="text-warning" data-feather="email"></span>
|
||||
{{ lead.get_latest_schedule.scheduled_at }}</span>
|
||||
{% endif %}
|
||||
{% endif %}
|
||||
</td>
|
||||
<td class="align-middle white-space-nowrap text-body-tertiary text-opacity-85 fw-semibold text-body-highlight">{{ lead.staff|upper }}</td>
|
||||
<td class="align-middle white-space-nowrap fw-semibold text-body-highlight">{{ lead.source|upper }}</td>
|
||||
<td class="align-middle white-space-nowrap text-body-tertiary text-opacity-85 fw-semibold text-body-highlight">{{ lead.channel|upper }}</td>
|
||||
<td class="align-middle white-space-nowrap text-body-tertiary text-opacity-85 text-body-tertiary">{{ lead.created|date }}</td>
|
||||
<td class="align-middle white-space-nowrap text-body-tertiary text-opacity-85 fw-semibold text-body-highlight">{{ lead.channel|upper }}</td>
|
||||
<td class="align-middle white-space-nowrap text-end">
|
||||
<div class="btn-reveal-trigger position-static">
|
||||
<button
|
||||
|
||||
@ -36,7 +36,11 @@
|
||||
</div>
|
||||
<div class="d-md-flex d-xl-block align-items-center justify-content-between mb-5">
|
||||
<div class="d-flex align-items-center mb-3 mb-md-0 mb-xl-3">
|
||||
<div class="avatar avatar-xl me-3"><img class="rounded" src="{{ opportunity.car.id_car_make.logo.url }}" alt="" /></div>
|
||||
<div class="avatar avatar-xl me-3">
|
||||
{% if opportunity.car.id_car_make.logo %}
|
||||
<img class="rounded" src="{{ opportunity.car.id_car_make.logo.url }}" alt="" />
|
||||
{% endif %}
|
||||
</div>
|
||||
<div>
|
||||
<h5>{{ opportunity.staff.get_local_name}}</h5>
|
||||
<div class="dropdown"><a class="text-body-secondary dropdown-toggle text-decoration-none dropdown-caret-none" href="#!" data-bs-toggle="dropdown" aria-expanded="false">
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user