update lead

This commit is contained in:
gitea 2025-02-04 16:12:13 +00:00
parent a49ace441f
commit 5cff4678ad
22 changed files with 481 additions and 74 deletions

View File

@ -17,6 +17,7 @@ from .models import (
Dealer,
# Branch,
Vendor,
Schedule,
Customer,
Car,
CarTransfer,
@ -38,7 +39,7 @@ from .models import (
SaleOrder
)
from django_ledger.models import ItemModel, InvoiceModel, BillModel,VendorModel
from django.forms import ModelMultipleChoiceField, ValidationError, DateInput
from django.forms import ModelMultipleChoiceField, ValidationError, DateInput,DateTimeInput
from django.utils.translation import gettext_lazy as _
import django_tables2 as tables
from django.forms import formset_factory
@ -646,8 +647,12 @@ class EmailForm(forms.Form):
class LeadForm(forms.ModelForm):
class Meta:
model = Lead
fields = ['customer',
'city',
fields = [
'first_name',
'last_name',
'email',
'phone_number',
'address',
'id_car_make',
'id_car_model',
'year',
@ -666,6 +671,11 @@ class LeadForm(forms.ModelForm):
(obj.id_car_make, obj.get_local_name()) for obj in queryset
]
class ScheduleForm(forms.ModelForm):
scheduled_at = forms.DateTimeField(widget=DateTimeInput(attrs={'type': 'datetime-local'}))
class Meta:
model = Schedule
fields = ['purpose','scheduled_type', 'scheduled_at', 'notes']
class NoteForm(forms.ModelForm):
class Meta:

View File

@ -4,6 +4,8 @@ from django.utils import timezone
from django.shortcuts import redirect
from django.urls import reverse
from inventory.utils import get_user_type
logger = logging.getLogger('user_activity')
@ -37,7 +39,8 @@ class InjectParamsMiddleware:
def __call__(self, request):
try:
request.entity = request.user.dealer.entity
# request.entity = request.user.dealer.entity
request.dealer = get_user_type(request)
except Exception as e:
pass

View File

@ -0,0 +1,21 @@
# Generated by Django 4.2.17 on 2025-02-04 09:34
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', '0001_initial'),
]
operations = [
migrations.AlterField(
model_name='lead',
name='customer',
field=models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, related_name='leads', to=settings.DJANGO_LEDGER_CUSTOMER_MODEL),
),
]

View File

@ -0,0 +1,46 @@
# Generated by Django 4.2.17 on 2025-02-04 11:38
from django.conf import settings
from django.db import migrations, models
import django.db.models.deletion
import phonenumber_field.modelfields
class Migration(migrations.Migration):
dependencies = [
migrations.swappable_dependency(settings.DJANGO_LEDGER_CUSTOMER_MODEL),
('inventory', '0002_alter_lead_customer'),
]
operations = [
migrations.AddField(
model_name='lead',
name='email',
field=models.EmailField(default='x@tenhal.sa', max_length=254, verbose_name='Email'),
preserve_default=False,
),
migrations.AddField(
model_name='lead',
name='first_name',
field=models.CharField(default='test', max_length=50, verbose_name='First Name'),
preserve_default=False,
),
migrations.AddField(
model_name='lead',
name='last_name',
field=models.CharField(default='test', max_length=50, verbose_name='Last Name'),
preserve_default=False,
),
migrations.AddField(
model_name='lead',
name='phone_number',
field=phonenumber_field.modelfields.PhoneNumberField(default='056523656', max_length=128, region='SA', verbose_name='Phone Number'),
preserve_default=False,
),
migrations.AlterField(
model_name='lead',
name='customer',
field=models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.CASCADE, related_name='leads', to=settings.DJANGO_LEDGER_CUSTOMER_MODEL),
),
]

View File

@ -0,0 +1,23 @@
# Generated by Django 4.2.17 on 2025-02-04 14:21
from django.db import migrations, models
class Migration(migrations.Migration):
dependencies = [
('inventory', '0003_lead_email_lead_first_name_lead_last_name_and_more'),
]
operations = [
migrations.RemoveField(
model_name='lead',
name='city',
),
migrations.AddField(
model_name='lead',
name='address',
field=models.CharField(default='', max_length=50, verbose_name='address'),
preserve_default=False,
),
]

