From 543d8702f3e0f8442d510b1b75263959a45f57ae Mon Sep 17 00:00:00 2001 From: ismail Date: Tue, 12 Aug 2025 12:02:21 +0300 Subject: [PATCH] update the car calculator class --- inventory/admin.py | 2 +- inventory/forms.py | 4 +- inventory/models.py | 260 ++++++---- inventory/signals.py | 443 +++++++++--------- inventory/urls.py | 13 +- inventory/utils.py | 415 +++++++++++----- inventory/views.py | 270 ++++------- templates/chart_of_accounts/coa_list.html | 38 ++ .../chart_of_accounts/includes/coa_card.html | 116 +++++ .../crm/opportunities/opportunity_detail.html | 4 +- .../opportunities/opportunity_list copy.html | 4 +- templates/header.html | 32 +- templates/inventory/car_detail.html | 16 +- templates/inventory/transfer_details.html | 4 +- templates/inventory/transfer_preview.html | 6 +- templates/ledger/reports/car_sale_report.html | 10 +- .../sales/estimates/estimate_detail.html | 31 +- .../estimates/payment_request_detail.html | 2 +- templates/sales/invoices/invoice_detail.html | 28 +- 19 files changed, 1007 insertions(+), 691 deletions(-) create mode 100644 templates/chart_of_accounts/coa_list.html create mode 100644 templates/chart_of_accounts/includes/coa_card.html diff --git a/inventory/admin.py b/inventory/admin.py index 87c571a3..1bd2956f 100644 --- a/inventory/admin.py +++ b/inventory/admin.py @@ -38,7 +38,7 @@ admin.site.register(models.DealerSettings) # admin.site.register(models.SaleQuotationCar) admin.site.register(models.SaleOrder) admin.site.register(models.CustomGroup) -admin.site.register(models.CarFinance) +# admin.site.register(models.CarFinance) admin.site.register(models.CarColors) admin.site.register(models.CarRegistration) admin.site.register(models.CustomCard) diff --git a/inventory/forms.py b/inventory/forms.py index 4bdd23c6..b01426ea 100644 --- a/inventory/forms.py +++ b/inventory/forms.py @@ -32,7 +32,7 @@ from .models import ( Car, VatRate, CarTransfer, - CarFinance, + # CarFinance, CustomCard, CarRegistration, CarColors, @@ -444,7 +444,7 @@ class CarFinanceForm(forms.ModelForm): return cleaned_data class Meta: - model = CarFinance + model = Car fields = ["cost_price","marked_price"] diff --git a/inventory/models.py b/inventory/models.py index 53dee7df..ab589f82 100644 --- a/inventory/models.py +++ b/inventory/models.py @@ -603,7 +603,6 @@ class AdditionalServices(models.Model, LocalizedNameMixin): class Car(Base): - item_model = models.OneToOneField( ItemModel, models.DO_NOTHING, @@ -668,6 +667,32 @@ class Car(Base): default=CarStockTypeChoices.NEW, verbose_name=_("Stock Type"), ) + # + additional_services = models.ManyToManyField( + AdditionalServices, related_name="additionals", blank=True,null=True + ) + cost_price = models.DecimalField( + max_digits=14, decimal_places=2, verbose_name=_("Cost Price"),default=Decimal("0.00") + ) + selling_price = models.DecimalField( + max_digits=14, + decimal_places=2, + verbose_name=_("Selling Price"), + default=Decimal("0.00"), + ) + marked_price = models.DecimalField( + max_digits=14, + decimal_places=2, + verbose_name=_("Marked Price"), + default=Decimal("0.00"), + ) + discount_amount = models.DecimalField( + max_digits=14, + decimal_places=2, + verbose_name=_("Discount Amount"), + default=Decimal("0.00"), + ) + # 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")) @@ -734,20 +759,16 @@ class Car(Base): @property def logo(self): return getattr(self.id_car_make, "logo", "") - @property - def additional_services(self): - return self.finances.additional_services.all() - @property - def total_additional_services(self): - return sum([service.price_ for service in self.additional_services]) + # @property + # def additional_services(self): + # return self.additional_services.all() @property def ready(self): try: return all( [ self.colors, - self.finances, - self.finances.marked_price > 0, + self.marked_price > 0, ] ) except Exception: @@ -838,10 +859,51 @@ class Car(Base): car=self, exterior=exterior, interior=interior ) self.save() - @property def logo(self): return self.id_car_make.logo.url if self.id_car_make.logo else None + # + @property + def get_additional_services_amount(self): + return sum([Decimal(x.price_) for x in self.additional_services.all()]) + + def get_additional_services(self): + return {"services": [x for x in self.additional_services.all()],"total":self.get_additional_services_amount} + + @property + def vat_amount(self): + vat = VatRate.objects.filter(dealer=self.dealer,is_active=True).first() + return Decimal(self.marked_price) * (vat.rate / 100) + + @property + def total_vat(self): + return Decimal(self.marked_price) + Decimal(self.vat_amount) + # def get_discount_amount(self,estimate,user): + # try: + # instance = models.ExtraInfo.objects.get( + # dealer=self.dealer, + # content_object=estimate, + # related_object=user + # ) + # if instance: + # return instance.data.get("discount",0) + # return 0 + # except Exception: + # print("Error getting discount amount") + # return 0 + # @property + # def total_discount(self): + # if self.discount_amount > 0: + # return self.marked_price - self.discount_amount + # return self.marked_price + + # @property + # def total_vat(self): + # return round(self.total_discount + self.vat_amount + self.total_additionals, 2) + + + + class CarTransfer(models.Model): @@ -880,7 +942,7 @@ class CarTransfer(models.Model): @property def total_price(self): - return self.quantity * self.car.finances.total_vat + return self.quantity * self.car.total_vat # TODO : check later class Meta: verbose_name = _("Car Transfer Log") @@ -924,105 +986,105 @@ class CarReservation(models.Model): # Car Finance Model -class CarFinance(models.Model): - additional_services = models.ManyToManyField( - AdditionalServices, related_name="additional_finances", blank=True - ) - car = models.OneToOneField(Car, on_delete=models.CASCADE, related_name="finances") - cost_price = models.DecimalField( - max_digits=14, decimal_places=2, verbose_name=_("Cost Price") - ) - selling_price = models.DecimalField( - max_digits=14, - decimal_places=2, - verbose_name=_("Selling Price"), - default=Decimal("0.00"), - ) - marked_price = models.DecimalField( - max_digits=14, - decimal_places=2, - verbose_name=_("Marked Price"), - default=Decimal("0.00"), - ) - discount_amount = models.DecimalField( - max_digits=14, - decimal_places=2, - verbose_name=_("Discount Amount"), - default=Decimal("0.00"), - ) - # is_sold = models.BooleanField(default=False) +# class CarFinance(models.Model): +# additional_services = models.ManyToManyField( +# AdditionalServices, related_name="additional_finances", blank=True +# ) +# car = models.OneToOneField(Car, on_delete=models.CASCADE, related_name="finances") +# cost_price = models.DecimalField( +# max_digits=14, decimal_places=2, verbose_name=_("Cost Price") +# ) +# selling_price = models.DecimalField( +# max_digits=14, +# decimal_places=2, +# verbose_name=_("Selling Price"), +# default=Decimal("0.00"), +# ) +# marked_price = models.DecimalField( +# max_digits=14, +# decimal_places=2, +# verbose_name=_("Marked Price"), +# default=Decimal("0.00"), +# ) +# discount_amount = models.DecimalField( +# max_digits=14, +# decimal_places=2, +# verbose_name=_("Discount Amount"), +# default=Decimal("0.00"), +# ) +# # is_sold = models.BooleanField(default=False) - @property - def total(self): - return self.marked_price +# @property +# def total(self): +# return self.marked_price - @property - def total_additionals_no_vat(self): - return sum(x.price for x in self.additional_services.all()) +# @property +# def total_additionals_no_vat(self): +# return sum(x.price for x in self.additional_services.all()) - @property - def total_additionals(self): - return sum(x.price_ for x in self.additional_services.all()) +# @property +# def total_additionals(self): +# return sum(x.price_ for x in self.additional_services.all()) - @property - def total_discount(self): - if self.discount_amount > 0: - return self.marked_price - self.discount_amount - return self.marked_price +# @property +# def total_discount(self): +# if self.discount_amount > 0: +# return self.marked_price - self.discount_amount +# return self.marked_price - @property - def total_vat(self): - return round(self.total_discount + self.vat_amount + self.total_additionals, 2) +# @property +# def total_vat(self): +# return round(self.total_discount + self.vat_amount + self.total_additionals, 2) - @property - def vat_amount(self): - vat = VatRate.objects.filter(dealer=self.car.dealer, is_active=True).first() - if vat: - return (self.total_discount * Decimal(vat.rate)).quantize(Decimal("0.01")) - return Decimal("0.00") +# @property +# def vat_amount(self): +# vat = VatRate.objects.filter(dealer=self.car.dealer, is_active=True).first() +# if vat: +# return (self.total_discount * Decimal(vat.rate)).quantize(Decimal("0.01")) +# return Decimal("0.00") - @property - def revenue(self): - return self.marked_price - self.cost_price +# @property +# def revenue(self): +# return self.marked_price - self.cost_price - def to_dict(self): - return { - "cost_price": str(self.cost_price), - "selling_price": str(self.selling_price), - "marked_price": str(self.marked_price), - "discount_amount": str(self.discount_amount), - "total": str(self.total), - "total_discount": str(self.total_discount), - "total_vat": str(self.total_vat), - "vat_amount": str(self.vat_amount), - } +# def to_dict(self): +# return { +# "cost_price": str(self.cost_price), +# "selling_price": str(self.selling_price), +# "marked_price": str(self.marked_price), +# "discount_amount": str(self.discount_amount), +# "total": str(self.total), +# "total_discount": str(self.total_discount), +# "total_vat": str(self.total_vat), +# "vat_amount": str(self.vat_amount), +# } - def __str__(self): - return f"Car: {self.car}, Marked Price: {self.marked_price}" +# def __str__(self): +# return f"Car: {self.car}, Marked Price: {self.marked_price}" - # def save(self, *args, **kwargs): - # self.full_clean() - # try: - # price_after_discount = self.selling_price - self.discount_amount - # self.profit_margin = price_after_discount - self.cost_price - # self.vat_amount = settings.VAT_RATE - # except InvalidOperation as e: - # raise ValidationError(_("Invalid decimal operation: %s") % str(e)) - # super().save(*args, **kwargs) +# # def save(self, *args, **kwargs): +# # self.full_clean() +# # try: +# # price_after_discount = self.selling_price - self.discount_amount +# # self.profit_margin = price_after_discount - self.cost_price +# # self.vat_amount = settings.VAT_RATE +# # except InvalidOperation as e: +# # raise ValidationError(_("Invalid decimal operation: %s") % str(e)) +# # super().save(*args, **kwargs) - 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=["marked_price"], name="car_finance_marked_price_idx"), - models.Index(fields=["discount_amount"], name="car_finance_discount_idx"), - ] +# 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=["marked_price"], name="car_finance_marked_price_idx"), +# models.Index(fields=["discount_amount"], name="car_finance_discount_idx"), +# ] class ExteriorColors(models.Model, LocalizedNameMixin): @@ -2875,7 +2937,7 @@ class SaleOrder(models.Model): @property def price(self): - return self.car.finances.marked_price + return self.car.marked_price @property def items(self): diff --git a/inventory/signals.py b/inventory/signals.py index 21947d35..aa3dbf5e 100644 --- a/inventory/signals.py +++ b/inventory/signals.py @@ -270,6 +270,9 @@ def create_item_model(sender, instance, created, **kwargs): ) instance.item_model = inventory inventory.save() + else: + instance.item_model.default_amount = instance.marked_price + # inventory = entity.create_item_inventory( # name=instance.vin, # uom_model=uom, @@ -284,108 +287,108 @@ def create_item_model(sender, instance, created, **kwargs): # # update price - CarFinance -@receiver(post_save, sender=models.CarFinance) -def update_item_model_cost(sender, instance, created, **kwargs): - """ - Signal handler for updating an inventory item's cost and additional information - when a CarFinance instance is saved. This function updates the corresponding - inventory item of the car dealer's entity associated with the car's VIN by - modifying its default amount and updating additional data fields. +# @receiver(post_save, sender=models.CarFinance) +# def update_item_model_cost(sender, instance, created, **kwargs): +# """ +# Signal handler for updating an inventory item's cost and additional information +# when a CarFinance instance is saved. This function updates the corresponding +# inventory item of the car dealer's entity associated with the car's VIN by +# modifying its default amount and updating additional data fields. - :param sender: The model class that triggered the signal. - :param instance: The instance of the CarFinance that was saved. - :param created: A boolean indicating whether the model instance was newly created. - :param kwargs: Additional keyword arguments passed during the signal invocation. - :return: None - """ - # if created and not instance.is_sold: - # if created: - # entity = instance.car.dealer.entity - # coa = entity.get_default_coa() - # inventory_account = ( - # entity.get_all_accounts() - # .filter(name=f"Inventory:{instance.car.id_car_make.name}") - # .first() - # ) - # if not inventory_account: - # inventory_account = create_make_accounts( - # entity, - # coa, - # [instance.car.id_car_make], - # "Inventory", - # roles.ASSET_CA_INVENTORY, - # "debit", - # ) +# :param sender: The model class that triggered the signal. +# :param instance: The instance of the CarFinance that was saved. +# :param created: A boolean indicating whether the model instance was newly created. +# :param kwargs: Additional keyword arguments passed during the signal invocation. +# :return: None +# """ +# # if created and not instance.is_sold: +# # if created: +# # entity = instance.car.dealer.entity +# # coa = entity.get_default_coa() +# # inventory_account = ( +# # entity.get_all_accounts() +# # .filter(name=f"Inventory:{instance.car.id_car_make.name}") +# # .first() +# # ) +# # if not inventory_account: +# # inventory_account = create_make_accounts( +# # entity, +# # coa, +# # [instance.car.id_car_make], +# # "Inventory", +# # roles.ASSET_CA_INVENTORY, +# # "debit", +# # ) - # cogs = ( - # entity.get_all_accounts() - # .filter(name=f"Cogs:{instance.car.id_car_make.name}") - # .first() - # ) - # if not cogs: - # cogs = create_make_accounts( - # entity, coa, [instance.car.id_car_make], "Cogs", roles.COGS, "debit" - # ) - # revenue = ( - # entity.get_all_accounts() - # .filter(name=f"Revenue:{instance.car.id_car_make.name}") - # .first() - # ) - # if not revenue: - # revenue = create_make_accounts( - # entity, - # coa, - # [instance.car.id_car_make], - # "Revenue", - # roles.ASSET_CA_RECEIVABLES, - # "credit", - # ) +# # cogs = ( +# # entity.get_all_accounts() +# # .filter(name=f"Cogs:{instance.car.id_car_make.name}") +# # .first() +# # ) +# # if not cogs: +# # cogs = create_make_accounts( +# # entity, coa, [instance.car.id_car_make], "Cogs", roles.COGS, "debit" +# # ) +# # revenue = ( +# # entity.get_all_accounts() +# # .filter(name=f"Revenue:{instance.car.id_car_make.name}") +# # .first() +# # ) +# # if not revenue: +# # revenue = create_make_accounts( +# # entity, +# # coa, +# # [instance.car.id_car_make], +# # "Revenue", +# # roles.ASSET_CA_RECEIVABLES, +# # "credit", +# # ) - # cash_account = ( - # # entity.get_all_accounts() - # # .filter(name="Cash", role=roles.ASSET_CA_CASH) - # # .first() - # entity.get_all_accounts() - # .filter(role=roles.ASSET_CA_CASH, role_default=True) - # .first() - # ) +# # cash_account = ( +# # # entity.get_all_accounts() +# # # .filter(name="Cash", role=roles.ASSET_CA_CASH) +# # # .first() +# # entity.get_all_accounts() +# # .filter(role=roles.ASSET_CA_CASH, role_default=True) +# # .first() +# # ) - # ledger = LedgerModel.objects.create( - # entity=entity, name=f"Inventory Purchase - {instance.car}" - # ) - # je = JournalEntryModel.objects.create( - # ledger=ledger, - # description=f"Acquired {instance.car} for inventory", - # ) - # TransactionModel.objects.create( - # journal_entry=je, - # account=inventory_account, - # amount=Decimal(instance.cost_price), - # tx_type="debit", - # description="", - # ) +# # ledger = LedgerModel.objects.create( +# # entity=entity, name=f"Inventory Purchase - {instance.car}" +# # ) +# # je = JournalEntryModel.objects.create( +# # ledger=ledger, +# # description=f"Acquired {instance.car} for inventory", +# # ) +# # TransactionModel.objects.create( +# # journal_entry=je, +# # account=inventory_account, +# # amount=Decimal(instance.cost_price), +# # tx_type="debit", +# # description="", +# # ) - # TransactionModel.objects.create( - # journal_entry=je, - # account=cash_account, - # amount=Decimal(instance.cost_price), - # tx_type="credit", - # description="", - # ) +# # TransactionModel.objects.create( +# # journal_entry=je, +# # account=cash_account, +# # amount=Decimal(instance.cost_price), +# # tx_type="credit", +# # description="", +# # ) - instance.car.item_model.default_amount = instance.marked_price - # if not isinstance(instance.car.item_model.additional_info, dict): - # instance.car.item_model.additional_info = {} - # instance.car.item_model.additional_info.update({"car_finance": instance.to_dict()}) - # instance.car.item_model.additional_info.update( - # { - # "additional_services": [ - # service.to_dict() for service in instance.additional_services.all() - # ] - # } - # ) - instance.car.item_model.save() - print(f"Inventory item updated with CarFinance data for Car: {instance.car}") +# instance.car.item_model.default_amount = instance.marked_price +# # if not isinstance(instance.car.item_model.additional_info, dict): +# # instance.car.item_model.additional_info = {} +# # instance.car.item_model.additional_info.update({"car_finance": instance.to_dict()}) +# # instance.car.item_model.additional_info.update( +# # { +# # "additional_services": [ +# # service.to_dict() for service in instance.additional_services.all() +# # ] +# # } +# # ) +# instance.car.item_model.save() +# print(f"Inventory item updated with CarFinance data for Car: {instance.car}") # @receiver(pre_save, sender=models.SaleQuotation) @@ -803,144 +806,144 @@ def create_dealer_settings(sender, instance, created, **kwargs): # ) -def save_journal(car_finance, ledger, vendor): - """ - Saves a journal entry pertaining to a car finance transaction for a specific ledger and vendor. +# def save_journal(car_finance, ledger, vendor): +# """ +# Saves a journal entry pertaining to a car finance transaction for a specific ledger and vendor. - This function ensures that relevant accounts are updated to record financial transactions. It handles - debiting of the inventory account and crediting of the vendor account to maintain accurate bookkeeping. - Additionally, it creates vendor accounts dynamically if required and ties the created journal entry to - the ledger passed as a parameter. All transactions adhere to the ledger's entity-specific Chart of - Accounts (COA) configuration. +# This function ensures that relevant accounts are updated to record financial transactions. It handles +# debiting of the inventory account and crediting of the vendor account to maintain accurate bookkeeping. +# Additionally, it creates vendor accounts dynamically if required and ties the created journal entry to +# the ledger passed as a parameter. All transactions adhere to the ledger's entity-specific Chart of +# Accounts (COA) configuration. - :param car_finance: Instance of the car finance object containing details about the financed car - and its associated costs. - :type car_finance: `CarFinance` object - :param ledger: Ledger instance to which the journal entry is tied. This ledger must provide - entity-specific details, including its COA and related accounts. - :type ledger: `Ledger` object - :param vendor: Vendor instance representing the supplier or vendor related to the car finance - transaction. This vendor is used to derive or create the vendor account in COA. - :type vendor: `Vendor` object +# :param car_finance: Instance of the car finance object containing details about the financed car +# and its associated costs. +# :type car_finance: `CarFinance` object +# :param ledger: Ledger instance to which the journal entry is tied. This ledger must provide +# entity-specific details, including its COA and related accounts. +# :type ledger: `Ledger` object +# :param vendor: Vendor instance representing the supplier or vendor related to the car finance +# transaction. This vendor is used to derive or create the vendor account in COA. +# :type vendor: `Vendor` object - :return: None - """ - entity = ledger.entity - coa = entity.get_default_coa() - journal = JournalEntryModel.objects.create( - posted=False, - description=f"Finances of Car:{car_finance.car.vin} for Vendor:{car_finance.car.vendor.name}", - ledger=ledger, - locked=False, - origin="Payment", - ) - ledger.additional_info["je_number"] = journal.je_number - ledger.save() +# :return: None +# """ +# entity = ledger.entity +# coa = entity.get_default_coa() +# journal = JournalEntryModel.objects.create( +# posted=False, +# description=f"Finances of Car:{car_finance.car.vin} for Vendor:{car_finance.car.vendor.name}", +# ledger=ledger, +# locked=False, +# origin="Payment", +# ) +# ledger.additional_info["je_number"] = journal.je_number +# ledger.save() - inventory_account = ( - entity.get_default_coa_accounts().filter(role=roles.ASSET_CA_INVENTORY).first() - ) - vendor_account = entity.get_default_coa_accounts().filter(name=vendor.name).first() +# inventory_account = ( +# entity.get_default_coa_accounts().filter(role=roles.ASSET_CA_INVENTORY).first() +# ) +# vendor_account = entity.get_default_coa_accounts().filter(name=vendor.name).first() - if not vendor_account: - last_account = ( - entity.get_all_accounts() - .filter(role=roles.LIABILITY_CL_ACC_PAYABLE) - .order_by("-created") - .first() - ) - if len(last_account.code) == 4: - code = f"{int(last_account.code)}{1:03d}" - elif len(last_account.code) > 4: - code = f"{int(last_account.code) + 1}" +# if not vendor_account: +# last_account = ( +# entity.get_all_accounts() +# .filter(role=roles.LIABILITY_CL_ACC_PAYABLE) +# .order_by("-created") +# .first() +# ) +# if len(last_account.code) == 4: +# code = f"{int(last_account.code)}{1:03d}" +# elif len(last_account.code) > 4: +# code = f"{int(last_account.code) + 1}" - vendor_account = entity.create_account( - name=vendor.name, - code=code, - role=roles.LIABILITY_CL_ACC_PAYABLE, - coa_model=coa, - balance_type="credit", - active=True, - ) - additional_services_account = ( - entity.get_default_coa_accounts() - .filter(name="Additional Services", role=roles.COGS) - .first() - ) +# vendor_account = entity.create_account( +# name=vendor.name, +# code=code, +# role=roles.LIABILITY_CL_ACC_PAYABLE, +# coa_model=coa, +# balance_type="credit", +# active=True, +# ) +# additional_services_account = ( +# entity.get_default_coa_accounts() +# .filter(name="Additional Services", role=roles.COGS) +# .first() +# ) - # Debit Inventory Account - TransactionModel.objects.create( - journal_entry=journal, - account=inventory_account, - amount=car_finance.cost_price, - tx_type="debit", - ) +# # Debit Inventory Account +# TransactionModel.objects.create( +# journal_entry=journal, +# account=inventory_account, +# amount=car_finance.cost_price, +# tx_type="debit", +# ) - # Credit Vendor Account - TransactionModel.objects.create( - journal_entry=journal, - account=vendor_account, - amount=car_finance.cost_price, - tx_type="credit", - ) +# # Credit Vendor Account +# TransactionModel.objects.create( +# journal_entry=journal, +# account=vendor_account, +# amount=car_finance.cost_price, +# tx_type="credit", +# ) -@receiver(post_save, sender=models.CarFinance) -def update_finance_cost(sender, instance, created, **kwargs): - """ - Signal to handle `post_save` functionality for the `CarFinance` model. This function - creates or updates financial records related to a car's finance details in the ledger - associated with the car's vendor and dealer. For newly created instances, a ledger is - created or retrieved, and the `save_journal` function is executed to log the financial - transactions. +# @receiver(post_save, sender=models.CarFinance) +# def update_finance_cost(sender, instance, created, **kwargs): +# """ +# Signal to handle `post_save` functionality for the `CarFinance` model. This function +# creates or updates financial records related to a car's finance details in the ledger +# associated with the car's vendor and dealer. For newly created instances, a ledger is +# created or retrieved, and the `save_journal` function is executed to log the financial +# transactions. - This function also has commented-out logic to handle updates for already created - instances, including journal updates or adjustments to existing financial transactions. +# This function also has commented-out logic to handle updates for already created +# instances, including journal updates or adjustments to existing financial transactions. - :param sender: Model class that triggered the signal - :type sender: Model - :param instance: Instance of the `CarFinance` model passed to the signal - :type instance: CarFinance - :param created: Boolean value indicating if the instance was created (`True`) or updated (`False`) - :type created: bool - :param kwargs: Arbitrary keyword arguments passed to the signal - :type kwargs: dict - :return: None - """ - if created: - entity = instance.car.dealer.entity - vendor = instance.car.vendor - vin = instance.car.vin if instance.car.vin else "" - make = instance.car.id_car_make.name if instance.car.id_car_make else "" - model = instance.car.id_car_model.name if instance.car.id_car_model else "" - year = instance.car.year - vendor_name = vendor.name if vendor else "" +# :param sender: Model class that triggered the signal +# :type sender: Model +# :param instance: Instance of the `CarFinance` model passed to the signal +# :type instance: CarFinance +# :param created: Boolean value indicating if the instance was created (`True`) or updated (`False`) +# :type created: bool +# :param kwargs: Arbitrary keyword arguments passed to the signal +# :type kwargs: dict +# :return: None +# """ +# if created: +# entity = instance.car.dealer.entity +# vendor = instance.car.vendor +# vin = instance.car.vin if instance.car.vin else "" +# make = instance.car.id_car_make.name if instance.car.id_car_make else "" +# model = instance.car.id_car_model.name if instance.car.id_car_model else "" +# year = instance.car.year +# vendor_name = vendor.name if vendor else "" - name = f"{vin}-{make}-{model}-{year}-{vendor_name}" - ledger, _ = LedgerModel.objects.get_or_create(name=name, entity=entity) - save_journal(instance, ledger, vendor) +# name = f"{vin}-{make}-{model}-{year}-{vendor_name}" +# ledger, _ = LedgerModel.objects.get_or_create(name=name, entity=entity) +# save_journal(instance, ledger, vendor) - # if not created: - # if ledger.additional_info.get("je_number"): - # journal = JournalEntryModel.objects.filter(je_number=ledger.additional_info.get("je_number")).first() - # journal.description = f"Finances of Car:{instance.car.vin} for Vendor:{instance.car.vendor.vendor_name}" - # journal.save() - # debit = journal.get_transaction_queryset().filter(tx_type='debit').first() - # credit = journal.get_transaction_queryset().filter(tx_type='credit').first() - # if debit and credit: - # if journal.is_locked(): - # journal.mark_as_unlocked() - # journal.save() - # debit.amount = instance.cost_price - # credit.amount = instance.cost_price - # debit.save() - # credit.save() - # else: - # save_journal(instance,ledger,vendor,journal=journal) - # else: - # save_journal(instance,ledger,vendor) - # else: - # save_journal(instance,ledger,vendor) +# # if not created: +# # if ledger.additional_info.get("je_number"): +# # journal = JournalEntryModel.objects.filter(je_number=ledger.additional_info.get("je_number")).first() +# # journal.description = f"Finances of Car:{instance.car.vin} for Vendor:{instance.car.vendor.vendor_name}" +# # journal.save() +# # debit = journal.get_transaction_queryset().filter(tx_type='debit').first() +# # credit = journal.get_transaction_queryset().filter(tx_type='credit').first() +# # if debit and credit: +# # if journal.is_locked(): +# # journal.mark_as_unlocked() +# # journal.save() +# # debit.amount = instance.cost_price +# # credit.amount = instance.cost_price +# # debit.save() +# # credit.save() +# # else: +# # save_journal(instance,ledger,vendor,journal=journal) +# # else: +# # save_journal(instance,ledger,vendor) +# # else: +# # save_journal(instance,ledger,vendor) @receiver(post_save, sender=PurchaseOrderModel) diff --git a/inventory/urls.py b/inventory/urls.py index cca5ab0a..4c788df5 100644 --- a/inventory/urls.py +++ b/inventory/urls.py @@ -345,13 +345,8 @@ urlpatterns = [ name="car_delete", ), path( - "/cars//finance/create/", - views.CarFinanceCreateView.as_view(), - name="car_finance_create", - ), - path( - "/cars/finance//update/", - views.CarFinanceUpdateView.as_view(), + "/cars//finance/update/", + views.CarFinanceUpdateView, name="car_finance_update", ), path( @@ -1091,6 +1086,10 @@ urlpatterns = [ views.MonthlyIncomeStatementView.as_view(), name="entity-ic-date", ), + # Chart of Accounts... + path('/chart-of-accounts//list/', + views.ChartOfAccountModelListView.as_view(), + name='coa-list'), # CASH FLOW STATEMENTS... # Entities... path( diff --git a/inventory/utils.py b/inventory/utils.py index 164d93f7..e21b9274 100644 --- a/inventory/utils.py +++ b/inventory/utils.py @@ -779,7 +779,6 @@ class CarTransfer: ) self.bill.additional_info = {} self.bill.additional_info.update({"car_info": self.car.to_dict()}) - self.bill.additional_info.update({"car_finance": self.car.finances.to_dict()}) self.bill.mark_as_review() self.bill.mark_as_approved( @@ -791,14 +790,14 @@ class CarTransfer: self.car.dealer = self.to_dealer self.car.vendor = self.vendor self.car.receiving_date = datetime.datetime.now() - self.car.finances.additional_services.clear() + self.car.additional_services.clear() if hasattr(self.car, "custom_cards"): self.car.custom_cards.delete() - self.car.finances.cost_price = self.transfer.total_price - self.car.finances.marked_price = 0 - self.car.finances.discount_amount = 0 - self.car.finances.save() + self.car.cost_price = self.transfer.total_price + self.car.marked_price = 0 + self.car.discount_amount = 0 + self.car.save() self.car.location.owner = self.to_dealer self.car.location.showroom = self.to_dealer self.car.location.description = "" @@ -920,7 +919,7 @@ class CarTransfer: # ) # bill.additional_info.update({"car_info": car.to_dict()}) -# bill.additional_info.update({"car_finance": car.finances.to_dict()}) +# bill.additional_info.update({"car_finance": car.to_dict()}) # bill.mark_as_review() # bill.mark_as_approved(to_dealer.entity.slug, to_dealer.entity.admin) @@ -929,14 +928,14 @@ class CarTransfer: # car.dealer = to_dealer # car.vendor = vendor # car.receiving_date = datetime.datetime.now() -# car.finances.additional_services.clear() +# car.additional_services.clear() # if hasattr(car, "custom_cards"): # car.custom_cards.delete() -# car.finances.cost_price = transfer.total_price -# car.finances.selling_price = 0 -# car.finances.discount_amount = 0 -# car.finances.save() +# car.cost_price = transfer.total_price +# car.selling_price = 0 +# car.discount_amount = 0 +# car.save() # car.location.owner = to_dealer # car.location.showroom = to_dealer # car.location.description = "" @@ -980,6 +979,153 @@ def to_dict(obj): return obj_dict +class CarFinanceCalculator1: + """ + Class responsible for calculating car financing details. + + This class provides methods and attributes required for calculating various + aspects related to car financing, such as VAT calculation, pricing, discounts, + and additional services. It processes data about cars, computes totals (e.g., + price, VAT, discounts), and aggregates the financial data for reporting or + further processing. + + :ivar model: The data model passed to the calculator for retrieving transaction data. + :type model: Any + :ivar vat_rate: The current active VAT rate retrieved from the database. + :type vat_rate: Decimal + :ivar item_transactions: A collection of item transactions retrieved from the model. + :type item_transactions: list + :ivar additional_services: A list of additional services with details (e.g., name, price, taxable status). + :type additional_services: list + """ + + VAT_OBJ_NAME = "vat_rate" + CAR_FINANCE_KEY = "car_finance" + CAR_INFO_KEY = "car_info" + ADDITIONAL_SERVICES_KEY = "additional_services" + + def __init__(self, model): + if isinstance(model, InvoiceModel): + self.dealer = models.Dealer.objects.get(entity=model.ce_model.entity) + self.extra_info = models.ExtraInfo.objects.get( + dealer=self.dealer, + content_type=ContentType.objects.get_for_model(model.ce_model), + object_id=model.ce_model.pk, + ) + elif isinstance(model, EstimateModel): + self.dealer = models.Dealer.objects.get(entity=model.entity) + self.extra_info = models.ExtraInfo.objects.get( + dealer=self.dealer, + content_type=ContentType.objects.get_for_model(model), + object_id=model.pk, + ) + self.model = model + self.vat_rate = self._get_vat_rate() + self.item_transactions = self._get_item_transactions() + # self.additional_services = self._get_additional_services() + + + def _get_vat_rate(self): + vat = models.VatRate.objects.filter(dealer=self.dealer,is_active=True).first() + if not vat: + raise ObjectDoesNotExist("No active VAT rate found") + return vat.rate + + def _get_additional_services(self): + return [x for item in self.item_transactions + for x in item.item_model.car.additional_services + ] + def _get_item_transactions(self): + return self.model.get_itemtxs_data()[0].all() + + def get_items(self): + return self._get_item_transactions() + @staticmethod + def _get_quantity(item): + return item.ce_quantity or item.quantity + + # def _get_nested_value(self, item, *keys): + # current = item.item_model.additional_info + # for key in keys: + # current = current.get(key, {}) + # return current + + def _get_car_data(self, item): + quantity = self._get_quantity(item) + car = item.item_model.car + unit_price = Decimal(car.marked_price) + discount = self.extra_info.data.get("discount",0) + sell_price = unit_price - Decimal(discount) + return { + "item_number": item.item_model.item_number, + "vin": car.vin, #car_info.get("vin"), + "make": car.id_car_make ,#car_info.get("make"), + "model": car.id_car_model ,#car_info.get("model"), + "year": car.year ,# car_info.get("year"), + "logo": car.logo, # getattr(car.id_car_make, "logo", ""), + "trim": car.id_car_trim ,# car_info.get("trim"), + "mileage": car.mileage ,# car_info.get("mileage"), + "cost_price": car.cost_price, + "selling_price": car.selling_price, + "marked_price": car.marked_price, + "discount": car.discount_amount, + "quantity": quantity, + "unit_price": unit_price, + "sell_price": sell_price, + "total": unit_price, + "total_vat": sell_price * self.vat_rate, + "total_discount": discount, + "final_price": sell_price + (sell_price * self.vat_rate), + "total_additionals": car.total_additional_services, + "grand_total": sell_price + (sell_price * self.vat_rate) + car.total_additional_services, + "additional_services": car.additional_services,# self._get_nested_value( + #item, self.ADDITIONAL_SERVICES_KEY + #), + } + + def calculate_totals(self): + total_price = sum( + Decimal(item.item_model.car.marked_price) + for item in self.item_transactions + ) + total_additionals = sum( + Decimal(item.price_) for item in self._get_additional_services()) + + total_discount = self.extra_info.data.get("discount",0) + total_price_discounted = total_price + if total_discount: + total_price_discounted = total_price - Decimal(total_discount) + print(total_price_discounted) + total_vat_amount = total_price_discounted * self.vat_rate + + return { + "total_price_discounted":total_price_discounted, + "total_price_before_discount":total_price, + "total_price": total_price_discounted, + "total_vat_amount": total_vat_amount, + "total_discount": Decimal(total_discount), + "total_additionals": total_additionals, + "grand_total":total_price_discounted + total_vat_amount + total_additionals, + } + + def get_finance_data(self): + totals = self.calculate_totals() + return { + "cars": [self._get_car_data(item) for item in self.item_transactions], + "quantity": sum( + self._get_quantity(item) for item in self.item_transactions + ), + "total_price": round(totals["total_price"], 2), + "total_price_discounted": round(totals["total_price_discounted"], 2), + "total_price_before_discount": round(totals["total_price_before_discount"], 2), + "total_vat": round(totals["total_vat_amount"] + totals["total_price"], 2), + "total_vat_amount": round(totals["total_vat_amount"], 2), + "total_discount": round(totals["total_discount"], 2), + "total_additionals": round(totals["total_additionals"], 2), + "grand_total": round(totals["grand_total"], 2), + "additionals": self._get_additional_services(), + "vat": round(self.vat_rate, 2), + } class CarFinanceCalculator: """ Class responsible for calculating car financing details. @@ -1054,7 +1200,7 @@ class CarFinanceCalculator: def _get_car_data(self, item): quantity = self._get_quantity(item) car = item.item_model.car - unit_price = Decimal(car.finances.marked_price) + unit_price = Decimal(car.marked_price) discount = self.extra_info.data.get("discount",0) sell_price = unit_price - Decimal(discount) return { @@ -1066,10 +1212,10 @@ class CarFinanceCalculator: "logo": car.logo, # getattr(car.id_car_make, "logo", ""), "trim": car.id_car_trim ,# car_info.get("trim"), "mileage": car.mileage ,# car_info.get("mileage"), - "cost_price": car.finances.cost_price, - "selling_price": car.finances.selling_price, - "marked_price": car.finances.marked_price, - "discount": car.finances.discount_amount, + "cost_price": car.cost_price, + "selling_price": car.selling_price, + "marked_price": car.marked_price, + "discount": car.discount_amount, "quantity": quantity, "unit_price": unit_price, "sell_price": sell_price, @@ -1086,7 +1232,7 @@ class CarFinanceCalculator: def calculate_totals(self): total_price = sum( - Decimal(item.item_model.car.finances.marked_price) + Decimal(item.item_model.car.marked_price) for item in self.item_transactions ) total_additionals = sum( @@ -1112,7 +1258,7 @@ class CarFinanceCalculator: def get_finance_data(self): totals = self.calculate_totals() return { - "cars": [self._get_car_data(item) for item in self.item_transactions], + "car": [self._get_car_data(item) for item in self.item_transactions], "quantity": sum( self._get_quantity(item) for item in self.item_transactions ), @@ -1127,6 +1273,53 @@ class CarFinanceCalculator: "additionals": self._get_additional_services(), "vat": round(self.vat_rate, 2), } + +def get_finance_data(estimate,dealer): + vat = models.VatRate.objects.filter(dealer=dealer,is_active=True).first() + item = estimate.get_itemtxs_data()[0].first() + car = item.item_model.car + if isinstance(estimate,InvoiceModel) and hasattr(estimate, "ce_model"): + estimate = estimate.ce_model + + extra_info = models.ExtraInfo.objects.get( + dealer=dealer, + content_type=ContentType.objects.get_for_model(EstimateModel), + object_id=estimate.pk, + ) + discount = extra_info.data.get("discount", 0) + discount = Decimal(discount) + vat_amount = car.marked_price * vat.rate + additional_services = car.get_additional_services() + + return { + "car": car, + "discounted_price": (Decimal(car.marked_price) - discount) or 0, + "price_before_discount": car.marked_price, + "vat_amount": vat_amount, + "vat_rate": vat.rate, + "discount_amount": discount, + "additional_services": additional_services, + "grand_total": (car.marked_price - discount) + vat_amount + additional_services.get("total") + } + + + # totals = self.calculate_totals() + # return { + # "car": [self._get_car_data(item) for item in self.item_transactions], + # "quantity": sum( + # self._get_quantity(item) for item in self.item_transactions + # ), + # "total_price": round(totals["total_price"], 2), + # "total_price_discounted": round(totals["total_price_discounted"], 2), + # "total_price_before_discount": round(totals["total_price_before_discount"], 2), + # "total_vat": round(totals["total_vat_amount"] + totals["total_price"], 2), + # "total_vat_amount": round(totals["total_vat_amount"], 2), + # "total_discount": round(totals["total_discount"], 2), + # "total_additionals": round(totals["total_additionals"], 2), + # "grand_total": round(totals["grand_total"], 2), + # "additionals": self._get_additional_services(), + # "vat": round(self.vat_rate, 2), + # } # class CarFinanceCalculator: # """ # Class responsible for calculating car financing details. @@ -1360,9 +1553,9 @@ def _post_sale_and_cogs(invoice, dealer): 2) COGS / Inventory journal """ entity = invoice.ledger.entity - calc = CarFinanceCalculator(invoice) - data = calc.get_finance_data() - + # calc = CarFinanceCalculator(invoice) + data = get_finance_data(invoice,dealer) + car = data.get("car") cash_acc = entity.get_all_accounts().filter(role_default=True, role=roles.ASSET_CA_CASH).first() ar_acc = entity.get_all_accounts().filter(role_default=True, role=roles.ASSET_CA_RECEIVABLES).first() vat_acc = entity.get_all_accounts().filter(role_default=True, role=roles.LIABILITY_CL_TAXES_PAYABLE).first() @@ -1371,105 +1564,105 @@ def _post_sale_and_cogs(invoice, dealer): cogs_acc = entity.get_all_accounts().filter(role_default=True, role=roles.COGS).first() inv_acc = entity.get_all_accounts().filter(role_default=True, role=roles.ASSET_CA_INVENTORY).first() - for car_data in data['cars']: - car = invoice.get_itemtxs_data()[0].filter( - item_model__car__vin=car_data['vin'] - ).first().item_model.car - qty = Decimal(car_data['quantity']) + # for car_data in data['cars']: + # car = invoice.get_itemtxs_data()[0].filter( + # item_model__car__vin=car_data['vin'] + # ).first().item_model.car + # qty = Decimal(car_data['quantity']) - net_car_price = Decimal(car_data['total']) - Decimal(car_data['total_discount']) - net_additionals_price = Decimal(data['total_additionals']) - vat_amount = Decimal(data['total_vat_amount']) * qty - grand_total = net_car_price + net_additionals_price + vat_amount - cost_total = Decimal(car_data['cost_price']) * qty + net_car_price = Decimal(data['discounted_price']) + net_additionals_price = Decimal(data['additional_services']['total']) + vat_amount = Decimal(data['vat_amount']) + grand_total = net_car_price + net_additionals_price + vat_amount + cost_total = Decimal(car.cost_price) - # ------------------------------------------------------------------ - # 2A. Journal: Cash / A-R / VAT / Sales - # ------------------------------------------------------------------ + # ------------------------------------------------------------------ + # 2A. Journal: Cash / A-R / VAT / Sales + # ------------------------------------------------------------------ - je_sale = JournalEntryModel.objects.create( - ledger=invoice.ledger, - description=f"Sale {car.vin}", - origin=f"Invoice {invoice.invoice_number}", - locked=False, - posted=False - ) - # Dr Cash (what the customer paid) + je_sale = JournalEntryModel.objects.create( + ledger=invoice.ledger, + description=f"Sale {car.vin}", + origin=f"Invoice {invoice.invoice_number}", + locked=False, + posted=False + ) + # Dr Cash (what the customer paid) + TransactionModel.objects.create( + journal_entry=je_sale, + account=cash_acc, + amount=grand_total, + tx_type='debit' + ) + + # # Cr A/R (clear the receivable) + # TransactionModel.objects.create( + # journal_entry=je_sale, + # account=ar_acc, + # amount=grand_total, + # tx_type='credit' + # ) + + # Cr VAT Payable + TransactionModel.objects.create( + journal_entry=je_sale, + account=vat_acc, + amount=vat_amount, + tx_type='credit' + ) + + # Cr Sales – Car + TransactionModel.objects.create( + journal_entry=je_sale, + account=car_rev, + amount=net_car_price, + tx_type='credit' + ) + + if net_additionals_price > 0: + # Cr Sales – Additional Services TransactionModel.objects.create( journal_entry=je_sale, - account=cash_acc, - amount=grand_total, - tx_type='debit' - ) - - # # Cr A/R (clear the receivable) - # TransactionModel.objects.create( - # journal_entry=je_sale, - # account=ar_acc, - # amount=grand_total, - # tx_type='credit' - # ) - - # Cr VAT Payable - TransactionModel.objects.create( - journal_entry=je_sale, - account=vat_acc, - amount=vat_amount, + account=add_rev, + amount=net_additionals_price, tx_type='credit' ) - # Cr Sales – Car - TransactionModel.objects.create( - journal_entry=je_sale, - account=car_rev, - amount=net_car_price, - tx_type='credit' - ) + # ------------------------------------------------------------------ + # 2B. Journal: COGS / Inventory reduction + # ------------------------------------------------------------------ + je_cogs = JournalEntryModel.objects.create( + ledger=invoice.ledger, + description=f"COGS {car.vin}", + origin=f"Invoice {invoice.invoice_number}", + locked=False, + posted=False + ) - if net_additionals_price > 0: - # Cr Sales – Additional Services - TransactionModel.objects.create( - journal_entry=je_sale, - account=add_rev, - amount=net_additionals_price, - tx_type='credit' - ) + # Dr COGS + TransactionModel.objects.create( + journal_entry=je_cogs, + account=cogs_acc, + amount=cost_total, + tx_type='debit' + ) - # ------------------------------------------------------------------ - # 2B. Journal: COGS / Inventory reduction - # ------------------------------------------------------------------ - je_cogs = JournalEntryModel.objects.create( - ledger=invoice.ledger, - description=f"COGS {car.vin}", - origin=f"Invoice {invoice.invoice_number}", - locked=False, - posted=False - ) - - # Dr COGS - TransactionModel.objects.create( - journal_entry=je_cogs, - account=cogs_acc, - amount=cost_total, - tx_type='debit' - ) - - # Cr Inventory - TransactionModel.objects.create( - journal_entry=je_cogs, - account=inv_acc, - amount=cost_total, - tx_type='credit' - ) - # ------------------------------------------------------------------ - # 2C. Update car state flags inside the same transaction - # ------------------------------------------------------------------ - entity.get_items_inventory().filter(name=car.vin).update(for_inventory=False) - # car.item_model.for_inventory = False - # car.item_model.save(update_fields=['for_inventory']) - car.finances.selling_price = grand_total - car.finances.is_sold = True - car.finances.save() + # Cr Inventory + TransactionModel.objects.create( + journal_entry=je_cogs, + account=inv_acc, + amount=cost_total, + tx_type='credit' + ) + # ------------------------------------------------------------------ + # 2C. Update car state flags inside the same transaction + # ------------------------------------------------------------------ + entity.get_items_inventory().filter(name=car.vin).update(for_inventory=False) + # car.item_model.for_inventory = False + # car.item_model.save(update_fields=['for_inventory']) + car.selling_price = grand_total + # car.is_sold = True + car.save() # def handle_account_process(invoice, amount, finance_data): # """ # Processes accounting transactions based on an invoice, financial data, diff --git a/inventory/views.py b/inventory/views.py index 212dcad3..da7b99ce 100644 --- a/inventory/views.py +++ b/inventory/views.py @@ -105,6 +105,9 @@ from django_ledger.forms.bank_account import ( BankAccountCreateForm, BankAccountUpdateForm, ) +from django_ledger.views.chart_of_accounts import ( + ChartOfAccountModelListView as ChartOfAccountModelListViewBase +) from django_ledger.views.bill import ( BillModelCreateView, BillModelDetailView, @@ -126,6 +129,7 @@ from django_ledger.forms.invoice import ( from django_ledger.forms.item import ( InventoryItemCreateForm, ) + from django_ledger.forms.purchase_order import ( PurchaseOrderModelCreateForm, BasePurchaseOrderModelUpdateForm, @@ -195,6 +199,7 @@ from .services import ( from .utils import ( CarFinanceCalculator, get_car_finance_data, + get_finance_data, get_item_transactions, handle_payment, reserve_car, @@ -421,10 +426,10 @@ class ManagerDashboard(LoginRequiredMixin, TemplateView): qs = models.Car.objects.filter(dealer=dealer) total_cars = qs.count() - stats = models.CarFinance.objects.filter(car__dealer=dealer).aggregate( - total_cost_price=Sum("cost_price"), - total_selling_price=Sum("selling_price"), - ) + stats = 0 # models.CarFinance.objects.filter(car__dealer=dealer).aggregate( #TODO:update_finance + # total_cost_price=Sum("cost_price"), + # total_selling_price=Sum("selling_price"), + # ) total_cost_price = stats["total_cost_price"] or 0 total_selling_price = stats["total_selling_price"] or 0 total_profit = total_selling_price - total_cost_price @@ -1459,124 +1464,21 @@ class CarDetailView(LoginRequiredMixin, PermissionRequiredMixin, DetailView): permission_required = ["inventory.view_car"] -class CarFinanceCreateView(LoginRequiredMixin, PermissionRequiredMixin,SuccessMessageMixin, CreateView): - """ - Handles the creation of car finance records within the inventory system. +def CarFinanceUpdateView(request,dealer_slug,slug): + car = get_object_or_404(models.Car, slug=slug) + dealer = get_object_or_404(models.Dealer, slug=dealer_slug) - This view provides functionality to create car finance records tied to a - specific car in the inventory. It enforces that the user is logged in and - has the required permissions to add a car finance record. It also customizes - form behavior and context data to associate the finance record with the - corresponding car and populate additional services based on the user's type. - - :ivar model: The database model associated with the view. - :type model: models.CarFinance - :ivar form_class: The form class used to create car finance records. - :type form_class: forms.CarFinanceForm - :ivar template_name: The template used to render the car finance creation page. - :type template_name: str - :ivar permission_required: The list of permissions required to access this view. - :type permission_required: list - """ - - model = models.CarFinance - form_class = forms.CarFinanceForm - template_name = "inventory/car_finance_form.html" - permission_required = ["inventory.add_carfinance"] - - def dispatch(self, request, *args, **kwargs): - self.car = get_object_or_404(models.Car, slug=self.kwargs["slug"]) - return super().dispatch(request, *args, **kwargs) - - def form_valid(self, form): - form.instance.car = self.car - messages.success(self.request, _("Car finance details saved successfully")) - return super().form_valid(form) - - def get_success_url(self): - return reverse( - "car_detail", - kwargs={"dealer_slug": self.request.dealer.slug, "slug": self.car.slug}, - ) - - def get_context_data(self, **kwargs): - context = super().get_context_data(**kwargs) - context["car"] = self.car - return context - - # def get_form(self, form_class=None): - # form = super().get_form(form_class) - # dealer = get_user_type(self.request) - # form.fields[ - # "additional_finances" - # ].queryset = models.AdditionalServices.objects.filter(dealer=dealer) - # return form - - -class CarFinanceUpdateView( - LoginRequiredMixin, PermissionRequiredMixin, SuccessMessageMixin, UpdateView -): - """ - Manages the update of car finance details. - - This class-based view provides functionality for updating car finance - information in the system. It enforces login and specific permissions for - access, and it leverages mixins to provide success messages upon completing - updates. It is designed to interact with car finance data, update form - handling, and enforce related business logic. - - :ivar model: The model associated with the view. - :type model: models.CarFinance - :ivar form_class: The form class used by the view. - :type form_class: forms.CarFinanceForm - :ivar template_name: The template used for rendering the form. - :type template_name: str - :ivar success_message: The success message displayed after an update. - :type success_message: str - :ivar permission_required: List of permissions required to access the view. - :type permission_required: list - """ - - model = models.CarFinance - form_class = forms.CarFinanceForm - template_name = "inventory/car_finance_form.html" - success_message = _("Car finance details updated successfully") - permission_required = ["inventory.change_carfinance"] - - def get_success_url(self): - return reverse( - "car_detail", - kwargs={ - "dealer_slug": self.request.dealer.slug, - "slug": self.object.car.slug, - }, - ) - - def get_form_kwargs(self): - kwargs = super().get_form_kwargs() - kwargs["instance"] = self.get_object() - return kwargs - - def get_context_data(self , **kwargs): - context = super().get_context_data(**kwargs) - context["car"] = self.object.car - return context - # def get_initial(self): - # initial = super().get_initial() - # instance = self.get_object() - # dealer = get_user_type(self.request) - # selected_items = instance.additional_services.filter(dealer=dealer) - # initial["additional_finances"] = selected_items - # return initial - - # def get_form(self, form_class=None): - # form = super().get_form(form_class) - # dealer = get_user_type(self.request) - # form.fields[ - # "additional_finances" - # ].queryset = models.AdditionalServices.objects.filter(dealer=dealer) - # return form + if request.method == "POST": + form = forms.CarFinanceForm(request.POST, instance=car) + if form.is_valid(): + form.save() + return redirect("car_detail", dealer_slug=dealer.slug, slug=car.slug) + else: + messages.error(request, "Please correct the errors below.") + else: + form = forms.CarFinanceForm(instance=car) + return render(request, "inventory/car_finance_form.html", {"car": car, "dealer": dealer, "form": form}) class CarUpdateView( LoginRequiredMixin, PermissionRequiredMixin, SuccessMessageMixin, UpdateView @@ -3064,7 +2966,7 @@ def GroupPermissionView(request, dealer_slug, pk): # Define ALL permissions you want to manage MODEL_LIST = [ ("inventory", "car"), - ("inventory", "carfinance"), + # ("inventory", "carfinance"), ("inventory", "carlocation"), ("inventory", "customcard"), ("inventory", "cartransfer"), @@ -3955,7 +3857,7 @@ class BankAccountListView(LoginRequiredMixin, PermissionRequiredMixin, ListView) def get_queryset(self): query = self.request.GET.get("q") - dealer = get_user_type(self.request) + dealer = self.request.dealer bank_accounts = BankAccountModel.objects.filter(entity_model=dealer.entity) return apply_search_filters(bank_accounts, query) @@ -4659,8 +4561,7 @@ def create_estimate(request, dealer_slug, slug=None): hash=item.get("item_id"), # finances__is_sold=False, colors__isnull=False, - finances__isnull=False, - finances__marked_price__gt=1, + marked_price__gt=1, status="available", ).all() @@ -4670,9 +4571,9 @@ def create_estimate(request, dealer_slug, slug=None): { "item_number": i.item_model.item_number, "quantity": 1, - "unit_cost": round(float(i.finances.marked_price)), - "unit_revenue": round(float(i.finances.marked_price)), - "total_amount": round(float(i.finances.total_vat)), + "unit_cost": round(float(i.marked_price)), + "unit_revenue": round(float(i.marked_price)), + "total_amount": round(float(i.total_vat)),# TODO : check later } ) @@ -4690,10 +4591,10 @@ def create_estimate(request, dealer_slug, slug=None): # instance = models.Car.objects.get(vin=item.name) # estimate_itemtxs = { # item.item_number: { - # "unit_cost": instance.finances.cost_price, - # "unit_revenue": instance.finances.marked_price, + # "unit_cost": instance.cost_price, + # "unit_revenue": instance.marked_price, # "quantity": Decimal(quantities), - # "total_amount": instance.finances.total_vat * int(quantities), + # "total_amount": instance.total_vat * int(quantities),# TODO : check later # } # } @@ -4773,8 +4674,7 @@ def create_estimate(request, dealer_slug, slug=None): models.Car.objects.filter( dealer=dealer, colors__isnull=False, - finances__isnull=False, - finances__marked_price__gt=1, + marked_price__gt=1, status="available", ) .annotate( @@ -4849,17 +4749,18 @@ class EstimateDetailView(LoginRequiredMixin, PermissionRequiredMixin, DetailView dealer = get_object_or_404(models.Dealer, slug=self.kwargs["dealer_slug"]) estimate = kwargs.get("object") if estimate.get_itemtxs_data(): - calculator = CarFinanceCalculator(estimate) - finance_data = calculator.get_finance_data() + # calculator = CarFinanceCalculator(estimate) + # finance_data = calculator.get_finance_data() + finance_data = get_finance_data(estimate,dealer) invoice_obj = InvoiceModel.objects.all().filter(ce_model=estimate).first() kwargs["data"] = finance_data kwargs["invoice"] = invoice_obj try: - car_finances = estimate.get_itemtxs_data()[0].first().item_model.car.finances - selected_items = car_finances.additional_services.filter(dealer=dealer) + car = estimate.get_itemtxs_data()[0].first().item_model.car + selected_items = car.additional_services.filter(dealer=dealer) form = forms.AdditionalFinancesForm() - form.fields["additional_finances"].queryset = form.fields["additional_finances"].queryset.filter(dealer=dealer) + form.fields["additional_finances"].queryset = form.fields["additional_finances"].queryset.filter(dealer=dealer) # TODO : check later form.initial["additional_finances"] = selected_items kwargs["additionals_form"] = form except Exception as e: @@ -4938,8 +4839,8 @@ def create_sale_order(request, dealer_slug, pk): # else: # form.fields["opportunity"].widget = HiddenInput() - calculator = CarFinanceCalculator(estimate) - finance_data = calculator.get_finance_data() + # calculator = CarFinanceCalculator(estimate) + finance_data = get_finance_data(estimate,dealer) return render( request, "sales/estimates/sale_order_form.html", @@ -4957,15 +4858,16 @@ def update_estimate_discount(request, dealer_slug, pk): content_type=ContentType.objects.get_for_model(EstimateModel), object_id=estimate.pk, ) + # calculator = CarFinanceCalculator(estimate) + # finance_data = calculator.get_finance_data() discount_amount = request.POST.get("discount_amount", 0) - calculator = CarFinanceCalculator(estimate) - finance_data = calculator.get_finance_data() - if Decimal(discount_amount) >= finance_data.get('cars')[0]['marked_price']: + finance_data = get_finance_data(estimate,dealer) + car = finance_data.get('car') + if Decimal(discount_amount) >= car.marked_price: messages.error(request, _("Discount amount cannot be greater than marked price")) return redirect("estimate_detail", dealer_slug=dealer_slug, pk=pk) - print(finance_data.get('cars')[0]['marked_price'] * Decimal('0.5')) - if Decimal(discount_amount) > finance_data.get('cars')[0]['marked_price'] * Decimal('0.5'): + if Decimal(discount_amount) > car.marked_price * Decimal('0.5'): messages.warning(request, _("Discount amount is greater than 50% of the marked price, proceed with caution.")) else: messages.success(request, _("Discount updated successfully")) @@ -4983,10 +4885,10 @@ def update_estimate_additionals(request, dealer_slug, pk): if form.is_valid(): estimate = get_object_or_404(EstimateModel, pk=pk) car = estimate.get_itemtxs_data()[0].first().item_model.car - car.finances.additional_services.set( + car.additional_services.set( form.cleaned_data["additional_finances"] ) - car.finances.save() + car.save() messages.success(request, "Additional Finances updated successfully") return redirect("estimate_detail", dealer_slug=dealer_slug, pk=pk) @@ -5005,10 +4907,12 @@ class SaleOrderDetail(LoginRequiredMixin, PermissionRequiredMixin, DetailView): def get_context_data(self, **kwargs): saleorder = kwargs.get("object") + dealer = self.request.dealer estimate = saleorder.estimate if estimate.get_itemtxs_data(): - calculator = CarFinanceCalculator(estimate) - finance_data = calculator.get_finance_data() + # calculator = CarFinanceCalculator(estimate) + # finance_data = calculator.get_finance_data() + finance_data = get_finance_data(estimate,dealer) kwargs["data"] = finance_data return super().get_context_data(**kwargs) @@ -5106,9 +5010,10 @@ class EstimatePreviewView(LoginRequiredMixin, PermissionRequiredMixin, DetailVie def get_context_data(self, **kwargs): estimate = kwargs.get("object") if estimate.get_itemtxs_data(): + # data = get_financial_values(estimate) - calculator = CarFinanceCalculator(estimate) - kwargs["data"] = calculator.get_finance_data() + # calculator = CarFinanceCalculator(estimate) + kwargs["data"] = get_finance_data(estimate,self.request.dealer) return super().get_context_data(**kwargs) @@ -5288,8 +5193,9 @@ class InvoiceDetailView(LoginRequiredMixin, PermissionRequiredMixin, DetailView) invoice = kwargs.get("object") if invoice.get_itemtxs_data(): - calculator = CarFinanceCalculator(invoice) - finance_data = calculator.get_finance_data() + # calculator = CarFinanceCalculator(invoice) + # finance_data = calculator.get_finance_data() + finance_data = get_finance_data(invoice,self.request.dealer) kwargs["data"] = finance_data kwargs["payments"] = JournalEntryModel.objects.filter( ledger=invoice.ledger @@ -5515,16 +5421,17 @@ def invoice_create(request, dealer_slug, pk): ledger.save() invoice.save() - calculator = CarFinanceCalculator(estimate) - finance_data = calculator.get_finance_data() + # calculator = CarFinanceCalculator(estimate) + # finance_data = calculator.get_finance_data() + finance_data = get_finance_data(estimate,dealer) + car = finance_data.get("car") invoice_itemtxs = { - i.get("item_number"): { - "unit_cost": i.get("grand_total"), + car.item_model.item_number: { + "unit_cost": finance_data.get("grand_total"), "quantity": 1, - "total_amount": i.get("grand_total"), + "total_amount": finance_data.get("grand_total"), } - for i in finance_data.get("cars") } invoice_itemtxs = invoice.migrate_itemtxs( @@ -5592,8 +5499,8 @@ class InvoicePreviewView(LoginRequiredMixin, PermissionRequiredMixin, DetailView dealer = get_object_or_404(models.Dealer, slug=self.kwargs["dealer_slug"]) invoice = kwargs.get("object") if invoice.get_itemtxs_data(): - calculator = CarFinanceCalculator(invoice) - finance_data = calculator.get_finance_data() + # calculator = CarFinanceCalculator(invoice) + finance_data = get_finance_data(invoice,dealer) kwargs["data"] = finance_data kwargs["dealer"] = dealer return super().get_context_data(**kwargs) @@ -6938,11 +6845,11 @@ class OpportunityCreateView( if self.request.is_dealer: form.fields["lead"].queryset = models.Lead.objects.filter( dealer=dealer - ) + ) elif self.request.is_staff: form.fields["lead"].queryset = models.Lead.objects.filter( dealer=dealer, staff=self.request.staff - ) + ) return form def get_success_url(self): @@ -9499,7 +9406,7 @@ def pricing_page(request, dealer_slug): else: messages.info(request,_("You already have an plan!!")) return redirect('home',dealer_slug=dealer_slug) - + @login_required @@ -10687,13 +10594,13 @@ def upload_cars(request, dealer_slug, pk=None): vendor=vendor, receiving_date=receiving_date, ) - if po_item: - models.CarFinance.objects.create( - car=car, - cost_price=po_item.item.unit_cost, - marked_price=0, - selling_price=0, - ) + # if po_item: #TODO:update + # models.CarFinance.objects.create( + # car=car, + # cost_price=po_item.item.unit_cost, + # marked_price=0, + # selling_price=0, + # ) car.add_colors(exterior=exterior, interior=interior) cars_created += 1 logger.debug( @@ -10765,11 +10672,8 @@ def bulk_update_car_price(request): else: for car_pk in cars: car = models.Car.objects.get(pk=car_pk) - if not hasattr(car, "finances"): - models.CarFinance.objects.create(car=car, cost_price=Decimal(price)) - else: - car.finances.cost_price = Decimal(price) - car.finances.save() + car.cost_price = Decimal(price) + car.save() messages.success(request, "Price updated successfully") response = HttpResponse() @@ -10979,11 +10883,11 @@ def car_sale_report_csv_export(request,dealer_slug): car.stock_type, car.created_at.strftime("%Y-%m-%d %H:%M:%S") if car.created_at else '', car.sold_date.strftime("%Y-%m-%d %H:%M:%S") if car.sold_date else '', - car.finances.cost_price, - car.finances.marked_price, - car.finances.discount_amount, - car.finances.selling_price, - car.finances.vat_amount, + car.cost_price, + car.marked_price, + car.discount_amount, + car.selling_price, + car.vat_amount, # TODO : check later car.item_model.invoicemodel_set.first().invoice_number ]) @@ -11232,4 +11136,8 @@ def ticket_update(request, ticket_id): return render(request, 'support/ticket_update.html', { 'ticket': ticket, 'form': form - }) \ No newline at end of file + }) + + +class ChartOfAccountModelListView(ChartOfAccountModelListViewBase): + template_name = 'chart_of_accounts/coa_list.html' \ No newline at end of file diff --git a/templates/chart_of_accounts/coa_list.html b/templates/chart_of_accounts/coa_list.html new file mode 100644 index 00000000..5ca6639c --- /dev/null +++ b/templates/chart_of_accounts/coa_list.html @@ -0,0 +1,38 @@ +{% extends 'base.html' %} +{% load i18n %} +{% load static %} +{% load icon from django_ledger %} + +{% block content %} +
+
+
+
+ + + {% if not inactive %} + + {% trans 'Show Inactive' %} + + {% else %} + + {% trans 'Show Active' %} + + {% endif %} +
+
+
+ {% for coa_model in coa_list %} +
+ {% include 'chart_of_accounts/includes/coa_card.html' with coa_model=coa_model %} +
+ {% endfor %} +
+
+
+{% endblock %} \ No newline at end of file diff --git a/templates/chart_of_accounts/includes/coa_card.html b/templates/chart_of_accounts/includes/coa_card.html new file mode 100644 index 00000000..e3d56ecd --- /dev/null +++ b/templates/chart_of_accounts/includes/coa_card.html @@ -0,0 +1,116 @@ +{% load django_ledger %} +{% load i18n %} +{% now "Y" as current_year %} + +
+
+
+
+ +
+
+ {{ coa_model.name }} + {% if coa_model.is_default %} + {% trans 'DEFAULT' %} + {% endif %} +
+
+
+ +
+
+
+
+ {% trans 'Entity Default' %}: + {% if coa_model.is_default %} + + {% else %} + + {% endif %} +
+ +
+ {% trans 'Is Active' %}: + {% if coa_model.is_active %} + + {% else %} + + {% endif %} +
+ +
+ CoA ID: + {{ coa_model.slug }} +
+
+ +
+
+ {% trans 'Total Accounts' %}: + {{ coa_model.accountmodel_total__count }} +
+ +
+ {% trans 'Active Accounts' %}: + {{ coa_model.accountmodel_active__count }} +
+ +
+ {% trans 'Locked Accounts' %}: + {{ coa_model.accountmodel_locked__count }} +
+
+
+ +
+ +
+
+ + + {% trans 'Created' %}: + {{ coa_model.created|date }} + +
+
+ + + {% trans 'Updated' %}: + {{ coa_model.created|timesince }} {% trans 'ago' %} + +
+
+
+ + +
\ No newline at end of file diff --git a/templates/crm/opportunities/opportunity_detail.html b/templates/crm/opportunities/opportunity_detail.html index 8de794ef..143670f0 100644 --- a/templates/crm/opportunities/opportunity_detail.html +++ b/templates/crm/opportunities/opportunity_detail.html @@ -70,9 +70,9 @@ {% endif %}
- {% if opportunity.car.finances %} + {% if opportunity.car.marked_price %}
- {{ opportunity.car.finances.total }} + {{ opportunity.car.total }} # TODO : check later
{% endif %}
diff --git a/templates/crm/opportunities/opportunity_list copy.html b/templates/crm/opportunities/opportunity_list copy.html index 3c9133dd..7250543d 100644 --- a/templates/crm/opportunities/opportunity_list copy.html +++ b/templates/crm/opportunities/opportunity_list copy.html @@ -75,7 +75,7 @@ height: 12px; width: 12px">{{ opportunity.get_stage_display }}

-

{{ opportunity.car.finances.total }}

+

{{ opportunity.car.total }}

# TODO : check later
@@ -107,7 +107,7 @@ :

- {{ opportunity.car.finances.total }} + {{ opportunity.car.total }}# TODO : check later

diff --git a/templates/header.html b/templates/header.html index cec75ee8..f1d20ad7 100644 --- a/templates/header.html +++ b/templates/header.html @@ -217,7 +217,7 @@
  • {% trans 'Financials' %}
  • {% if perms.django_ledger.view_accountmodel %}
    {% endif %} - + {% if perms.django_ledger.can_view_reports %} - +
    - + {% endif %} - - + +
    {% endif %} - + {# --- Support & Contact Section (New) --- #} diff --git a/templates/inventory/car_detail.html b/templates/inventory/car_detail.html index f6499423..fc02cefa 100644 --- a/templates/inventory/car_detail.html +++ b/templates/inventory/car_detail.html @@ -20,11 +20,11 @@