This commit is contained in:
gitea 2025-02-09 16:19:52 +00:00
parent ffd72e2d93
commit f4111803bb
10 changed files with 210 additions and 83 deletions

View File

@ -686,7 +686,7 @@ class ScheduleForm(forms.ModelForm):
scheduled_at = forms.DateTimeField(widget=DateTimeInput(attrs={'type': 'datetime-local'})) scheduled_at = forms.DateTimeField(widget=DateTimeInput(attrs={'type': 'datetime-local'}))
class Meta: class Meta:
model = Schedule model = Schedule
fields = ['purpose','scheduled_type', 'scheduled_at', 'notes'] fields = ['purpose','scheduled_type', 'scheduled_at','duration', 'notes']
class NoteForm(forms.ModelForm): class NoteForm(forms.ModelForm):

View File

@ -0,0 +1,14 @@
# Generated by Django 4.2.17 on 2025-02-09 08:16
from django.db import migrations
class Migration(migrations.Migration):
dependencies = [
('inventory', '0012_merge_20250206_1308'),
('inventory', '0014_remove_lead_car_lead_id_car_make_lead_id_car_model_and_more'),
]
operations = [
]

View File

@ -0,0 +1,19 @@
# Generated by Django 4.2.17 on 2025-02-09 08:23
import datetime
from django.db import migrations, models
class Migration(migrations.Migration):
dependencies = [
('inventory', '0015_merge_20250209_1116'),
]
operations = [
migrations.AddField(
model_name='schedule',
name='duration',
field=models.DurationField(default=datetime.timedelta(seconds=300)),
),
]

View File

@ -0,0 +1,18 @@
# Generated by Django 4.2.17 on 2025-02-09 11:36
from django.db import migrations, models
class Migration(migrations.Migration):
dependencies = [
('inventory', '0016_schedule_duration'),
]
operations = [
migrations.AddField(
model_name='car',
name='hash',
field=models.CharField(blank=True, max_length=64, null=True, verbose_name='Hash'),
),
]

View File