View File

@ -0,0 +1,33 @@
# Generated by Django 4.2.17 on 2025-02-04 15:15
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', '0004_remove_lead_city_lead_address'),
]
operations = [
migrations.CreateModel(
name='Schedule',
fields=[
('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
('purpose', models.CharField(max_length=200)),
('scheduled_at', models.DateTimeField()),
('notes', models.TextField(blank=True, null=True)),
('created_at', models.DateTimeField(auto_now_add=True)),
('updated_at', models.DateTimeField(auto_now=True)),
('customer', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, related_name='schedules', to=settings.DJANGO_LEDGER_CUSTOMER_MODEL)),
('lead', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, related_name='schedules', to='inventory.lead')),
('scheduled_by', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to='inventory.staff')),
],
options={
'ordering': ['-scheduled_at'],
},
),
]

View File

@ -0,0 +1,18 @@
# Generated by Django 4.2.17 on 2025-02-04 15:31
from django.db import migrations, models
class Migration(migrations.Migration):
dependencies = [
('inventory', '0005_schedule'),
]
operations = [
migrations.AlterField(
model_name='schedule',
name='purpose',
field=models.CharField(choices=[('Product Demo', 'Product Demo'), ('Follow-Up Call', 'Follow-Up Call'), ('Contract Discussion', 'Contract Discussion'), ('Sales Meeting', 'Sales Meeting'), ('Support Call', 'Support Call'), ('Other', 'Other')], max_length=200),
),
]

View File

@ -0,0 +1,18 @@
# Generated by Django 4.2.17 on 2025-02-04 15:36
from django.db import migrations, models
class Migration(migrations.Migration):
dependencies = [
('inventory', '0006_alter_schedule_purpose'),
]
operations = [
migrations.AddField(
model_name='schedule',
name='scheduled_type',
field=models.CharField(choices=[('Call', 'Call'), ('Meeting', 'Meeting'), ('Email', 'Email')], default='Call', max_length=200),
),
]

View File

