This commit is contained in:
gitea 2025-02-09 07:36:28 +00:00
parent 1f2cf5a7ec
commit 778a41aa47
10 changed files with 198 additions and 98 deletions

View File

@ -1,3 +1,4 @@
from django.urls import reverse
from django_countries.widgets import CountrySelectWidget from django_countries.widgets import CountrySelectWidget
from phonenumber_field.formfields import PhoneNumberField from phonenumber_field.formfields import PhoneNumberField
from django.core.validators import MinLengthValidator from django.core.validators import MinLengthValidator
@ -35,8 +36,7 @@ from .models import (
# SaleQuotationCar, # SaleQuotationCar,
AdditionalServices, AdditionalServices,
Staff, Staff,
Opportunity, Priority, Sources, Lead, Activity, Notes, CarModel, Opportunity, Priority, Sources, Lead, Activity, Notes, CarModel,SaleOrder,CarMake
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,DateTimeInput from django.forms import ModelMultipleChoiceField, ValidationError, DateInput,DateTimeInput
@ -650,6 +650,12 @@ class EmailForm(forms.Form):
class LeadForm(forms.ModelForm): class LeadForm(forms.ModelForm):
id_car_make = forms.ModelChoiceField(label="Make",
queryset=CarMake.objects.filter(is_sa_import=True),
widget=forms.Select(attrs={"class": "form-control form-control-sm","hx-get":"","hx-include":"#id_id_car_make","hx-select":"#div_id_id_car_model","hx-target":"#div_id_id_car_model","hx-swap":"outerHTML"}),
required=True
)
id_car_model = forms.ModelChoiceField(label="Model", queryset=CarModel.objects.none(),widget=forms.Select(attrs={"class": "form-control form-control-sm"}),required=True)
class Meta: class Meta:
model = Lead model = Lead
fields = [ fields = [
@ -658,7 +664,9 @@ class LeadForm(forms.ModelForm):
'email', 'email',
'phone_number', 'phone_number',
'address', 'address',
'car', 'id_car_make',
'id_car_model',
'year',
'source', 'source',
'channel', 'channel',
'staff', 'staff',
@ -666,8 +674,8 @@ class LeadForm(forms.ModelForm):
] ]
def __init__(self, *args, **kwargs): def __init__(self, *args, **kwargs):
super().__init__(*args, **kwargs) super().__init__(*args, **kwargs)
if "id_car_make" in self.fields: if "id_car_make" in self.fields:
queryset = self.fields["id_car_make"].queryset.filter(is_sa_import=True) queryset = self.fields["id_car_make"].queryset.filter(is_sa_import=True)
self.fields["id_car_make"].choices = [ self.fields["id_car_make"].choices = [

View File

@ -0,0 +1,14 @@
# Generated by Django 4.2.17 on 2025-02-06 10:08
from django.db import migrations
class Migration(migrations.Migration):
dependencies = [
('inventory', '0002_alter_carregistration_car'),
('inventory', '0011_remove_lead_year_alter_schedule_customer'),
]
operations = [
]

View File

@ -0,0 +1,23 @@
# Generated by Django 4.2.17 on 2025-02-06 11:25
from django.db import migrations, models
class Migration(migrations.Migration):
dependencies = [
('inventory', '0012_merge_20250206_1308'),
]
operations = [
migrations.AlterField(
model_name='carregistration',
name='text2',
field=models.CharField(blank=True, max_length=1, null=True, verbose_name='Text 2'),
),
migrations.AlterField(
model_name='carregistration',
name='text3',
field=models.CharField(blank=True, max_length=1, null=True, verbose_name='Text 3'),
),
]

View File

@ -0,0 +1,33 @@
# Generated by Django 4.2.17 on 2025-02-06 12:07
from django.db import migrations, models
import django.db.models.deletion
class Migration(migrations.Migration):
dependencies = [
('inventory', '0013_alter_carregistration_text2_and_more'),
]
operations = [
migrations.RemoveField(
model_name='lead',
name='car',
),
migrations.AddField(
model_name='lead',
name='id_car_make',
field=models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.DO_NOTHING, to='inventory.carmake', verbose_name='Make'),
),
migrations.AddField(
model_name='lead',
name='id_car_model',
field=models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.DO_NOTHING, to='inventory.carmodel', verbose_name='Model'),
),
migrations.AddField(
model_name='lead',
name='year',
field=models.PositiveSmallIntegerField(blank=True, null=True, verbose_name='Year'),
),
]

View File

@ -1096,26 +1096,26 @@ class Lead(models.Model):
CustomerModel, on_delete=models.CASCADE, related_name="leads", CustomerModel, on_delete=models.CASCADE, related_name="leads",
null=True,blank=True null=True,blank=True
) )
car = models.ForeignKey( # car = models.ForeignKey(
Car, on_delete=models.DO_NOTHING, blank=True, null=True, verbose_name=_("Car") # 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
) )
# 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( source = models.CharField(
max_length=50, choices=Sources.choices, verbose_name=_("Source") max_length=50, choices=Sources.choices, verbose_name=_("Source")
) )
@ -1167,7 +1167,8 @@ class Lead(models.Model):
"email": str(self.email), "email": str(self.email),
"address": str(self.address), "address": str(self.address),
"phone_number": str(self.phone_number), "phone_number": str(self.phone_number),
"car": self.car.to_dict(), "make": str(self.id_car_make.name),
"model": str(self.id_car_model.name),
"created_at": str(self.created.strftime("%Y-%m-%d")), "created_at": str(self.created.strftime("%Y-%m-%d")),
} }
@property @property
@ -1225,8 +1226,9 @@ class Schedule(models.Model):
def __str__(self): def __str__(self):
return f"Scheduled {self.purpose} with {self.customer.customer_name} on {self.scheduled_at}" return f"Scheduled {self.purpose} with {self.customer.customer_name} on {self.scheduled_at}"
@property
def schedule_past_date(self): def schedule_past_date(self):
if self.scheduled_at < timezone.now(): if self.scheduled_at < now():
return True return True
return False return False

