update
This commit is contained in:
parent
ffd72e2d93
commit
f4111803bb
@ -686,7 +686,7 @@ 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']
|
||||
fields = ['purpose','scheduled_type', 'scheduled_at','duration', 'notes']
|
||||
|
||||
|
||||
class NoteForm(forms.ModelForm):
|
||||
|
||||
14
inventory/migrations/0015_merge_20250209_1116.py
Normal file
14
inventory/migrations/0015_merge_20250209_1116.py
Normal 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 = [
|
||||
]
|
||||
19
inventory/migrations/0016_schedule_duration.py
Normal file
19
inventory/migrations/0016_schedule_duration.py
Normal 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)),
|
||||
),
|
||||
]
|
||||
18
inventory/migrations/0017_car_hash.py
Normal file
18
inventory/migrations/0017_car_hash.py
Normal 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'),
|
||||
),
|
||||
]
|
||||
@ -1,38 +1,21 @@
|
||||
# from datetime import timezone
|
||||
from django.utils import timezone
|
||||
import itertools
|
||||
from uuid import uuid4
|
||||
from django.conf import settings
|
||||
from django.db import models, transaction
|
||||
from django.db.models import Sum, F, Count
|
||||
from decimal import Decimal
|
||||
import hashlib
|
||||
from django.db import models
|
||||
from datetime import timedelta
|
||||
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_ledger.models import (
|
||||
VendorModel,
|
||||
EntityModel,
|
||||
EntityUnitModel,
|
||||
ItemModel,
|
||||
AccountModel,
|
||||
ItemModelAbstract,
|
||||
UnitOfMeasureModel,
|
||||
CustomerModel,
|
||||
ItemModelQuerySet,
|
||||
)
|
||||
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 phonenumber_field.modelfields import PhoneNumberField
|
||||
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 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.models import ContentType
|
||||
|
||||
@ -403,6 +386,11 @@ class Car(models.Model):
|
||||
remarks = models.TextField(blank=True, null=True, verbose_name=_("Remarks"))
|
||||
mileage = models.IntegerField(blank=True, null=True, verbose_name=_("Mileage"))
|
||||
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:
|
||||
verbose_name = _("Car")
|
||||
@ -424,6 +412,12 @@ class Car(models.Model):
|
||||
def get_car_group(self):
|
||||
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):
|
||||
return {
|
||||
"vin": self.vin,
|
||||
@ -437,6 +431,7 @@ class Car(models.Model):
|
||||
"remarks": self.remarks,
|
||||
"mileage": self.mileage,
|
||||
"receiving_date": self.receiving_date.strftime('%Y-%m-%d %H:%M:%S'),
|
||||
'hash': self.get_hash,
|
||||
"id": self.id,
|
||||
}
|
||||
|
||||
@ -1176,7 +1171,8 @@ class Lead(models.Model):
|
||||
def full_name(self):
|
||||
return f"{self.first_name} {self.last_name}"
|
||||
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_model_kwargs={
|
||||
"customer_name": self.full_name,
|
||||
@ -1184,12 +1180,13 @@ class Lead(models.Model):
|
||||
"phone": self.phone_number,
|
||||
"email": self.email,
|
||||
}
|
||||
)
|
||||
customer.additional_info = {}
|
||||
customer.additional_info.update({"info":self.to_dict()})
|
||||
self.customer = customer
|
||||
customer.save()
|
||||
self.save()
|
||||
)
|
||||
|
||||
customer.additional_info = {}
|
||||
customer.additional_info.update({"info":self.to_dict()})
|
||||
self.customer = customer
|
||||
customer.save()
|
||||
self.save()
|
||||
|
||||
def get_latest_schedule(self):
|
||||
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)
|
||||
scheduled_at = models.DateTimeField()
|
||||
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)
|
||||
status = models.CharField(max_length=200, choices=ScheduleStatusChoices, default='Scheduled')
|
||||
created_at = models.DateTimeField(auto_now_add=True)
|
||||
|
||||
@ -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.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({"additional_services": [service.to_dict() for service in instance.additional_services.all()]})
|
||||
product.save()
|
||||
|
||||
@ -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 random import randint
|
||||
from rich import print
|
||||
@ -116,6 +118,11 @@ logger = logging.getLogger(__name__)
|
||||
logging.basicConfig(level=logging.INFO)
|
||||
|
||||
|
||||
|
||||
class Hash(Func):
|
||||
function = 'get_hash'
|
||||
|
||||
|
||||
def switch_language(request):
|
||||
language = request.GET.get("language", "en")
|
||||
referer = request.META.get("HTTP_REFERER", "/")
|
||||
@ -2339,12 +2346,13 @@ def create_estimate(request):
|
||||
|
||||
items = data.get("item", [])
|
||||
quantities = data.get("quantity", [])
|
||||
|
||||
|
||||
if not all([items, quantities]):
|
||||
return JsonResponse(
|
||||
{"status": "error", "message": "Items and Quantities are required"},
|
||||
status=400,
|
||||
)
|
||||
)
|
||||
|
||||
if isinstance(quantities, list):
|
||||
if "0" in quantities:
|
||||
return JsonResponse(
|
||||
@ -2355,7 +2363,17 @@ def create_estimate(request):
|
||||
return JsonResponse(
|
||||
{"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_title=title, customer_model=customer, contract_terms=terms
|
||||
)
|
||||
@ -2375,18 +2393,20 @@ def create_estimate(request):
|
||||
]
|
||||
items_txs = []
|
||||
for item in items_list:
|
||||
item_instance = ItemModel.objects.get(pk=item.get("item_id"))
|
||||
car_instance = models.Car.objects.get(vin=item_instance.name)
|
||||
items_txs.append(
|
||||
{
|
||||
"item_number": item_instance.item_number,
|
||||
"quantity": Decimal(item.get("quantity")),
|
||||
"unit_cost": car_instance.finances.selling_price,
|
||||
"unit_revenue": car_instance.finances.selling_price,
|
||||
"total_amount": (car_instance.finances.total_vat)
|
||||
* int(item.get("quantity")),
|
||||
}
|
||||
)
|
||||
# item_instance = ItemModel.objects.get(pk=item.get("item_id"))
|
||||
car_instance = ItemModel.objects.filter(additional_info__car_info__hash=item.get("item_id")).all()
|
||||
|
||||
# car_instance = models.Car.objects.get(vin=item_instance.name)
|
||||
for i in car_instance[:int(quantities[0])]:
|
||||
items_txs.append(
|
||||
{
|
||||
"item_number": i.item_number,
|
||||
"quantity": 1,
|
||||
"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 = {
|
||||
item.get("item_number"): {
|
||||
@ -2416,14 +2436,18 @@ def create_estimate(request):
|
||||
)
|
||||
|
||||
if isinstance(items, list):
|
||||
for item in items:
|
||||
item_instance = ItemModel.objects.get(pk=item)
|
||||
instance = models.Car.objects.get(vin=item_instance.name)
|
||||
for item in estimate_itemtxs.keys():
|
||||
item_instance = ItemModel.objects.get(item_number=item)
|
||||
instance = models.Car.objects.get(name=item_instance.name)
|
||||
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:
|
||||
item_instance = ItemModel.objects.get(pk=items)
|
||||
instance = models.Car.objects.get(vin=item_instance.name)
|
||||
item_instance = ItemModel.objects.get(additioinal_info__car_info__hash=items)
|
||||
instance = models.Car.objects.get(hash=item)
|
||||
response = reserve_car(instance, request)
|
||||
|
||||
url = reverse("estimate_detail", kwargs={"pk": estimate.pk})
|
||||
@ -2439,22 +2463,21 @@ def create_estimate(request):
|
||||
entity_slug=entity.slug, user_model=entity.admin
|
||||
)
|
||||
form.fields["customer"].queryset = entity.get_customers().filter(active=True)
|
||||
car_list = models.Car.objects.filter(
|
||||
dealer=dealer, finances__selling_price__gt=0
|
||||
).exclude(status="reserved")
|
||||
car_list = models.Car.objects.filter(dealer=dealer).exclude(status="reserved").values_list(
|
||||
'id_car_make__name', 'id_car_model__name','hash').distinct()
|
||||
|
||||
context = {
|
||||
"form": form,
|
||||
"items": [
|
||||
{
|
||||
"car": x,
|
||||
"product": entity.get_items_all()
|
||||
.filter(item_role=ItemModel.ITEM_ROLE_PRODUCT, name=x.vin)
|
||||
.first(),
|
||||
'make':x[0],
|
||||
'model':x[1],
|
||||
'hash': x[2]
|
||||
}
|
||||
for x in car_list
|
||||
],
|
||||
}
|
||||
|
||||
|
||||
return render(request, "sales/estimates/estimate_form.html", context)
|
||||
|
||||
|
||||
@ -3031,12 +3054,14 @@ def lead_convert(request, pk):
|
||||
if lead.is_converted:
|
||||
messages.error(request, "Lead is already converted to customer.")
|
||||
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)
|
||||
messages.success(request, "Lead converted to customer successfully!")
|
||||
return redirect("opportunity_create",pk=lead.pk)
|
||||
|
||||
|
||||
@login_required
|
||||
def schedule_lead(request, pk):
|
||||
lead = get_object_or_404(models.Lead, pk=pk)
|
||||
@ -3047,8 +3072,33 @@ def schedule_lead(request, pk):
|
||||
instance.lead = lead
|
||||
if hasattr(request.user, "staff"):
|
||||
instance.scheduled_by = request.user.staff
|
||||
|
||||
# Save the Schedule instance
|
||||
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")
|
||||
else:
|
||||
messages.error(request, f"Invalid form data: {str(form.errors)}")
|
||||
@ -3131,9 +3181,9 @@ class OpportunityCreateView(CreateView):
|
||||
def get_initial(self):
|
||||
initial = super().get_initial()
|
||||
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['car'] = lead.car
|
||||
return initial
|
||||
|
||||
def form_valid(self, form):
|
||||
|
||||
@ -1,16 +1,17 @@
|
||||
from django_ledger.models.invoice import InvoiceModel
|
||||
from django_ledger.utils import accruable_net_summary
|
||||
from decimal import Decimal
|
||||
from django_ledger.models import EstimateModel,EntityModel
|
||||
from django_ledger.models import EstimateModel,EntityModel,ItemModel
|
||||
from rich import print
|
||||
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 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
|
||||
|
||||
from django.utils import timezone
|
||||
import hashlib
|
||||
|
||||
User = get_user_model()
|
||||
|
||||
@ -24,7 +25,7 @@ def run():
|
||||
# service="Haircut",
|
||||
# date_time="2023-10-15 10:00:00",
|
||||
# status="pending")
|
||||
make = CarMake.objects.first()
|
||||
# make = CarMake.objects.first()
|
||||
# Lead.objects.create(
|
||||
# first_name="John",
|
||||
# last_name="Doe",
|
||||
@ -53,19 +54,43 @@ def run():
|
||||
# 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(),
|
||||
)
|
||||
# now = timezone.now()
|
||||
# end_time = now + timedelta(minutes=10)
|
||||
|
||||
appointment = Appointment.objects.create(
|
||||
client=User.objects.first(),
|
||||
appointment_request=appointment_request,
|
||||
phone="123-456-7890",
|
||||
address="123 Main St",
|
||||
)
|
||||
|
||||
# service = Service.objects.first()
|
||||
# appointment_request = AppointmentRequest.objects.create(
|
||||
# date=now.date(),
|
||||
# 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)
|
||||
|
||||
|
||||
@ -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">
|
||||
{% if lead.get_latest_schedule %}
|
||||
<a href="{% url 'appointment:get_user_appointments' %}">
|
||||
{% 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>
|
||||
{{ 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>
|
||||
{{ lead.get_latest_schedule.scheduled_at }}</span>
|
||||
{% endif %}
|
||||
</a>
|
||||
{% endif %}
|
||||
</td>
|
||||
<td class="align-middle white-space-nowrap text-body-tertiary text-opacity-85 fw-semibold text-body-highlight">{{ lead.staff|upper }}</td>
|
||||
|
||||
@ -18,7 +18,7 @@
|
||||
<div class="mb-2 col-sm-2">
|
||||
<select class="form-control item" name="item[]" required>
|
||||
{% 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 %}
|
||||
</select>
|
||||
</div>
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user