@ -1,3 +1,4 @@
from datetime import timezone
import itertools
from uuid import uuid4
from django.conf import settings
@ -1087,8 +1088,13 @@ class Representative(models.Model, LocalizedNameMixin):
class Lead(models.Model):
dealer = models.ForeignKey(Dealer, on_delete=models.CASCADE, related_name="leads")
first_name = models.CharField(max_length=50, verbose_name=_("First Name"))
last_name = models.CharField(max_length=50, verbose_name=_("Last Name"))
email = models.EmailField(verbose_name=_("Email"))
phone_number = PhoneNumberField(region="SA", verbose_name=_("Phone Number"))
customer = models.ForeignKey(
Customer, on_delete=models.CASCADE, related_name="leads"
CustomerModel, on_delete=models.CASCADE, related_name="leads",
null=True,blank=True
)
id_car_make = models.ForeignKey(
CarMake,
@ -1113,7 +1119,7 @@ class Lead(models.Model):
channel = models.CharField(
max_length=50, choices=Channel.choices, verbose_name=_("Channel")
)
city = models.CharField(max_length=50, verbose_name=_("City"))
address = models.CharField(max_length=50, verbose_name=_("address"))
staff = models.ForeignKey(
Staff,
on_delete=models.SET_NULL,
@ -1146,7 +1152,80 @@ class Lead(models.Model):
def __str__(self):
return f"{self.first_name} {self.last_name}"
@property
def is_converted(self):
return bool(self.customer)
def to_dict(self):
return {
"first_name": str(self.first_name),
"last_name": str(self.last_name),
"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),
"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():
customer = entity.create_customer(
customer_model_kwargs={
"customer_name": self.full_name,
"address_1": self.address,
"phone": self.phone_number,
"email": self.email,
}
)
customer.additional_info = {}
customer.additional_info.update({"info":self.to_dict()})
customer.save()
self.customer = customer
self.save()
def get_latest_schedule(self):
return self.schedules.order_by('-scheduled_at').first()
class Schedule(models.Model):
PURPOSE_CHOICES = [
('Product Demo', 'Product Demo'),
('Follow-Up Call', 'Follow-Up Call'),
('Contract Discussion', 'Contract Discussion'),
('Sales Meeting', 'Sales Meeting'),
('Support Call', 'Support Call'),
('Other', 'Other'),
]
ScheduledType = [
('Call', 'Call'),
('Meeting', 'Meeting'),
('Email', 'Email'),
]
lead = models.ForeignKey(Lead, on_delete=models.CASCADE, related_name='schedules')
customer = models.ForeignKey(CustomerModel, on_delete=models.CASCADE, related_name='schedules')
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)
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
return False
class Meta:
ordering = ['-scheduled_at']
class LeadStatusHistory(models.Model):
lead = models.ForeignKey(
@ -1181,7 +1260,7 @@ class Opportunity(models.Model):
Dealer, on_delete=models.CASCADE, related_name="opportunities"
)
customer = models.ForeignKey(
Customer, on_delete=models.CASCADE, related_name="opportunities"
Customer, on_delete=models.CASCADE, related_name="opportunities"
)
car = models.ForeignKey(
Car, on_delete=models.SET_NULL, null=True, blank=True, verbose_name=_("Car")
@ -1213,7 +1292,7 @@ class Opportunity(models.Model):
verbose_name_plural = _("Opportunities")
def __str__(self):
return f"{self.car.id_car_make.name} - {self.car.id_car_model.name} : {self.customer.get_full_name}"
return f"{self.car.id_car_make.name} - {self.car.id_car_model.name} : {self.customer}"
class Notes(models.Model):

View File

@ -811,7 +811,7 @@ def notify_assigned_staff(sender, instance, created, **kwargs):
if instance.staff: # Check if the lead is assigned
models.Notification.objects.create(
user=instance.staff.user,
message=f"You have been assigned a new lead: {instance.customer.get_full_name}."
message=f"You have been assigned a new lead: {instance.full_name}."
)

View File

@ -92,15 +92,25 @@ urlpatterns = [
path(
"crm/leads/<int:pk>/update/", views.LeadUpdateView.as_view(), name="lead_update"
),
path(
"crm/leads/<int:pk>/delete/", views.LeadDeleteView.as_view(), name="lead_delete"
),
path("crm/leads/<int:pk>/delete/", views.LeadDeleteView, name="lead_delete"),
path("crm/leads/<int:pk>/add-note/", views.add_note_to_lead, name="add_note"),
path("crm/leads/<int:pk>/lead-convert/", views.lead_convert, name="lead_convert"),
path(
"crm/leads/<int:pk>/add-activity/",
views.add_activity_to_lead,
name="add_activity",
),
path(
"crm/leads/<int:pk>/send_lead_email/",
views.send_lead_email,
name="send_lead_email",
),
path(
"crm/leads/<int:pk>/schedule/",
views.schedule_lead,
name="schedule_lead",
),
path(
"crm/opportunities/create/",
views.OpportunityCreateView.as_view(),

View File

@ -1779,7 +1779,7 @@ class OrganizationListView(LoginRequiredMixin, ListView):
class OrganizationDetailView(DetailView):
model = models.Organization
model = CustomerModel
template_name = "organizations/organization_detail.html"
context_object_name = "organization"
@ -1787,12 +1787,8 @@ class OrganizationDetailView(DetailView):
def OrganizationCreateView(request):
if request.method == "POST":
form = forms.OrganizationForm(request.POST)
# upload logo
image = request.FILES.get("logo")
file_name = default_storage.save("images/{}".format(image.name), image)
file_url = default_storage.url(file_name)
organization_dict = {
x: request.POST[x] for x in request.POST if x != "csrfmiddlewaretoken"
}
@ -1806,7 +1802,12 @@ def OrganizationCreateView(request):
"email": organization_dict["email"],
}
)
organization_dict["logo"] = file_url
image = request.FILES.get("logo")
if image:
file_name = default_storage.save("images/{}".format(image.name), image)
file_url = default_storage.url(file_name)
organization_dict["logo"] = file_url
organization_dict["pk"] = str(instance.pk)
instance.additional_info["organization_info"] = organization_dict
instance.additional_info["type"] = "organization"
@ -2926,11 +2927,10 @@ class LeadCreateView(CreateView, SuccessMessageMixin, LoginRequiredMixin):
model = models.Lead
form_class = forms.LeadForm
template_name = "crm/leads/lead_form.html"
# success_message = "Lead created successfully!"
success_message = "Lead created successfully!"
success_url = reverse_lazy("lead_list")
def form_valid(self, form):
print("Form data:", form.cleaned_data) # Debug form data
def form_valid(self, form):
dealer = get_user_type(self.request)
form.instance.dealer = dealer
return super().form_valid(form)
@ -2953,10 +2953,11 @@ class LeadUpdateView(UpdateView):
success_url = reverse_lazy("lead_list")
class LeadDeleteView(DeleteView):
model = models.Lead
template_name = "crm/leads/lead_confirm_delete.html"
success_url = reverse_lazy("lead_list")
def LeadDeleteView(request,pk):
lead = get_object_or_404(models.Lead, pk=pk)
lead.delete()
messages.success(request, "Lead deleted successfully!")
return redirect("lead_list")
def add_note_to_lead(request, pk):
@ -2974,6 +2975,74 @@ def add_note_to_lead(request, pk):
form = forms.NoteForm()
return render(request, "crm/add_note.html", {"form": form, "lead": lead})
def lead_convert(request, pk):
lead = get_object_or_404(models.Lead, pk=pk)
dealer = get_user_type(request)
lead.convert_to_customer(dealer.entity)
messages.success(request, "Lead converted to customer successfully!")
return redirect("lead_list")
def schedule_lead(request, pk):
lead = get_object_or_404(models.Lead, pk=pk)
if request.method == "POST":
form = forms.ScheduleForm(request.POST)
if form.is_valid():
instance = form.save(commit=False)
instance.lead = lead
instance.customer = lead.customer
if hasattr(request.user, "staff"):
instance.scheduled_by = request.user.staff
instance.save()
messages.success(request, "Lead scheduled successfully!")
return redirect("lead_list")
else:
messages.error(request, f"Invalid form data: {str(form.errors)}")
return redirect("lead_list")
form = forms.ScheduleForm()
return render(request, "crm/leads/schedule_lead.html", {"lead": lead, "form": form})
def send_lead_email(request, pk):
lead = get_object_or_404(models.Lead, pk=pk)
dealer = get_user_type(request)
lead.convert_to_customer(dealer.entity)
if request.method == "POST":
send_email(
"manager@tenhal.com",
request.POST.get("to"),
request.POST.get("subject"),
request.POST.get("message"),
)
messages.success(request, "Email sent successfully!")
return redirect("lead_list")
msg = f"""
السلام عليكم
Dear {lead.full_name},
أود أن أشارككم تقدير المشروع الذي ناقشناه. يرجى العثور على الوثيقة التفصيلية للمقترح المرفقة.
I hope this email finds you well. I wanted to share with you the estimate for the project we discussed. Please find the detailed estimate document attached.
يرجى مراجعة المقترح وإعلامي إذا كانت لديك أي أسئلة أو مخاوف. إذا كانت كل شيء يبدو جيدًا، يمكننا المضي قدمًا في المشروع.
Please review the estimate and let me know if you have any questions or concerns. If everything looks good, we can proceed with the project.
شكراً لاهتمامكم بهذا الأمر.
Thank you for your attention to this matter.
تحياتي,
Best regards,
[Your Name]
[Your Position]
[Your Company]
[Your Contact Information]
"""
return render(
request,
"crm/leads/lead_send.html",
{"lead": lead, "message": msg},
)
def add_activity_to_lead(request, pk):
lead = get_object_or_404(models.Lead, pk=pk)
@ -3219,7 +3288,7 @@ class BillDetailView(LoginRequiredMixin, DetailView):
transactions = [
{
"item": x,
"total": Decimal(x.unit_cost) * Decimal(x.quantity),
"total": Decimal(x.unit_cost) * Decimal(x.quantity),
}
for x in txs
]
@ -3454,9 +3523,7 @@ def bill_create(request):
),
}
)
car_list = models.Car.objects.filter(
dealer=dealer, finances__selling_price__gt=0, status="available"
)
car_list = models.Car.objects.filter(dealer=dealer)
context = {
"form": form,
"items": [

View File

@ -52,7 +52,6 @@
<script src="https://cdnjs.cloudflare.com/ajax/libs/jquery/3.7.1/jquery.min.js"
integrity="sha512-v2CJ7UaYy4JwqLDIrZUI/4hqeoQieOmAZNXBeQyjo21dadnwR+8ZaIJVT8EE2iyI61OV8e6M8PP2/4hpQINQ/g=="
crossorigin="anonymous" referrerpolicy="no-referrer"></script>
{% block customCSS %}
@ -135,10 +134,8 @@ function notify(tag,msg){
<script src="https://unpkg.com/htmx.org@2.0.4"></script>
<script src="{% static 'vendors/swiper/swiper-bundle.min.js' %}"></script>
<script src="{% static 'vendors/flatpickr/flatpickr.min.js' %}"></script>
<script>
<script>
{% if entity_slug %}
let entitySlug = "{{ view.kwargs.entity_slug }}"
{% endif %}

View File

@ -1,8 +1,6 @@
{% extends 'base.html' %}
{% load i18n static %}
{% block content %}
<div class="row g-3">
<div class="col-12">
<div class="row align-items-center justify-content-between g-3 mb-3">

View File

@ -1,5 +1,6 @@
{% extends 'base.html' %}
{% load i18n static crispy_forms_filters %}
{% block content %}
<h1>{% if object %}Update{% else %}Create{% endif %}</h1>
<form method="post">

View File

@ -36,13 +36,12 @@
<table class="table fs-9 mb-0 border-top border-translucent">
<thead>
<tr>
<th class="align-middle white-space-nowrap text-uppercase" scope="col" style="width: 10%;">{{ _("Status")|capfirst }}</th>
<th class="align-middle white-space-nowrap text-uppercase" scope="col" style="width: 25%;">
<th class="align-middle white-space-nowrap text-uppercase" scope="col" style="width: 20%;">{{ _("Lead Name")|capfirst }}</th>
<th class="align-middle white-space-nowrap text-uppercase" scope="col" style="width: 15%;">
<div class="d-inline-flex flex-center">
<div class="d-flex align-items-center px-1 py-1 bg-warning-subtle rounded me-2"><span class="text-warning-dark" data-feather="user"></span></div>
<span>{{ _("Name")|capfirst }}</span>
<div class="d-flex align-items-center px-1 py-1 bg-success-subtle rounded me-2"><i class="text-success-dark fas fa-car"></i></div>
<span>{{ _("Car")|capfirst }}</span>
</div>
</th>
<th class="align-middle white-space-nowrap text-uppercase" scope="col" style="width: 15%;">
<div class="d-inline-flex flex-center">
@ -56,12 +55,24 @@
<span>{{ _("Phone Number") }}</span>
</div>
</th>
<th class="align-middle white-space-nowrap text-uppercase" scope="col" style="width: 15%;">
<div class="d-inline-flex flex-center">
<div class="d-flex align-items-center px-1 py-1 bg-primary-subtle rounded me-2"><span class="text-primary-dark" data-feather="phone"></span></div>
<span>{{ _("Schedule") }}</span>
</div>
</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-info-subtle rounded me-2"><span class="text-info-dark" data-feather="database"></span></div>
<span>{{ _("Assigned To")|capfirst }}</span>
</div>
</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-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>
@ -101,29 +112,39 @@
</div>
</div>
</div>
<tr class="hover-actions-trigger btn-reveal-trigger position-static">
<td class="align-middle white-space-nowrap fw-semibold">
<div class="d-flex align-items-center">
{% if lead.status == "new" %}
<span class="badge badge-phoenix badge-phoenix-primary"><span class="badge-label">{{_("New")}}</span><span class="fa fa-bell ms-1"></span></span>
{% elif lead.status == "pending" %}
<span class="badge badge-phoenix badge-phoenix-warning"><span class="badge-label">{{_("Pending")}}</span><span class="fa fa-clock-o ms-1"></span></span>
{% elif lead.status == "in_progress" %}
<span class="badge badge-phoenix badge-phoenix-info"><span class="badge-label">{{_("In Progress")}}</span><span class="fa fa-wrench ms-1"></span></span>
{% elif lead.status == "qualified" %}
<span class="badge badge-phoenix badge-phoenix-success"><span class="badge-label">{{_("Qualified")}}</span><span class="fa fa-check ms-1"></span></span>
{% elif lead.status == "canceled" %}
<span class="badge badge-phoenix badge-phoenix-danger"><span class="badge-label">{{_("Canceled")}}</span><span class="fa fa-times ms-1"></span></span>
{% endif %}
<tr class="hover-actions-trigger btn-reveal-trigger position-static">
<td class="name align-middle white-space-nowrap ps-0">
<div class="d-flex align-items-center"><a href="#!">
</a>
<div><a class="fs-8 fw-bold" href="{% url 'lead_detail' lead.pk %}">{{lead.full_name}}</a>
<div class="d-flex align-items-center">
<p class="mb-0 text-body-highlight fw-semibold fs-9 me-2"></p>
{% if lead.status == "new" %}
<span class="badge badge-phoenix badge-phoenix-primary"><span class="badge-label">{{_("New")}}</span><span class="fa fa-bell ms-1"></span></span>
{% elif lead.status == "pending" %}
<span class="badge badge-phoenix badge-phoenix-warning"><span class="badge-label">{{_("Pending")}}</span><span class="fa fa-clock-o ms-1"></span></span>
{% elif lead.status == "in_progress" %}
<span class="badge badge-phoenix badge-phoenix-info"><span class="badge-label">{{_("In Progress")}}</span><span class="fa fa-wrench ms-1"></span></span>
{% elif lead.status == "qualified" %}
<span class="badge badge-phoenix badge-phoenix-success"><span class="badge-label">{{_("Qualified")}}</span><span class="fa fa-check ms-1"></span></span>
{% elif lead.status == "canceled" %}
<span class="badge badge-phoenix badge-phoenix-danger"><span class="badge-label">{{_("Canceled")}}</span><span class="fa fa-times ms-1"></span></span>
{% endif %}
</div>
</div>
</div>
</td>
</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.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">
<div class="d-flex align-items-center">
<a class="fs-8 fw-bold" href="{% url 'lead_detail' lead.pk %}">{{ lead.customer.get_full_name }}</a>
</div>
</td>
<td class="align-middle white-space-nowrap fw-semibold"><a class="text-body-highlight" href="">{{ lead.customer.email }}</a></td>
<td class="align-middle white-space-nowrap fw-semibold"><a class="text-body-highlight" href="tel:{{ lead.customer.phone_number }}">{{ lead.customer.phone_number }}</a></td>
{% 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>
{% 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>
@ -141,6 +162,11 @@
</button>
<div class="dropdown-menu dropdown-menu-end py-2">
<a href="{% url 'lead_update' lead.id %}" class="dropdown-item text-success-dark">{% trans "Edit" %}</a>
<a href="{% url 'send_lead_email' lead.id %}" class="dropdown-item text-success-dark">{% trans "Send Email" %}</a>
<a href="{% url 'schedule_lead' lead.id %}" class="dropdown-item text-success-dark">{% trans "Set Schedule" %}</a>
{% if not lead.is_converted %}
<a href="{% url 'lead_convert' lead.id %}" class="dropdown-item text-success-dark">{% trans "Convert To Customer" %}</a>
{% endif %}
<div class="dropdown-divider"></div>
<button class="dropdown-item text-danger" data-bs-toggle="modal" data-bs-target="#deleteModal">{% trans "Delete" %}</button>
</div>

View File

@ -0,0 +1,36 @@
{% extends "base.html" %}
{% load crispy_forms_filters %}
{% load i18n static %}
{% block title %}{{ _("Leads") }}{% endblock title %}
{% block content %}
<div class="card email-content">
<h5 class="card-header">Send Mail</h5>
<div class="card-body">
<form class="d-flex flex-column h-100" action="{% url 'send_lead_email' lead.pk %}" method="post">
{% csrf_token %}
<div class="row g-3 mb-2">
<div class="col-12">
<input class="form-control" name="to" type="text" placeholder="To" value="{{lead.email}}" />
</div>
<div class="col-12">
<input class="form-control" name="subject" type="text" placeholder="Subject" value="" />
</div>
</div>
<div class="mb-3 flex-1">
<textarea class="form-control" name="message" rows="15" placeholder="Message">{{message}}</textarea>
</div>
<div class="d-flex justify-content-between align-items-center">
<div class="d-flex">
<a href="{% url 'lead_detail' lead.pk %}" class="btn btn-link text-body fs-10 text-decoration-none">Discard</a>
<button class="btn btn-primary fs-10" type="submit">Send<span class="fa-solid fa-paper-plane ms-1"></span></button>
</div>
</div>
</form>
</div>
</div>
</div>
</div>
{% endblock content %}

View File

@ -0,0 +1,21 @@
{% extends 'base.html' %}
{% load i18n static crispy_forms_filters %}
{% block content %}
<h1>{% if object %}Update{% else %}Create{% endif %}</h1>
<form method="post">
{% csrf_token %}
{{ form|crispy }}
<button
type="submit"
name="schedule_lead"
id="lead-save"
class="btn btn-phoenix-primary"
>
{{ _("Save") }}
</button>
<a href="{% url 'lead_list' %}" class="btn btn-phoenix-secondary">
{{ _("Cancel") }}
</a>
</form>
{% endblock %}

View File

@ -56,7 +56,7 @@
<!-- ============================================-->
<!-- <section> begin ============================-->
<section class="pt-5 pb-9 bg-body-emphasis dark__bg-gray-1200 border-top">
<div class="row-small mt-3">
<div class="row-small mt-3 mx-3">
<div class="d-flex justify-content-between align-items-end mb-4">
<h2 class="mb-0">{% trans 'Bill' %}</h2>
<div class="d-flex align-items-center gap-2">

View File

@ -5,14 +5,14 @@
<div class="row my-4">
<h2>{{ organization.get_local_name }}</h2>
<ul class="list-group mb-4">
<li class="list-group-item"><strong>{% trans "CRN" %}:</strong> {{ organization.crn }}</li>
<li class="list-group-item"><strong>{% trans "VRN" %}:</strong> {{ organization.vrn }}</li>
<li class="list-group-item"><strong>{% trans "Phone" %}:</strong> {{ organization.phone_number }}</li>
<li class="list-group-item"><strong>{% trans "Address" %}:</strong> {{ organization.address }}</li>
<li class="list-group-item"><strong>{% trans "CRN" %}:</strong> {{ organization.additional_info.organization_info.crn }}</li>
<li class="list-group-item"><strong>{% trans "VRN" %}:</strong> {{ organization.additional_info.organization_info.vrn }}</li>
<li class="list-group-item"><strong>{% trans "Phone" %}:</strong> {{ organization.additional_info.organization_info.phone_number }}</li>
<li class="list-group-item"><strong>{% trans "Address" %}:</strong> {{ organization.additional_info.organization_info.address }}</li>
</ul>
<div class="d-flex">
<a href="{% url 'organization_update' organization.id %}" class="btn btn-sm btn-warning me-2">{% trans "Edit" %}</a>
<form method="post" action="{% url 'organization_delete' organization.id %}">
<a href="{% url 'organization_update' organization.pk %}" class="btn btn-sm btn-warning me-2">{% trans "Edit" %}</a>
<form method="post" action="{% url 'organization_delete' organization.pk %}">
{% csrf_token %}
<button type="submit" class="btn btn-sm btn-danger">{% trans "Delete" %}</button>
</form>

View File

@ -108,9 +108,9 @@
<td class="name align-middle white-space-nowrap ps-0">
<div class="d-flex align-items-center">
<div><a class="fs-8 fw-bold" href="{% url 'organization_detail' org.pk %}">{{ org.name }}</a>
<div><a class="fs-8 fw-bold" href="{% url 'organization_detail' org.pk %}">{{ org.customer_name }}</a>
<div class="d-flex align-items-center">
<p class="mb-0 text-body-highlight fw-semibold fs-9 me-2">{{ org.arabic_name }}</p><span class="badge badge-phoenix badge-phoenix-primary">{{ org.pk}}</span>
<p class="mb-0 text-body-highlight fw-semibold fs-9 me-2"></p><span class="badge badge-phoenix badge-phoenix-primary">{{ org.customer_name}}</span>
</div>
</div>
</div>

View File

@ -55,8 +55,8 @@
</div>
<!-- ============================================-->
<!-- <section> begin ============================-->
<section class="pt-5 pb-9">
<div class="row-small mt-3">
<section class="pt-5 pb-9 bg-body-emphasis dark__bg-gray-1200 border-top">
<div class="row-small mt-3 mx-3">
<div class="d-flex justify-content-between align-items-end mb-4 mx-3">
<h2 class="mb-0">{% trans 'Invoice' %}</h2>
<div class="d-flex align-items-center gap-2">