View File

@ -319,10 +319,10 @@ def number_to_words_arabic(number):
return ' '.join(words) return ' '.join(words)
@register.filter(name='num2words') # @register.filter(name='num2words')
def num2words(number, language='en'): # def num2words(number, language='en'):
"""Template filter to convert a number to words in the specified language.""" # """Template filter to convert a number to words in the specified language."""
if language == 'ar': # if language == 'ar':
return number_to_words_arabic(number) # return number_to_words_arabic(number)
else: # else:
return number_to_words_english(number) # return number_to_words_english(number)

View File

@ -88,7 +88,7 @@ urlpatterns = [
path( path(
"crm/leads/<int:pk>/view/", views.LeadDetailView.as_view(), name="lead_detail" "crm/leads/<int:pk>/view/", views.LeadDetailView.as_view(), name="lead_detail"
), ),
path("crm/leads/create/", views.LeadCreateView.as_view(), name="lead_create"), path("crm/leads/create/", views.lead_create, name="lead_create"),
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"
), ),
@ -194,7 +194,6 @@ urlpatterns = [
), ),
path("cars/add/", views.CarCreateView.as_view(), name="car_add"), path("cars/add/", views.CarCreateView.as_view(), name="car_add"),
path("ajax/", views.AjaxHandlerView.as_view(), name="ajax_handler"), path("ajax/", views.AjaxHandlerView.as_view(), name="ajax_handler"),
path("cars/get-car-models/", views.get_car_models, name="get_car_models"),
path( path(
"cars/<int:car_pk>/add-color/", views.CarColorCreate.as_view(), name="add_color" "cars/<int:car_pk>/add-color/", views.CarColorCreate.as_view(), name="add_color"
), ),

View File

@ -1,3 +1,4 @@
from appointment.models import Appointment
from calendar import month_name from calendar import month_name
from random import randint from random import randint
from rich import print from rich import print
@ -2944,28 +2945,25 @@ class LeadDetailView(DetailView):
return context return context
class LeadCreateView(CreateView, SuccessMessageMixin, LoginRequiredMixin): def lead_create(request):
model = models.Lead form = forms.LeadForm()
form_class = forms.LeadForm make = request.GET.get("id_car_make",None)
template_name = "crm/leads/lead_form.html" if make:
success_message = "Lead created successfully!" form.fields['id_car_model'].queryset = models.CarModel.objects.filter(id_car_make=int(make))
success_url = reverse_lazy("lead_list") if request.method == "POST":
form = forms.LeadForm(request.POST)
def form_valid(self, form): form.fields['id_car_model'].queryset = models.CarModel.objects.filter(id_car_make=int(request.POST['id_car_make']))
dealer = get_user_type(self.request) if form.is_valid():
form.instance.dealer = dealer instance = form.save(commit=False)
return super().form_valid(form) dealer = get_user_type(request)
instance = form.save(commit=False)
instance.dealer = dealer
def get_car_models(request): instance.save()
make_id = request.GET.get("id_car_make")
if make_id: messages.success(request, "Lead created successfully!")
car_models = models.CarModel.objects.filter(id_car_make=make_id).values( return redirect("lead_list")
"id_car_model", "name", "arabic_name"
)
return JsonResponse(list(car_models), safe=False)
return JsonResponse([], safe=False)
return render(request, "crm/leads/lead_form.html", {"form": form})
class LeadUpdateView(UpdateView): class LeadUpdateView(UpdateView):
model = models.Lead model = models.Lead
@ -2973,6 +2971,10 @@ class LeadUpdateView(UpdateView):
template_name = "crm/leads/lead_form.html" template_name = "crm/leads/lead_form.html"
success_url = reverse_lazy("lead_list") success_url = reverse_lazy("lead_list")
def get_form(self, form_class=None):
form = super().get_form(form_class)
form.fields["id_car_model"].queryset = form.instance.id_car_make.carmodel_set.all()
return form
def LeadDeleteView(request,pk): def LeadDeleteView(request,pk):
lead = get_object_or_404(models.Lead, pk=pk) lead = get_object_or_404(models.Lead, pk=pk)