@ -1,38 +1,21 @@
# from datetime import timezone from decimal import Decimal
from django.utils import timezone import hashlib
import itertools from django.db import models
from uuid import uuid4 from datetime import timedelta
from django.conf import settings
from django.db import models, transaction
from django.db.models import Sum, F, Count
from django.contrib.auth.models import User, UserManager from django.contrib.auth.models import User, UserManager
from django.db.models.signals import pre_save, post_save
from django.dispatch import receiver
from django.utils.translation import gettext_lazy as _ from django.utils.translation import gettext_lazy as _
from django_ledger.models import ( from django_ledger.models import (
VendorModel, VendorModel,
EntityModel, EntityModel,
EntityUnitModel,
ItemModel, ItemModel,
AccountModel,
ItemModelAbstract,
UnitOfMeasureModel,
CustomerModel, CustomerModel,
ItemModelQuerySet,
) )
from django_ledger.io.io_core import get_localdate from django_ledger.io.io_core import get_localdate
from django.db.models import Sum
from decimal import Decimal, InvalidOperation
from django.core.exceptions import ValidationError from django.core.exceptions import ValidationError
from phonenumber_field.modelfields import PhoneNumberField from phonenumber_field.modelfields import PhoneNumberField
from django.utils.timezone import now from django.utils.timezone import now
from sqlalchemy.orm.base import object_state
from .utilities.financials import get_financial_value, get_total, get_total_financials
from django.db.models import FloatField
from .mixins import LocalizedNameMixin from .mixins import LocalizedNameMixin
from django_ledger.models import EntityModel, ItemModel,EstimateModel,InvoiceModel from django_ledger.models import EntityModel, ItemModel,EstimateModel,InvoiceModel
from django_countries.fields import CountryField
from django.contrib.contenttypes.fields import GenericForeignKey from django.contrib.contenttypes.fields import GenericForeignKey
from django.contrib.contenttypes.models import ContentType from django.contrib.contenttypes.models import ContentType
@ -403,6 +386,11 @@ class Car(models.Model):
remarks = models.TextField(blank=True, null=True, verbose_name=_("Remarks")) remarks = models.TextField(blank=True, null=True, verbose_name=_("Remarks"))
mileage = models.IntegerField(blank=True, null=True, verbose_name=_("Mileage")) mileage = models.IntegerField(blank=True, null=True, verbose_name=_("Mileage"))
receiving_date = models.DateTimeField(verbose_name=_("Receiving Date")) receiving_date = models.DateTimeField(verbose_name=_("Receiving Date"))
hash = models.CharField(max_length=64, blank=True, null=True, verbose_name=_("Hash"))
def save(self, *args, **kwargs):
self.hash = self.get_hash
super(Car, self).save(*args, **kwargs)
class Meta: class Meta:
verbose_name = _("Car") verbose_name = _("Car")
@ -424,6 +412,12 @@ class Car(models.Model):
def get_car_group(self): def get_car_group(self):
return f"{self.id_car_make.get_local_name} {self.id_car_model.get_local_name}" return f"{self.id_car_make.get_local_name} {self.id_car_model.get_local_name}"
@property
def get_hash(self):
hash_object = hashlib.sha256()
hash_object.update(f"{self.id_car_make.name}{self.id_car_model.name}".encode('utf-8'))
return hash_object.hexdigest()
def to_dict(self): def to_dict(self):
return { return {
"vin": self.vin, "vin": self.vin,
@ -437,6 +431,7 @@ class Car(models.Model):
"remarks": self.remarks, "remarks": self.remarks,
"mileage": self.mileage, "mileage": self.mileage,
"receiving_date": self.receiving_date.strftime('%Y-%m-%d %H:%M:%S'), "receiving_date": self.receiving_date.strftime('%Y-%m-%d %H:%M:%S'),
'hash': self.get_hash,
"id": self.id, "id": self.id,
} }
@ -1176,7 +1171,8 @@ class Lead(models.Model):
def full_name(self): def full_name(self):
return f"{self.first_name} {self.last_name}" return f"{self.first_name} {self.last_name}"
def convert_to_customer(self,entity): def convert_to_customer(self,entity):
if entity and not entity.get_customers().filter(email=self.email).exists(): customer = entity.get_customers().filter(email=self.email).first()
if entity and not customer:
customer = entity.create_customer( customer = entity.create_customer(
customer_model_kwargs={ customer_model_kwargs={
"customer_name": self.full_name, "customer_name": self.full_name,
@ -1185,11 +1181,12 @@ class Lead(models.Model):
"email": self.email, "email": self.email,
} }
) )
customer.additional_info = {}
customer.additional_info.update({"info":self.to_dict()}) customer.additional_info = {}
self.customer = customer customer.additional_info.update({"info":self.to_dict()})
customer.save() self.customer = customer
self.save() customer.save()
self.save()
def get_latest_schedule(self): def get_latest_schedule(self):
return self.schedules.order_by('-scheduled_at').first() return self.schedules.order_by('-scheduled_at').first()
@ -1219,6 +1216,7 @@ class Schedule(models.Model):
purpose = models.CharField(max_length=200, choices=PURPOSE_CHOICES) purpose = models.CharField(max_length=200, choices=PURPOSE_CHOICES)
scheduled_at = models.DateTimeField() scheduled_at = models.DateTimeField()
scheduled_type = models.CharField(max_length=200, choices=ScheduledType,default='Call') scheduled_type = models.CharField(max_length=200, choices=ScheduledType,default='Call')
duration = models.DurationField(default=timedelta(minutes=5))
notes = models.TextField(blank=True, null=True) notes = models.TextField(blank=True, null=True)
status = models.CharField(max_length=200, choices=ScheduleStatusChoices, default='Scheduled') status = models.CharField(max_length=200, choices=ScheduleStatusChoices, default='Scheduled')
created_at = models.DateTimeField(auto_now_add=True) created_at = models.DateTimeField(auto_now_add=True)

View File

@ -694,7 +694,8 @@ def update_item_model_cost(sender, instance, created, **kwargs):
product = entity.get_items_all().filter(name=instance.car.vin).first() product = entity.get_items_all().filter(name=instance.car.vin).first()
product.default_amount = instance.selling_price product.default_amount = instance.selling_price
product.additional_info = {} if not isinstance(product.additional_info, dict):
product.additional_info = {}
product.additional_info.update({"car_finance":instance.to_dict()}) product.additional_info.update({"car_finance":instance.to_dict()})
product.additional_info.update({"additional_services": [service.to_dict() for service in instance.additional_services.all()]}) product.additional_info.update({"additional_services": [service.to_dict() for service in instance.additional_services.all()]})
product.save() product.save()

