diff --git a/car_inventory/urls.py b/car_inventory/urls.py index 956e9db9..55d3bc26 100644 --- a/car_inventory/urls.py +++ b/car_inventory/urls.py @@ -4,6 +4,8 @@ from django.conf.urls.static import static from django.conf import settings from django.conf.urls.i18n import i18n_patterns from inventory import views +from debug_toolbar.toolbar import debug_toolbar_urls + # import debug_toolbar from schema_graph.views import Schema @@ -15,7 +17,7 @@ urlpatterns = [ path("api-auth/", include("rest_framework.urls")), path("api/", include("api.urls")), # path('dj-rest-auth/', include('dj_rest_auth.urls')), -] +] + debug_toolbar_urls() urlpatterns += i18n_patterns( path("admin/", admin.site.urls), path("switch_language/", views.switch_language, name="switch_language"), @@ -32,3 +34,5 @@ urlpatterns += i18n_patterns( ) urlpatterns += static(settings.MEDIA_URL, document_root=settings.MEDIA_ROOT) + + diff --git a/inventory/models.py b/inventory/models.py index 5489f212..4fb138f2 100644 --- a/inventory/models.py +++ b/inventory/models.py @@ -43,6 +43,7 @@ from django.core.serializers.json import DjangoJSONEncoder from appointment.models import StaffMember from plans.quota import get_user_quota from plans.models import UserPlan +from django.db.models import Q # from plans.models import AbstractPlan # from simple_history.models import HistoricalRecords @@ -243,6 +244,11 @@ class CarMake(models.Model, LocalizedNameMixin): class Meta: verbose_name = _("Make") + indexes = [ + models.Index(fields=['name'], name='car_make_name_idx'), + models.Index(fields=['is_sa_import'], name='car_make_sa_import_idx'), + models.Index(fields=['car_type'], name='car_make_type_idx'), + ] class CarModel(models.Model, LocalizedNameMixin): @@ -272,6 +278,11 @@ class CarModel(models.Model, LocalizedNameMixin): class Meta: verbose_name = _("Model") + indexes = [ + models.Index(fields=['id_car_make'], name='car_model_make_idx'), + models.Index(fields=['name'], name='car_model_name_idx'), + models.Index(fields=['id_car_make', 'name'], name='car_model_make_name_idx'), + ] class CarSerie(models.Model, LocalizedNameMixin): @@ -306,6 +317,12 @@ class CarSerie(models.Model, LocalizedNameMixin): class Meta: verbose_name = _("Series") + indexes = [ + models.Index(fields=['id_car_model'], name='car_serie_model_idx'), + models.Index(fields=['year_begin', 'year_end'], name='car_serie_years_idx'), + models.Index(fields=['name'], name='car_serie_name_idx'), + models.Index(fields=['generation_name'], name='car_serie_generation_idx'), + ] class CarTrim(models.Model, LocalizedNameMixin): @@ -339,6 +356,11 @@ class CarTrim(models.Model, LocalizedNameMixin): class Meta: verbose_name = _("Trim") + indexes = [ + models.Index(fields=['id_car_serie'], name='car_trim_serie_idx'), + models.Index(fields=['start_production_year', 'end_production_year'], name='car_trim_prod_years_idx'), + models.Index(fields=['name'], name='car_trim_name_idx'), + ] class CarEquipment(models.Model, LocalizedNameMixin): @@ -359,6 +381,11 @@ class CarEquipment(models.Model, LocalizedNameMixin): class Meta: verbose_name = _("Equipment") + indexes = [ + models.Index(fields=['id_car_trim'], name='car_equipment_trim_idx'), + models.Index(fields=['year_begin'], name='car_equipment_year_idx'), + models.Index(fields=['name'], name='car_equipment_name_idx'), + ] class CarSpecification(models.Model, LocalizedNameMixin): @@ -390,6 +417,10 @@ class CarSpecification(models.Model, LocalizedNameMixin): class Meta: verbose_name = _("Specification") + indexes = [ + models.Index(fields=['id_parent'], name='car_spec_parent_idx'), + models.Index(fields=['name'], name='car_spec_name_idx'), + ] class CarSpecificationValue(models.Model): @@ -406,6 +437,11 @@ class CarSpecificationValue(models.Model): class Meta: verbose_name = _("Specification Value") + indexes = [ + models.Index(fields=['id_car_trim'], name='car_spec_val_trim_idx'), + models.Index(fields=['id_car_specification'], name='car_spec_val_spec_idx'), + models.Index(fields=['id_car_trim', 'id_car_specification'], name='car_spec_val_trim_spec_idx'), + ] class CarOption(models.Model, LocalizedNameMixin): @@ -437,6 +473,10 @@ class CarOption(models.Model, LocalizedNameMixin): class Meta: verbose_name = _("Option") + indexes = [ + models.Index(fields=['id_parent'], name='car_option_parent_idx'), + models.Index(fields=['name'], name='car_option_name_idx'), + ] class CarOptionValue(models.Model): @@ -456,6 +496,12 @@ class CarOptionValue(models.Model): class Meta: verbose_name = _("Option Value") + indexes = [ + models.Index(fields=['id_car_option'], name='car_opt_val_option_idx'), + models.Index(fields=['id_car_equipment'], name='car_opt_val_equipment_idx'), + models.Index(fields=['is_base'], name='car_opt_val_is_base_idx'), + models.Index(fields=['id_car_option', 'id_car_equipment'], name='cov_option_equipment_idx'), + ] class CarTransferStatusChoices(models.TextChoices): @@ -614,6 +660,26 @@ class Car(Base): class Meta: verbose_name = _("Car") verbose_name_plural = _("Cars") + indexes = [ + models.Index(fields=['vin'], name='car_vin_idx'), + models.Index(fields=['year'], name='car_year_idx'), + models.Index(fields=['status'], name='car_status_idx'), + + models.Index(fields=['dealer'], name='car_dealer_idx'), + models.Index(fields=['vendor'], name='car_vendor_idx'), + models.Index(fields=['id_car_make'], name='car_make_idx'), + models.Index(fields=['id_car_model'], name='car_model_idx'), + models.Index(fields=['id_car_serie'], name='car_serie_idx'), + models.Index(fields=['id_car_trim'], name='car_trim_idx'), + + models.Index(fields=['id_car_make', 'id_car_model'], name='car_make_model_idx'), + models.Index(fields=['id_car_make', 'year'], name='car_make_year_idx'), + models.Index(fields=['dealer', 'status'], name='car_dealer_status_idx'), + models.Index(fields=['vendor', 'status'], name='car_vendor_status_idx'), + models.Index(fields=['year', 'status'], name='car_year_status_idx'), + models.Index(fields=['status'], name='car_active_status_idx', + condition=Q(status=CarStatusChoices.AVAILABLE)), + ] def __str__(self): make = self.id_car_make.name if self.id_car_make else "Unknown Make" @@ -891,6 +957,12 @@ class CarFinance(models.Model): class Meta: verbose_name = _("Car Financial Details") verbose_name_plural = _("Car Financial Details") + indexes = [ + models.Index(fields=['car'], name='car_finance_car_idx'), + models.Index(fields=['cost_price'], name='car_finance_cost_price_idx'), + models.Index(fields=['selling_price'], name='car_finance_selling_price_idx'), + models.Index(fields=['discount_amount'], name='car_finance_discount_idx'), + ] class ExteriorColors(models.Model, LocalizedNameMixin): @@ -901,6 +973,10 @@ class ExteriorColors(models.Model, LocalizedNameMixin): class Meta: verbose_name = _("Exterior Colors") verbose_name_plural = _("Exterior Colors") + indexes = [ + models.Index(fields=['name'], name='exterior_color_name_idx'), + models.Index(fields=['arabic_name'], name='exterior_color_arabic_name_idx'), + ] def __str__(self): return f"{self.name} ({self.rgb})" @@ -914,6 +990,10 @@ class InteriorColors(models.Model, LocalizedNameMixin): class Meta: verbose_name = _("Interior Colors") verbose_name_plural = _("Interior Colors") + indexes = [ + models.Index(fields=['name'], name='interior_color_name_idx'), + models.Index(fields=['arabic_name'], name='interior_color_arabic_name_idx'), + ] def __str__(self): return f"{self.name} ({self.rgb})" @@ -932,6 +1012,11 @@ class CarColors(models.Model): verbose_name = _("Color") verbose_name_plural = _("Colors") unique_together = ("car", "exterior", "interior") + indexes = [ + models.Index(fields=['exterior'], name='car_colors_exterior_idx'), + models.Index(fields=['interior'], name='car_colors_interior_idx'), + models.Index(fields=['exterior', 'interior'], name='car_colors_ext_int_combo_idx'), + ] def __str__(self): return f"{self.car} ({self.exterior.name}) ({self.interior.name})" @@ -1108,6 +1193,7 @@ class Dealer(models.Model, LocalizedNameMixin): class Meta: verbose_name = _("Dealer") verbose_name_plural = _("Dealers") + indexes = [models.Index(fields=["name"])] # permissions = [ # ('change_dealer_type', 'Can change dealer type'), # ] @@ -1188,7 +1274,7 @@ class Staff(models.Model, LocalizedNameMixin): @property def groups(self): - return CustomGroup.objects.filter(pk__in=[x.customgroup.pk for x in self.user.groups.all()]) + return CustomGroup.objects.select_related("group").filter(pk__in=[x.customgroup.pk for x in self.user.groups.all()]) def clear_groups(self): self.remove_superuser_permission() @@ -1217,6 +1303,10 @@ class Staff(models.Model, LocalizedNameMixin): class Meta: verbose_name = _("Staff") verbose_name_plural = _("Staff") + indexes = [ + models.Index(fields=["name"]), + models.Index(fields=["staff_type"]), + ] permissions = [] def __str__(self): @@ -1372,6 +1462,13 @@ class Customer(models.Model): class Meta: verbose_name = _("Customer") verbose_name_plural = _("Customers") + indexes = [ + models.Index(fields=["title"]), + models.Index(fields=["first_name"]), + models.Index(fields=["last_name"]), + models.Index(fields=["email"]), + models.Index(fields=["phone_number"]), + ] def __str__(self): # middle = f" {self.middle_name}" if self.middle_name else "" @@ -1510,6 +1607,11 @@ class Organization(models.Model, LocalizedNameMixin): class Meta: verbose_name = _("Organization") verbose_name_plural = _("Organizations") + indexes = [ + models.Index(fields=["name"]), + models.Index(fields=["email"]), + models.Index(fields=["phone_number"]), + ] def __str__(self): return self.name @@ -1699,6 +1801,17 @@ class Lead(models.Model): class Meta: verbose_name = _("Lead") verbose_name_plural = _("Leads") + indexes = [ + models.Index(fields=["dealer"]), + models.Index(fields=["customer"]), + models.Index(fields=["organization"]), + models.Index(fields=["staff"]), + models.Index(fields=["first_name"]), + models.Index(fields=["last_name"]), + models.Index(fields=["email"]), + models.Index(fields=["phone_number"]), + models.Index(fields=["created"]), + ] def __str__(self): return f"{self.first_name} {self.last_name}" @@ -1873,6 +1986,14 @@ class Schedule(models.Model): class Meta: ordering = ["-scheduled_at"] + verbose_name = _("Schedule") + verbose_name_plural = _("Schedules") + indexes = [ + models.Index(fields=["dealer"]), + models.Index(fields=["customer"]), + models.Index(fields=["content_type", "object_id"]), + models.Index(fields=["scheduled_at"]), + ] class LeadStatusHistory(models.Model): @@ -2045,6 +2166,14 @@ class Opportunity(models.Model): class Meta: verbose_name = _("Opportunity") verbose_name_plural = _("Opportunities") + indexes = [ + models.Index(fields=["dealer"]), + models.Index(fields=["customer"]), + models.Index(fields=["car"]), + models.Index(fields=["lead"]), + models.Index(fields=["organization"]), + models.Index(fields=["created"]), + ] def __str__(self): if self.customer: @@ -2069,6 +2198,19 @@ class Notes(models.Model): class Meta: verbose_name = _("Note") verbose_name_plural = _("Notes") + indexes = [ + models.Index(fields=['dealer'], name='note_dealer_idx'), + models.Index(fields=['created_by'], name='note_created_by_idx'), + models.Index(fields=['content_type'], name='note_content_type_idx'), + models.Index(fields=['content_type', 'object_id'], name='note_content_object_idx'), + + models.Index(fields=['created'], name='note_created_date_idx'), + models.Index(fields=['updated'], name='note_updated_date_idx'), + + models.Index(fields=['dealer', 'created'], name='note_dealer_created_idx'), + models.Index(fields=['content_type', 'object_id', 'created'], + name='note_content_obj_created_idx'), + ] def __str__(self): return f"Note by {self.created_by.first_name} on {self.content_object}" @@ -2099,6 +2241,19 @@ class Tasks(models.Model): class Meta: verbose_name = _("Task") verbose_name_plural = _("Tasks") + indexes = [ + models.Index(fields=['dealer'], name='task_dealer_idx'), + models.Index(fields=['created_by'], name='task_created_by_idx'), + models.Index(fields=['content_type'], name='task_content_type_idx'), + models.Index(fields=['content_type', 'object_id'], name='task_content_object_idx'), + + models.Index(fields=['created'], name='task_created_date_idx'), + models.Index(fields=['updated'], name='task_updated_date_idx'), + + models.Index(fields=['dealer', 'created'], name='task_dealer_created_idx'), + models.Index(fields=['content_type', 'object_id', 'created'], + name='task_content_obj_created_idx'), + ] def __str__(self): return f"Task by {self.created_by.email} on {self.content_object}" @@ -2127,6 +2282,17 @@ class Email(models.Model): class Meta: verbose_name = _("Email") verbose_name_plural = _("Emails") + indexes = [ + models.Index(fields=['created_by'], name='email_created_by_idx'), + models.Index(fields=['content_type'], name='email_content_type_idx'), + models.Index(fields=['content_type', 'object_id'], name='email_content_object_idx'), + + models.Index(fields=['created'], name='email_created_date_idx'), + models.Index(fields=['updated'], name='email_updated_date_idx'), + + models.Index(fields=['content_type', 'object_id', 'created'], + name='email_content_obj_created_idx'), + ] def __str__(self): return f"Email by {self.created_by.first_name} on {self.content_object}" @@ -2152,6 +2318,17 @@ class Activity(models.Model): class Meta: verbose_name = _("Activity") verbose_name_plural = _("Activities") + indexes = [ + models.Index(fields=['created_by'], name='activity_created_by_idx'), + models.Index(fields=['content_type'], name='activity_content_type_idx'), + models.Index(fields=['content_type', 'object_id'], name='activity_content_object_idx'), + + models.Index(fields=['created'], name='activity_created_date_idx'), + models.Index(fields=['updated'], name='activity_updated_date_idx'), + + models.Index(fields=['content_type', 'object_id', 'created'], + name='a_content_obj_created_idx'), + ] def __str__(self): return f"{self.get_activity_type_display()} by {self.created_by.get_full_name} on {self.content_object}" @@ -2170,6 +2347,12 @@ class Notification(models.Model): verbose_name_plural = _("Notifications") ordering = ["-created"] + indexes = [ + models.Index(fields=['user'], name='notification_user_idx'), + models.Index(fields=['is_read'], name='notification_is_read_idx'), + models.Index(fields=['created'], name='notification_created_date_idx'), + ] + def __str__(self): return self.message @@ -2233,6 +2416,12 @@ class Vendor(models.Model, LocalizedNameMixin): class Meta: verbose_name = _("Vendor") verbose_name_plural = _("Vendors") + indexes = [ + models.Index(fields=['slug'], name='vendor_slug_idx'), + models.Index(fields=['active'], name='vendor_active_idx'), + models.Index(fields=['crn'], name='vendor_crn_idx'), + models.Index(fields=['vrn'], name='vendor_vrn_idx'), + ] def __str__(self): return self.name @@ -2474,9 +2663,21 @@ class SaleOrder(models.Model): ) class Meta: - verbose_name = "Sales Order" - verbose_name_plural = "Sales Orders" + verbose_name = _("Sales Order") + verbose_name_plural = _("Sales Orders") ordering = ["-order_date"] # Order by most recent first + indexes = [ + models.Index(fields=["dealer"]), + models.Index(fields=["estimate"]), + models.Index(fields=["invoice"]), + models.Index(fields=["opportunity"]), + models.Index(fields=["customer"]), + models.Index(fields=["status"]), + models.Index(fields=["order_date"]), + models.Index(fields=["expected_delivery_date"]), + models.Index(fields=["actual_delivery_date"]), + models.Index(fields=["cancelled_date"]), + ] def save(self, *args, **kwargs): if not self.formatted_order_id: @@ -2534,6 +2735,14 @@ class CustomGroup(models.Model): group = models.OneToOneField( "auth.Group", verbose_name=_("Group"), on_delete=models.CASCADE ) + class Meta: + verbose_name = _("Custom Group") + verbose_name_plural = _("Custom Groups") + indexes = [ + models.Index(fields=["name"]), + models.Index(fields=["dealer"]), + models.Index(fields=["group"]), + ] @property def entity(self): @@ -2669,7 +2878,7 @@ class CustomGroup(models.Model): app="inventory", allowed_models=[ "saleorder", - "payment", + # "payment", "staff", "schedule", "activity", @@ -2938,6 +3147,13 @@ class PoItemsUploaded(models.Model): created_at = models.DateTimeField(auto_now_add=True) updated_at = models.DateTimeField(auto_now=True) + class Meta: + verbose_name = _("PO Items") + verbose_name_plural = _("PO Items") + indexes = [ + models.Index(fields=["po"]), + models.Index(fields=["item"]), + ] def get_name(self): return self.item.item.name.split('||') class ExtraInfo(models.Model): @@ -2989,7 +3205,9 @@ class ExtraInfo(models.Model): models.Index(fields=['content_type', 'object_id']), models.Index(fields=['related_content_type', 'related_object_id']), ] - verbose_name_plural = "Extra Info" + verbose_name_plural = _("Extra Info") + verbose_name = _("Extra Info") + def __str__(self): return f"ExtraInfo for {self.content_object} ({self.content_type})" diff --git a/inventory/override.py b/inventory/override.py index 4e87d83c..a03dd08e 100644 --- a/inventory/override.py +++ b/inventory/override.py @@ -316,12 +316,18 @@ class BasePurchaseOrderActionActionView(LoginRequiredMixin, ) except ValidationError as e: # --- Single-line log for ValidationError --- + print(f"User {user_username} encountered a validation error " + f"while performing action '{self.action_name}' on Purchase Order ID: {po_model.pk}. " + f"Error: {e}") logger.warning( f"User {user_username} encountered a validation error " f"while performing action '{self.action_name}' on Purchase Order ID: {po_model.pk}. " f"Error: {e}" ) except AttributeError as e: + print(f"User {user_username} encountered an AttributeError " + f"while performing action '{self.action_name}' on Purchase Order ID: {po_model.pk}. " + f"Error: {e}") logger.warning( f"User {user_username} encountered an AttributeError " f"while performing action '{self.action_name}' on Purchase Order ID: {po_model.pk}. " @@ -421,11 +427,16 @@ class BillModelUpdateView(LoginRequiredMixin, PermissionRequiredMixin, UpdateVie user_model=dealer.entity.admin, instance=self.object ) - return form_class( + form = form_class( entity_model=entity_model, user_model=dealer.entity.admin, **self.get_form_kwargs() ) + try: + form.initial['amount_paid'] = self.object.get_itemtxs_data()[1]["total_amount__sum"] + except Exception as e: + print(e) + return form def get_form_class(self): bill_model: BillModel = self.object diff --git a/inventory/signals.py b/inventory/signals.py index ba90031c..9e6c250b 100644 --- a/inventory/signals.py +++ b/inventory/signals.py @@ -1,10 +1,10 @@ from decimal import Decimal - from django.urls import reverse from inventory.tasks import create_coa_accounts, create_make_accounts from django.contrib.auth.models import Group from django.db.models.signals import post_save, post_delete from django.dispatch import receiver +from appointment.models import Service from django.utils.translation import gettext_lazy as _ from django.contrib.contenttypes.models import ContentType from django.contrib.auth import get_user_model @@ -941,6 +941,11 @@ def create_po_item_upload(sender,instance,created,**kwargs): dealer = models.Dealer.objects.get(entity=instance.entity) models.PoItemsUploaded.objects.create(dealer=dealer,po=instance, item=item, status="fulfilled") +@receiver(post_save, sender=models.Staff) +def add_service_to_staff(sender,instance,created,**kwargs): + if created: + for service in Service.objects.all(): + instance.staff_member.services_offered.add(service) ########################################################## ######################Notification######################## diff --git a/inventory/utils.py b/inventory/utils.py index b2130b04..41903eb2 100644 --- a/inventory/utils.py +++ b/inventory/utils.py @@ -473,7 +473,7 @@ def set_invoice_payment(dealer, entity, invoice, amount, payment_method): calculator = CarFinanceCalculator(invoice) finance_data = calculator.get_finance_data() - # handle_account_process(invoice, amount, finance_data) + handle_account_process(invoice, amount, finance_data) invoice.make_payment(amount) invoice.save() diff --git a/inventory/views.py b/inventory/views.py index 842b665e..427c366d 100644 --- a/inventory/views.py +++ b/inventory/views.py @@ -206,7 +206,7 @@ from .tasks import create_accounts_for_make, create_user_dealer, send_email # djago easy audit log from easyaudit.models import RequestEvent, CRUDEvent, LoginEvent from django_q.tasks import async_task - +from django.db.models import Prefetch logger = logging.getLogger(__name__) logging.basicConfig(level=logging.INFO) @@ -1150,7 +1150,7 @@ class CarListView(LoginRequiredMixin, PermissionRequiredMixin, ListView): def get_queryset(self): dealer = get_user_type(self.request) - qs = super().get_queryset() + qs = super().get_queryset().prefetch_related("id_car_make", "id_car_model", "id_car_trim","finances","colors") qs = qs.filter(dealer=dealer) status = self.request.GET.get("status") search = self.request.GET.get("search") @@ -1202,8 +1202,193 @@ def inventory_stats_view(request, dealer_slug): "inventory/inventory_stats.html" template. :rtype: HttpResponse """ + # cars = ( + # models.Car.objects + # .filter(dealer=request.dealer) + # .select_related( + # "id_car_make", + # "id_car_model", + # "id_car_trim__id_car_serie__id_car_model__id_car_make" + # ) + # .only( + # "id", + # "id_car_make__id_car_make", + # "id_car_make__slug", + # "id_car_make__name", + # "id_car_make__arabic_name", + # "id_car_model__id_car_model", + # "id_car_model__slug", + # "id_car_model__name", + # "id_car_model__arabic_name", + # "id_car_trim__id_car_trim", + # "id_car_trim__slug", + # "id_car_trim__name", + # "id_car_trim__id_car_serie__id_car_model__id_car_make", + # "id_car_trim__id_car_serie__id_car_model__id_car_model", + # ) + # .order_by('id_car_make', 'id_car_model', 'id_car_trim') + # ) - # Base queryset for cars belonging to the dealer + # # Get counts in optimized queries + # total_cars = cars.count() + # reserved_cars = models.CarReservation.objects.filter( + # car__dealer=request.dealer + # ).count() + + # # Prefetch related data if needed for additional fields + # cars = cars.prefetch_related( + # Prefetch('colors', queryset=models.CarColors.objects.select_related('exterior', 'interior')) + # ) + + # # Get distinct makes, models, trims in database-compatible way + # makes = ( + # cars.order_by('id_car_make') + # .values_list('id_car_make', flat=True) + # .distinct() + # ) + + # _models = ( + # cars.order_by('id_car_model') + # .values_list('id_car_model', flat=True) + # .distinct() + # ) + + # trims = ( + # cars.order_by('id_car_trim') + # .values_list('id_car_trim', flat=True) + # .distinct() + # ) + + # # Get counts by make/model/trim + # make_counts = dict( + # cars.values_list('id_car_make') + # .annotate(count=Count('id')) + # .order_by('id_car_make') + # ) + + # model_counts = dict( + # cars.values_list('id_car_model') + # .annotate(count=Count('id')) + # .order_by('id_car_model') + # ) + + # trim_counts = dict( + # cars.values_list('id_car_trim') + # .annotate(count=Count('id')) + # .order_by('id_car_trim') + # ) + + # # Build inventory structure + # inventory = {} + + # # Process makes + # make_objects = { + # m.id_car_make: m for m in + # models.CarMake.objects.filter(id_car_make__in=makes) + # .only('id_car_make', 'slug', 'name', 'arabic_name') + # } + + # for make_id in makes: + # if not make_id: + # continue + + # make_obj = make_objects.get(make_id) + # if not make_obj: + # continue + + # inventory[make_id] = { + # "make_id": make_id, + # "slug": make_obj.slug, + # "make_name": make_obj.get_local_name(), + # "total_cars": make_counts.get(make_id, 0), + # "models": {}, + # } + + # # Process models + # model_objects = { + # m.id_car_model: m for m in + # models.CarModel.objects.filter(id_car_model__in=_models) + # .select_related('id_car_make') + # .only('id_car_model', 'slug', 'name', 'arabic_name', 'id_car_make') + # } + + # for model_id in _models: + # if not model_id: + # continue + + # model_obj = model_objects.get(model_id) + # if not model_obj: + # continue + + # make_id = model_obj.id_car_make.id_car_make + # if make_id not in inventory: + # continue + + # inventory[make_id]["models"][model_id] = { + # "model_id": model_id, + # "slug": model_obj.slug, + # "model_name": model_obj.get_local_name(), + # "total_cars": model_counts.get(model_id, 0), + # "trims": {}, + # } + + # # Process trims + # trim_objects = { + # t.id_car_trim: t for t in + # models.CarTrim.objects.filter(id_car_trim__in=trims) + # .select_related('id_car_serie__id_car_model__id_car_make') + # .only('id_car_trim', 'slug', 'name', 'id_car_serie__id_car_model__id_car_make') + # } + + # for trim_id in trims: + # if not trim_id: + # continue + + # trim_obj = trim_objects.get(trim_id) + # if not trim_obj: + # continue + + # make_id = trim_obj.id_car_serie.id_car_model.id_car_make.id_car_make + # model_id = trim_obj.id_car_serie.id_car_model.id_car_model + + # if make_id not in inventory or model_id not in inventory[make_id]["models"]: + # continue + + # inventory[make_id]["models"][model_id]["trims"][trim_id] = { + # "trim_id": trim_id, + # "slug": trim_obj.slug, + # "trim_name": trim_obj.name, + # "total_cars": trim_counts.get(trim_id, 0), + # } + + # # Convert to final structure + # result = { + # "total_cars": total_cars, + # "reserved_cars": reserved_cars, + # "makes": [ + # { + # "make_id": make_data["make_id"], + # "slug": make_data["slug"], + # "make_name": make_data["make_name"], + # "total_cars": make_data["total_cars"], + # "models": [ + # { + # "model_id": model_data["model_id"], + # "slug": model_data["slug"], + # "model_name": model_data["model_name"], + # "total_cars": model_data["total_cars"], + # "trims": list(model_data["trims"].values()), + # } + # for model_data in make_data["models"].values() + # if model_data["model_id"] # Skip empty models + # ], + # } + # for make_data in inventory.values() + # if make_data["make_id"] # Skip empty makes + # ], + # } + ############################################### + # # Base queryset for cars belonging to the dealer cars = models.Car.objects.filter(dealer=request.dealer) # Count for total, reserved, showroom, and unreserved cars @@ -1443,6 +1628,18 @@ class CarDetailView(LoginRequiredMixin, PermissionRequiredMixin, DetailView): context_object_name = "car" permission_required = ["inventory.view_car"] + def get_queryset(self): + qs = super().get_queryset() + qs = qs.select_related( + "id_car_make", + "id_car_model", + "id_car_trim", + "colors", + "finances", + "vendor", + "registrations" + ) + return qs class CarFinanceCreateView(LoginRequiredMixin, PermissionRequiredMixin, CreateView): """ @@ -3877,7 +4074,7 @@ class BankAccountDetailView(LoginRequiredMixin, PermissionRequiredMixin, DetailV template_name = "ledger/bank_accounts/bank_account_detail.html" context_object_name = "bank_account" permission_required = ["django_ledger.view_bankaccountmodel"] - + def get_queryset(self): dealer = get_object_or_404(models.Dealer, slug=self.kwargs["dealer_slug"]) query=self.request.GET.get('q') @@ -5602,25 +5799,25 @@ class LeadListView(LoginRequiredMixin, PermissionRequiredMixin, ListView): def get_queryset(self): dealer = get_object_or_404(models.Dealer, slug=self.kwargs["dealer_slug"]) query = self.request.GET.get("q") - qs = models.Lead.objects.filter(dealer=dealer).exclude(status="converted") + qs = models.Lead.objects.select_related("staff","id_car_make","id_car_model","customer").filter(dealer=dealer).exclude(status="converted") + if query: qs = qs.filter(Q(first_name__icontains=query) - | Q(last_name__icontains=query) - | Q(id_car_make__name__icontains=query) - | Q(id_car_model__name__icontains=query) - | Q(email__icontains=query) - | Q(phone_number__icontains=query) - | Q(next_action__icontains=query) - | Q(staff__name__icontains=query)) - + | Q(last_name__icontains=query) + | Q(id_car_make__name__icontains=query) + | Q(id_car_model__name__icontains=query) + | Q(email__icontains=query) + | Q(phone_number__icontains=query) + | Q(next_action__icontains=query) + | Q(staff__name__icontains=query)) + if self.request.is_dealer: return qs if self.request.user.is_staff: staff = getattr(self.request.user.staffmember, "staff", None) return qs.filter(staff=staff) return models.Lead.objects.none() - - + class LeadDetailView(LoginRequiredMixin, PermissionRequiredMixin, DetailView): """ @@ -6689,9 +6886,9 @@ class OpportunityListView(LoginRequiredMixin,PermissionRequiredMixin, ListView): Q(customer__first_name__icontains=search) | Q(customer__last_name__icontains=search) | Q(customer__email__icontains=search) - + ) - + # Stage filter stage = self.request.GET.get("stage") @@ -7110,7 +7307,7 @@ class ItemExpenseListView(LoginRequiredMixin, PermissionRequiredMixin, ListView) # def get_queryset(self): # dealer = get_user_type(self.request) # return dealer.entity.get_items_expenses() - + def get_queryset(self): dealer = get_user_type(self.request) query=self.request.GET.get('q') @@ -9986,9 +10183,9 @@ class PurchaseOrderListView(LoginRequiredMixin, PermissionRequiredMixin, ListVie if query: qs=apply_search_filters(qs,query) return qs - - - + + + # def get_queryset(self): # dealer = get_user_type(self.request) # entity = dealer.entity @@ -10122,11 +10319,28 @@ class BillModelActionMarkAsInReviewView(BaseBillActionView): class BillModelActionMarkAsApprovedView(BaseBillActionView): action_name = "mark_as_approved" + def get_redirect_url(self, dealer_slug, entity_slug, bill_pk, *args, **kwargs): + if self.request.is_manager: + messages.add_message( + self.request, + message="Bill updated successfully.", + level=messages.SUCCESS, + ) + return reverse("home",kwargs={"dealer_slug": dealer_slug}) + + return reverse( + "bill-update", + kwargs={ + "dealer_slug": dealer_slug, + "entity_slug": entity_slug, + "bill_pk": bill_pk, + }, + ) + class BillModelActionMarkAsPaidView(BaseBillActionView): action_name = "mark_as_paid" - class BillModelActionDeleteView(BaseBillActionView): action_name = "mark_as_delete" diff --git a/static/css/custom.css b/static/css/custom.css index fd4b7369..b61b11af 100644 --- a/static/css/custom.css +++ b/static/css/custom.css @@ -130,4 +130,5 @@ html[dir="rtl"] .form-icon-container .form-control { @keyframes spin { to { transform: rotate(360deg); } -} \ No newline at end of file +} + diff --git a/templates/bill/bill_update.html b/templates/bill/bill_update.html index 0da68007..508b99eb 100644 --- a/templates/bill/bill_update.html +++ b/templates/bill/bill_update.html @@ -47,7 +47,6 @@ -