View File

@ -4,49 +4,68 @@ from decimal import Decimal
from django_ledger.models import EstimateModel,EntityModel from django_ledger.models import EstimateModel,EntityModel
from rich import print from rich import print
from datetime import date from datetime import date
from inventory.models import VatRate from inventory.models import VatRate,Lead,CarMake,CarModel,Schedule
from inventory.utils import CarFinanceCalculator from inventory.utils import CarFinanceCalculator
from appointment.models import Appointment,AppointmentRequest,Service,StaffMember
from django.contrib.auth import get_user_model
from django_ledger.io.io_core import get_localdate
from datetime import datetime, timedelta
def run(): User = get_user_model()
# estimate = EstimateModel.objects.first()
# calculator = CarFinanceCalculator(estimate) def run():
# finance_data = calculator.get_finance_data() # print(Service.objects.first().pk)
# print(Appointment.objects.first().client)
# print(finance_data) # appointment = Appointment.objects.create(
# entity = EntityModel.objects.get(name="ismail") # client_name="John Doe",
# bs_report = entity.get_balance_sheet_statement( # client_email="john@example.com",
# to_date=date(2025, 1, 1), # service="Haircut",
# save_pdf=False, # date_time="2023-10-15 10:00:00",
# filepath='./' # status="pending")
# ) make = CarMake.objects.first()
# Lead.objects.create(
# ic_report = entity.get_income_statement( # first_name="John",
# from_date=date(2022, 1, 1), # last_name="Doe",
# to_date=date(2022, 12, 31), # email="john@example.com",
# save_pdf=False, # phone_number="123-456-7890",
# filepath='./' # address="123 Main St",
# id_car_make=make,
# id_car_model=make.carmodel_set.first(),
# year="2022",
# source="website",
# channel="online",
# staff="John Doe",
# priority="high",
# ) # )
# # print(bs_report) # schedult = Schedule.objects.create(
# print(ic_report.get_report_data()) # name="John Doe",
# estimate = EstimateModel.objects.first() # email="john@example.com",
# calculator = CarFinanceCalculator(estimate) # phone_number="123-456-7890",
# finance_data = calculator.get_finance_data() # address="123 Main St",
# id_car_make=make,
# id_car_model=make.carmodel_set.first(),
# year="2022",
# source="website",
# channel="online",
# staff="John Doe",
# priority="high",
# )
service = Service.objects.first()
appointment_request = AppointmentRequest.objects.create(
date=get_localdate(),
start_time=datetime.now().strftime("%H:%M:%S"),
end_time=datetime.time(datetime.now() + timedelta(minutes=30)).strftime("%H:%M:%S"),
service=service,
staff_member=StaffMember.objects.first(),
)
appointment = Appointment.objects.create(
# invoice_itemtxs = { client=User.objects.first(),
# i.get("item_number"): { appointment_request=appointment_request,
# "unit_cost": i.get("total_price"), phone="123-456-7890",
# "quantity": i.get("quantity"), address="123 Main St",
# "total_amount": i.get("total_vat"), )
# }
# for i in finance_data.get("cars")
# }
# invoice = InvoiceModel.objects.first()
entity = EntityModel.objects.filter(name="ismail").first()
invoice_qs = InvoiceModel.objects.for_entity(
entity_slug=entity.slug,
user_model=entity.admin,
).unpaid()
print(accruable_net_summary(invoice_qs))

View File

@ -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> <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>
@ -135,13 +135,13 @@
</div> </div>
</td> </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.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="">{{ 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"><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">
{% if lead.get_latest_schedule %} {% if lead.get_latest_schedule %}
{% if lead.get_latest_schedule.scheduled_type == "Call" %} {% 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> <span class="badge badge-phoenix badge-phoenix-primary text-primary {% if lead.get_latest_schedule.schedule_past_date %}badge-phoenix-danger text-danger{% endif %} fw-semibold"><span class="text-primary {% if lead.get_latest_schedule.schedule_past_date %}text-danger{% endif %}" data-feather="phone"></span>
{{ lead.get_latest_schedule.scheduled_at }}</span> {{ lead.get_latest_schedule.scheduled_at }}</span>
{% elif lead.get_latest_schedule.scheduled_type == "Meeting" %} {% 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> <span class="badge badge-phoenix badge-phoenix-success text-success fw-semibold"><span class="text-success" data-feather="calendar"></span>