View File

@ -1,4 +1,6 @@
from appointment.models import Appointment from django.db.models import Func
from appointment.models import Appointment,AppointmentRequest,Service,StaffMember
from datetime import timedelta
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
@ -116,6 +118,11 @@ logger = logging.getLogger(__name__)
logging.basicConfig(level=logging.INFO) logging.basicConfig(level=logging.INFO)
class Hash(Func):
function = 'get_hash'
def switch_language(request): def switch_language(request):
language = request.GET.get("language", "en") language = request.GET.get("language", "en")
referer = request.META.get("HTTP_REFERER", "/") referer = request.META.get("HTTP_REFERER", "/")
@ -2345,6 +2352,7 @@ def create_estimate(request):
{"status": "error", "message": "Items and Quantities are required"}, {"status": "error", "message": "Items and Quantities are required"},
status=400, status=400,
) )
if isinstance(quantities, list): if isinstance(quantities, list):
if "0" in quantities: if "0" in quantities:
return JsonResponse( return JsonResponse(
@ -2355,7 +2363,17 @@ def create_estimate(request):
return JsonResponse( return JsonResponse(
{"status": "error", "message": "Quantity must be greater than zero"} {"status": "error", "message": "Quantity must be greater than zero"}
) )
if isinstance(items, list):
for item, quantity in zip(items, quantities):
if int(quantity) > models.Car.objects.filter(hash=item).count():
return JsonResponse(
{"status": "error", "message": "Quantity must be less than or equal to the number of cars in stock"},
)
else:
if int(quantities) > models.Car.objects.filter(hash=item).count():
return JsonResponse(
{"status": "error", "message": "Quantity must be less than or equal to the number of cars in stock"},
)
estimate = entity.create_estimate( estimate = entity.create_estimate(
estimate_title=title, customer_model=customer, contract_terms=terms estimate_title=title, customer_model=customer, contract_terms=terms
) )
@ -2375,18 +2393,20 @@ def create_estimate(request):
] ]
items_txs = [] items_txs = []
for item in items_list: for item in items_list:
item_instance = ItemModel.objects.get(pk=item.get("item_id")) # item_instance = ItemModel.objects.get(pk=item.get("item_id"))
car_instance = models.Car.objects.get(vin=item_instance.name) car_instance = ItemModel.objects.filter(additional_info__car_info__hash=item.get("item_id")).all()
items_txs.append(
{ # car_instance = models.Car.objects.get(vin=item_instance.name)
"item_number": item_instance.item_number, for i in car_instance[:int(quantities[0])]:
"quantity": Decimal(item.get("quantity")), items_txs.append(
"unit_cost": car_instance.finances.selling_price, {
"unit_revenue": car_instance.finances.selling_price, "item_number": i.item_number,
"total_amount": (car_instance.finances.total_vat) "quantity": 1,
* int(item.get("quantity")), "unit_cost": i.additional_info.get('car_finance').get("selling_price"),
} "unit_revenue": i.additional_info.get('car_finance').get("selling_price"),
) "total_amount": (i.additional_info.get('car_finance').get("total_vat"))
}
)
estimate_itemtxs = { estimate_itemtxs = {
item.get("item_number"): { item.get("item_number"): {
@ -2416,14 +2436,18 @@ def create_estimate(request):
) )
if isinstance(items, list): if isinstance(items, list):
for item in items: for item in estimate_itemtxs.keys():
item_instance = ItemModel.objects.get(pk=item) item_instance = ItemModel.objects.get(item_number=item)
instance = models.Car.objects.get(vin=item_instance.name) instance = models.Car.objects.get(name=item_instance.name)
reserve_car(instance, request) reserve_car(instance, request)
# for item in items:
# item_instance = ItemModel.objects.filter(additioinal_info__car_info__hash=item).first()
# instance = models.Car.objects.get(hash=item)
# reserve_car(instance, request)
else: else:
item_instance = ItemModel.objects.get(pk=items) item_instance = ItemModel.objects.get(additioinal_info__car_info__hash=items)
instance = models.Car.objects.get(vin=item_instance.name) instance = models.Car.objects.get(hash=item)
response = reserve_car(instance, request) response = reserve_car(instance, request)
url = reverse("estimate_detail", kwargs={"pk": estimate.pk}) url = reverse("estimate_detail", kwargs={"pk": estimate.pk})
@ -2439,17 +2463,16 @@ def create_estimate(request):
entity_slug=entity.slug, user_model=entity.admin entity_slug=entity.slug, user_model=entity.admin
) )
form.fields["customer"].queryset = entity.get_customers().filter(active=True) form.fields["customer"].queryset = entity.get_customers().filter(active=True)
car_list = models.Car.objects.filter( car_list = models.Car.objects.filter(dealer=dealer).exclude(status="reserved").values_list(
dealer=dealer, finances__selling_price__gt=0 'id_car_make__name', 'id_car_model__name','hash').distinct()
).exclude(status="reserved")
context = { context = {
"form": form, "form": form,
"items": [ "items": [
{ {
"car": x, 'make':x[0],
"product": entity.get_items_all() 'model':x[1],
.filter(item_role=ItemModel.ITEM_ROLE_PRODUCT, name=x.vin) 'hash': x[2]
.first(),
} }
for x in car_list for x in car_list
], ],
@ -3031,12 +3054,14 @@ def lead_convert(request, pk):
if lead.is_converted: if lead.is_converted:
messages.error(request, "Lead is already converted to customer.") messages.error(request, "Lead is already converted to customer.")
return redirect("opportunity_create",pk=lead.pk) return redirect("opportunity_create",pk=lead.pk)
if not models.Car.objects.filter(id_car_make=lead.id_car_make,id_car_model=lead.id_car_model).first():
messages.error(request, "Cannot convert lead to customer. Car model not found.")
return redirect("lead_list")
lead.convert_to_customer(dealer.entity) lead.convert_to_customer(dealer.entity)
messages.success(request, "Lead converted to customer successfully!") messages.success(request, "Lead converted to customer successfully!")
return redirect("opportunity_create",pk=lead.pk) return redirect("opportunity_create",pk=lead.pk)
@login_required @login_required
def schedule_lead(request, pk): def schedule_lead(request, pk):
lead = get_object_or_404(models.Lead, pk=pk) lead = get_object_or_404(models.Lead, pk=pk)
@ -3047,8 +3072,33 @@ def schedule_lead(request, pk):
instance.lead = lead instance.lead = lead
if hasattr(request.user, "staff"): if hasattr(request.user, "staff"):
instance.scheduled_by = request.user.staff instance.scheduled_by = request.user.staff
# Save the Schedule instance
instance.save() instance.save()
messages.success(request, "Lead scheduled successfully!")
# Create AppointmentRequest
service = Service.objects.filter(name=instance.scheduled_type).first()
if not service:
messages.error(request, "Service not found!")
return redirect("lead_list")
appointment_request = AppointmentRequest.objects.create(
date=instance.scheduled_at.date(),
start_time=instance.scheduled_at.time(),
end_time=(instance.scheduled_at + instance.duration).time(),
service=service,
staff_member=StaffMember.objects.first()
)
# Create Appointment
Appointment.objects.create(
client=request.user, # Replace with the appropriate client
appointment_request=appointment_request,
phone="123-456-7890", # Replace with actual phone number
address="123 Main St", # Replace with actual address
)
messages.success(request, "Lead scheduled and appointment created successfully!")
return redirect("lead_list") return redirect("lead_list")
else: else:
messages.error(request, f"Invalid form data: {str(form.errors)}") messages.error(request, f"Invalid form data: {str(form.errors)}")
@ -3132,8 +3182,8 @@ class OpportunityCreateView(CreateView):
initial = super().get_initial() initial = super().get_initial()
if self.kwargs.get('pk',None): if self.kwargs.get('pk',None):
lead = models.Lead.objects.get(pk=self.kwargs.get('pk')) lead = models.Lead.objects.get(pk=self.kwargs.get('pk'))
initial['customer'] = lead.customer initial['customer'] = lead.customer
initial['car'] = lead.car
return initial return initial
def form_valid(self, form): def form_valid(self, form):

View File

@ -1,16 +1,17 @@
from django_ledger.models.invoice import InvoiceModel from django_ledger.models.invoice import InvoiceModel
from django_ledger.utils import accruable_net_summary from django_ledger.utils import accruable_net_summary
from decimal import Decimal from decimal import Decimal
from django_ledger.models import EstimateModel,EntityModel from django_ledger.models import EstimateModel,EntityModel,ItemModel
from rich import print from rich import print
from datetime import date from datetime import date
from inventory.models import VatRate,Lead,CarMake,CarModel,Schedule from inventory.models import Car, Dealer, VatRate,Lead,CarMake,CarModel,Schedule
from inventory.utils import CarFinanceCalculator from inventory.utils import CarFinanceCalculator
from appointment.models import Appointment,AppointmentRequest,Service,StaffMember from appointment.models import Appointment,AppointmentRequest,Service,StaffMember
from django.contrib.auth import get_user_model from django.contrib.auth import get_user_model
from django_ledger.io.io_core import get_localdate from django_ledger.io.io_core import get_localdate
from datetime import datetime, timedelta from datetime import datetime, timedelta
from django.utils import timezone
import hashlib
User = get_user_model() User = get_user_model()
@ -24,7 +25,7 @@ def run():
# service="Haircut", # service="Haircut",
# date_time="2023-10-15 10:00:00", # date_time="2023-10-15 10:00:00",
# status="pending") # status="pending")
make = CarMake.objects.first() # make = CarMake.objects.first()
# Lead.objects.create( # Lead.objects.create(
# first_name="John", # first_name="John",
# last_name="Doe", # last_name="Doe",
@ -53,19 +54,43 @@ def run():
# staff="John Doe", # staff="John Doe",
# priority="high", # priority="high",
# ) # )
service = Service.objects.first() # now = timezone.now()
appointment_request = AppointmentRequest.objects.create( # end_time = now + timedelta(minutes=10)
date=get_localdate(),
start_time=datetime.now().strftime("%H:%M:%S"), # service = Service.objects.first()
end_time=datetime.time(datetime.now() + timedelta(minutes=30)).strftime("%H:%M:%S"), # appointment_request = AppointmentRequest.objects.create(
service=service, # date=now.date(),
staff_member=StaffMember.objects.first(), # start_time=now.time(),
) # end_time=end_time.time(),
# service=service,
# staff_member=StaffMember.objects.first(),
# )
# appointment = Appointment.objects.create(
# client=User.objects.first(),
# appointment_request=appointment_request,
# phone="123-456-7890",
# address="123 Main St",
# )
dealer = Dealer.objects.get(user__email="ismail.mosa.ibrahim@gmail.com")
entity = dealer.entity
car_list = Car.objects.filter(dealer=dealer).all()
# context = {
# "items": [
# {
# "car": x,
# "product": entity.get_items_all()
# .filter(item_role=ItemModel.ITEM_ROLE_PRODUCT, name=x.vin)
# .first(),
# }
# for x in car_list
# ],
# }
for i in car_list:
hash_object = hashlib.sha256()
hash_object.update(f"{i.id_car_make.name}{i.id_car_model.name}".encode('utf-8'))
print(hash_object.hexdigest() , i.id_car_make.name, i.id_car_model.name)
appointment = Appointment.objects.create(
client=User.objects.first(),
appointment_request=appointment_request,
phone="123-456-7890",
address="123 Main St",
)

View File

@ -140,6 +140,7 @@
<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 %}
<a href="{% url 'appointment:get_user_appointments' %}">
{% 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 {% 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> <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>
@ -150,6 +151,7 @@
<span class="badge badge-phoenix badge-phoenix-warning text-warning fw-semibold"><span class="text-warning" data-feather="email"></span> <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> {{ lead.get_latest_schedule.scheduled_at }}</span>
{% endif %} {% endif %}
</a>
{% endif %} {% endif %}
</td> </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 text-body-tertiary text-opacity-85 fw-semibold text-body-highlight">{{ lead.staff|upper }}</td>

View File

@ -18,7 +18,7 @@
<div class="mb-2 col-sm-2"> <div class="mb-2 col-sm-2">
<select class="form-control item" name="item[]" required> <select class="form-control item" name="item[]" required>
{% for item in items %} {% for item in items %}
<option value="{{ item.product.pk }}">{{ item.car.id_car_model }}</option> <option value="{{ item.hash }}">{{ item.make }} {{item.model}}</option>
{% endfor %} {% endfor %}
</select> </select>
</div> </div>