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, Dealer,
# Branch, # Branch,
Vendor, Vendor,
Schedule,
Customer, Customer,
Car, Car,
CarTransfer, CarTransfer,
@ -38,7 +39,7 @@ from .models import (
SaleOrder SaleOrder
) )
from django_ledger.models import ItemModel, InvoiceModel, BillModel,VendorModel 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 _ from django.utils.translation import gettext_lazy as _
import django_tables2 as tables import django_tables2 as tables
from django.forms import formset_factory from django.forms import formset_factory
@ -646,8 +647,12 @@ class EmailForm(forms.Form):
class LeadForm(forms.ModelForm): class LeadForm(forms.ModelForm):
class Meta: class Meta:
model = Lead model = Lead
fields = ['customer', fields = [
'city', 'first_name',
'last_name',
'email',
'phone_number',
'address',
'id_car_make', 'id_car_make',
'id_car_model', 'id_car_model',
'year', 'year',
@ -666,6 +671,11 @@ class LeadForm(forms.ModelForm):
(obj.id_car_make, obj.get_local_name()) for obj in queryset (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 NoteForm(forms.ModelForm):
class Meta: class Meta:

View File

@ -4,6 +4,8 @@ from django.utils import timezone
from django.shortcuts import redirect from django.shortcuts import redirect
from django.urls import reverse from django.urls import reverse
from inventory.utils import get_user_type
logger = logging.getLogger('user_activity') logger = logging.getLogger('user_activity')
@ -37,7 +39,8 @@ class InjectParamsMiddleware:
def __call__(self, request): def __call__(self, request):
try: try:
request.entity = request.user.dealer.entity # request.entity = request.user.dealer.entity
request.dealer = get_user_type(request)
except Exception as e: except Exception as e:
pass 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 import itertools
from uuid import uuid4 from uuid import uuid4
from django.conf import settings from django.conf import settings
@ -1087,8 +1088,13 @@ class Representative(models.Model, LocalizedNameMixin):
class Lead(models.Model): class Lead(models.Model):
dealer = models.ForeignKey(Dealer, on_delete=models.CASCADE, related_name="leads") 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 = 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( id_car_make = models.ForeignKey(
CarMake, CarMake,
@ -1113,7 +1119,7 @@ class Lead(models.Model):
channel = models.CharField( channel = models.CharField(
max_length=50, choices=Channel.choices, verbose_name=_("Channel") 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 = models.ForeignKey(
Staff, Staff,
on_delete=models.SET_NULL, on_delete=models.SET_NULL,
@ -1146,7 +1152,80 @@ class Lead(models.Model):
def __str__(self): def __str__(self):
return f"{self.first_name} {self.last_name}" 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): class LeadStatusHistory(models.Model):
lead = models.ForeignKey( lead = models.ForeignKey(
@ -1181,7 +1260,7 @@ class Opportunity(models.Model):
Dealer, on_delete=models.CASCADE, related_name="opportunities" Dealer, on_delete=models.CASCADE, related_name="opportunities"
) )
customer = models.ForeignKey( customer = models.ForeignKey(
Customer, on_delete=models.CASCADE, related_name="opportunities" Customer, on_delete=models.CASCADE, related_name="opportunities"
) )
car = models.ForeignKey( car = models.ForeignKey(
Car, on_delete=models.SET_NULL, null=True, blank=True, verbose_name=_("Car") 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") verbose_name_plural = _("Opportunities")
def __str__(self): 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): 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 if instance.staff: # Check if the lead is assigned
models.Notification.objects.create( models.Notification.objects.create(
user=instance.staff.user, 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( path(
"crm/leads/<int:pk>/update/", views.LeadUpdateView.as_view(), name="lead_update" "crm/leads/<int:pk>/update/", views.LeadUpdateView.as_view(), name="lead_update"
), ),
path( path("crm/leads/<int:pk>/delete/", views.LeadDeleteView, name="lead_delete"),
"crm/leads/<int:pk>/delete/", views.LeadDeleteView.as_view(), name="lead_delete"
),
path("crm/leads/<int:pk>/add-note/", views.add_note_to_lead, name="add_note"), 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( path(
"crm/leads/<int:pk>/add-activity/", "crm/leads/<int:pk>/add-activity/",
views.add_activity_to_lead, views.add_activity_to_lead,
name="add_activity", 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( path(
"crm/opportunities/create/", "crm/opportunities/create/",
views.OpportunityCreateView.as_view(), views.OpportunityCreateView.as_view(),

View File

@ -1779,7 +1779,7 @@ class OrganizationListView(LoginRequiredMixin, ListView):
class OrganizationDetailView(DetailView): class OrganizationDetailView(DetailView):
model = models.Organization model = CustomerModel
template_name = "organizations/organization_detail.html" template_name = "organizations/organization_detail.html"
context_object_name = "organization" context_object_name = "organization"
@ -1787,12 +1787,8 @@ class OrganizationDetailView(DetailView):
def OrganizationCreateView(request): def OrganizationCreateView(request):
if request.method == "POST": if request.method == "POST":
form = forms.OrganizationForm(request.POST) form = forms.OrganizationForm(request.POST)
# upload logo # 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 = { organization_dict = {
x: request.POST[x] for x in request.POST if x != "csrfmiddlewaretoken" x: request.POST[x] for x in request.POST if x != "csrfmiddlewaretoken"
} }
@ -1806,7 +1802,12 @@ def OrganizationCreateView(request):
"email": organization_dict["email"], "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) organization_dict["pk"] = str(instance.pk)
instance.additional_info["organization_info"] = organization_dict instance.additional_info["organization_info"] = organization_dict
instance.additional_info["type"] = "organization" instance.additional_info["type"] = "organization"
@ -2926,11 +2927,10 @@ class LeadCreateView(CreateView, SuccessMessageMixin, LoginRequiredMixin):
model = models.Lead model = models.Lead
form_class = forms.LeadForm form_class = forms.LeadForm
template_name = "crm/leads/lead_form.html" template_name = "crm/leads/lead_form.html"
# success_message = "Lead created successfully!" success_message = "Lead created successfully!"
success_url = reverse_lazy("lead_list") success_url = reverse_lazy("lead_list")
def form_valid(self, form): def form_valid(self, form):
print("Form data:", form.cleaned_data) # Debug form data
dealer = get_user_type(self.request) dealer = get_user_type(self.request)
form.instance.dealer = dealer form.instance.dealer = dealer
return super().form_valid(form) return super().form_valid(form)
@ -2953,10 +2953,11 @@ class LeadUpdateView(UpdateView):
success_url = reverse_lazy("lead_list") success_url = reverse_lazy("lead_list")
class LeadDeleteView(DeleteView): def LeadDeleteView(request,pk):
model = models.Lead lead = get_object_or_404(models.Lead, pk=pk)
template_name = "crm/leads/lead_confirm_delete.html" lead.delete()
success_url = reverse_lazy("lead_list") messages.success(request, "Lead deleted successfully!")
return redirect("lead_list")
def add_note_to_lead(request, pk): def add_note_to_lead(request, pk):
@ -2974,6 +2975,74 @@ def add_note_to_lead(request, pk):
form = forms.NoteForm() form = forms.NoteForm()
return render(request, "crm/add_note.html", {"form": form, "lead": lead}) 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): def add_activity_to_lead(request, pk):
lead = get_object_or_404(models.Lead, pk=pk) lead = get_object_or_404(models.Lead, pk=pk)
@ -3219,7 +3288,7 @@ class BillDetailView(LoginRequiredMixin, DetailView):
transactions = [ transactions = [
{ {
"item": x, "item": x,
"total": Decimal(x.unit_cost) * Decimal(x.quantity), "total": Decimal(x.unit_cost) * Decimal(x.quantity),
} }
for x in txs for x in txs
] ]
@ -3454,9 +3523,7 @@ def bill_create(request):
), ),
} }
) )
car_list = models.Car.objects.filter( car_list = models.Car.objects.filter(dealer=dealer)
dealer=dealer, finances__selling_price__gt=0, status="available"
)
context = { context = {
"form": form, "form": form,
"items": [ "items": [

View File

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

View File

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

View File

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

View File

@ -36,13 +36,12 @@
<table class="table fs-9 mb-0 border-top border-translucent"> <table class="table fs-9 mb-0 border-top border-translucent">
<thead> <thead>
<tr> <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: 20%;">{{ _("Lead Name")|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: 15%;">
<div class="d-inline-flex flex-center"> <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> <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>{{ _("Name")|capfirst }}</span> <span>{{ _("Car")|capfirst }}</span>
</div> </div>
</th> </th>
<th class="align-middle white-space-nowrap text-uppercase" scope="col" style="width: 15%;"> <th class="align-middle white-space-nowrap text-uppercase" scope="col" style="width: 15%;">
<div class="d-inline-flex flex-center"> <div class="d-inline-flex flex-center">
@ -56,12 +55,24 @@
<span>{{ _("Phone Number") }}</span> <span>{{ _("Phone Number") }}</span>
</div> </div>
</th> </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%;"> <th class="align-middle white-space-nowrap text-uppercase" scope="col" style="width: 10%;">
<div class="d-inline-flex flex-center"> <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> <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> <span>{{ _("Source")|capfirst }}</span>
</div> </div>
</th> </th>``
<th class="align-middle white-space-nowrap text-uppercase" scope="col" style="width: 10%;"> <th class="align-middle white-space-nowrap text-uppercase" scope="col" style="width: 10%;">
<div class="d-inline-flex flex-center"> <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> <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> </div>
</div> </div>
<tr class="hover-actions-trigger btn-reveal-trigger position-static"> <tr class="hover-actions-trigger btn-reveal-trigger position-static">
<td class="align-middle white-space-nowrap fw-semibold"> <td class="name align-middle white-space-nowrap ps-0">
<div class="d-flex align-items-center"> <div class="d-flex align-items-center"><a href="#!">
{% if lead.status == "new" %} </a>
<span class="badge badge-phoenix badge-phoenix-primary"><span class="badge-label">{{_("New")}}</span><span class="fa fa-bell ms-1"></span></span> <div><a class="fs-8 fw-bold" href="{% url 'lead_detail' lead.pk %}">{{lead.full_name}}</a>
{% elif lead.status == "pending" %} <div class="d-flex align-items-center">
<span class="badge badge-phoenix badge-phoenix-warning"><span class="badge-label">{{_("Pending")}}</span><span class="fa fa-clock-o ms-1"></span></span> <p class="mb-0 text-body-highlight fw-semibold fs-9 me-2"></p>
{% elif lead.status == "in_progress" %} {% if lead.status == "new" %}
<span class="badge badge-phoenix badge-phoenix-info"><span class="badge-label">{{_("In Progress")}}</span><span class="fa fa-wrench ms-1"></span></span> <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 == "qualified" %} {% elif lead.status == "pending" %}
<span class="badge badge-phoenix badge-phoenix-success"><span class="badge-label">{{_("Qualified")}}</span><span class="fa fa-check ms-1"></span></span> <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 == "canceled" %} {% elif lead.status == "in_progress" %}
<span class="badge badge-phoenix badge-phoenix-danger"><span class="badge-label">{{_("Canceled")}}</span><span class="fa fa-times ms-1"></span></span> <span class="badge badge-phoenix badge-phoenix-info"><span class="badge-label">{{_("In Progress")}}</span><span class="fa fa-wrench ms-1"></span></span>
{% endif %} {% 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> </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"> <td class="align-middle white-space-nowrap fw-semibold">
<div class="d-flex align-items-center"> {% if lead.get_latest_schedule %}
<a class="fs-8 fw-bold" href="{% url 'lead_detail' lead.pk %}">{{ lead.customer.get_full_name }}</a> <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>
</div> <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>
</td> {% endif %}
<td class="align-middle white-space-nowrap fw-semibold"><a class="text-body-highlight" href="">{{ lead.customer.email }}</a></td> </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> <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 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 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 text-body-tertiary">{{ lead.created|date }}</td>
@ -141,6 +162,11 @@
</button> </button>
<div class="dropdown-menu dropdown-menu-end py-2"> <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 '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> <div class="dropdown-divider"></div>
<button class="dropdown-item text-danger" data-bs-toggle="modal" data-bs-target="#deleteModal">{% trans "Delete" %}</button> <button class="dropdown-item text-danger" data-bs-toggle="modal" data-bs-target="#deleteModal">{% trans "Delete" %}</button>
</div> </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> begin ============================-->
<section class="pt-5 pb-9 bg-body-emphasis dark__bg-gray-1200 border-top"> <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"> <div class="d-flex justify-content-between align-items-end mb-4">
<h2 class="mb-0">{% trans 'Bill' %}</h2> <h2 class="mb-0">{% trans 'Bill' %}</h2>
<div class="d-flex align-items-center gap-2"> <div class="d-flex align-items-center gap-2">

View File

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

View File

@ -108,9 +108,9 @@
<td class="name align-middle white-space-nowrap ps-0"> <td class="name align-middle white-space-nowrap ps-0">
<div class="d-flex align-items-center"> <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"> <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> </div>
</div> </div>

View File

@ -55,8 +55,8 @@
</div> </div>
<!-- ============================================--> <!-- ============================================-->
<!-- <section> begin ============================--> <!-- <section> begin ============================-->
<section class="pt-5 pb-9"> <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 mx-3"> <div class="d-flex justify-content-between align-items-end mb-4 mx-3">
<h2 class="mb-0">{% trans 'Invoice' %}</h2> <h2 class="mb-0">{% trans 'Invoice' %}</h2>
<div class="d-flex align-items-center gap-2"> <div class="d-flex align-items-center gap-2">