diff --git a/.gitignore b/.gitignore index 3bc4a7b9..3dc9a18c 100644 --- a/.gitignore +++ b/.gitignore @@ -17,7 +17,7 @@ media .idea/**/usage.statistics.xml .idea/**/dictionaries .idea/**/shelf - +Makefile # AWS User-specific .idea/**/aws.xml diff --git a/inventory/forms.py b/inventory/forms.py index 8123f86c..5ed86b8d 100644 --- a/inventory/forms.py +++ b/inventory/forms.py @@ -28,6 +28,10 @@ import django_tables2 as tables from django.forms import formset_factory +class AdditionalServiceForm(forms.ModelForm): + class Meta: + model = AdditionalServices + fields = ['name', 'price','description','taxable', 'uom'] class PaymentForm(forms.ModelForm): class Meta: diff --git a/inventory/migrations/0031_remove_salequotation_entity.py b/inventory/migrations/0031_remove_salequotation_entity.py new file mode 100644 index 00000000..715069b0 --- /dev/null +++ b/inventory/migrations/0031_remove_salequotation_entity.py @@ -0,0 +1,17 @@ +# Generated by Django 4.2.17 on 2024-12-26 07:58 + +from django.db import migrations + + +class Migration(migrations.Migration): + + dependencies = [ + ('inventory', '0030_account'), + ] + + operations = [ + migrations.RemoveField( + model_name='salequotation', + name='entity', + ), + ] diff --git a/inventory/migrations/0032_remove_additionalservices_vatable_and_more.py b/inventory/migrations/0032_remove_additionalservices_vatable_and_more.py new file mode 100644 index 00000000..e97bfde8 --- /dev/null +++ b/inventory/migrations/0032_remove_additionalservices_vatable_and_more.py @@ -0,0 +1,22 @@ +# Generated by Django 4.2.17 on 2024-12-26 08:02 + +from django.db import migrations, models + + +class Migration(migrations.Migration): + + dependencies = [ + ('inventory', '0031_remove_salequotation_entity'), + ] + + operations = [ + migrations.RemoveField( + model_name='additionalservices', + name='vatable', + ), + migrations.AddField( + model_name='additionalservices', + name='taxable', + field=models.BooleanField(default=False, verbose_name='taxable'), + ), + ] diff --git a/inventory/models.py b/inventory/models.py index c1b5f4df..0a7468e3 100644 --- a/inventory/models.py +++ b/inventory/models.py @@ -148,6 +148,9 @@ class AdditionalServices(models.Model, LocalizedNameMixin): arabic_name = models.CharField(max_length=255, verbose_name=_("Arabic Name")) description = models.TextField(verbose_name=_("Description")) price = models.DecimalField(max_digits=14, decimal_places=2, verbose_name=_("Price")) + taxable = models.BooleanField(default=False, verbose_name=_("taxable")) + uom = models.CharField(max_length=10, choices=UNIT_CHOICES, verbose_name=_("Unit of Measurement")) + dealer = models.ForeignKey("Dealer", on_delete=models.CASCADE, verbose_name=_("Dealer")) class Meta: verbose_name = _("Additional Services") diff --git a/inventory/signals.py b/inventory/signals.py index 85279d00..54ed0a11 100644 --- a/inventory/signals.py +++ b/inventory/signals.py @@ -5,10 +5,12 @@ from django.dispatch import receiver from django.utils import timezone from django_ledger.models import EntityModel from django.utils.translation import gettext_lazy as _ - - +from django.contrib.auth import get_user_model +from django_ledger.io import roles +from django_ledger.models import EntityModel,AccountModel,ItemModel,ItemModelAbstract,UnitOfMeasureModel from . import models +User = get_user_model() # @receiver(post_save, sender=models.SaleQuotation) # def link_quotation_to_entity(sender, instance, created, **kwargs): @@ -22,6 +24,25 @@ from . import models # user = instance.user # if user: # user.delete() + +@receiver(post_save, sender=User) +def create_dealer(instance, created, *args, **kwargs): + if created: + models.Dealer.objects.create(user=instance,name=instance.username,email=instance.email) + +@receiver(post_save, sender=models.Dealer) +def create_user_account(sender, instance, created, **kwargs): + if created: + if instance.dealer_type != "Owner": + user = User.objects.create_user( + username=instance.name, + email=instance.email, + ) + user.set_password("Tenhal@123") + user.save() + instance.user = user + instance.save() + @receiver(post_save, sender=models.Car) def create_car_location(sender, instance, created, **kwargs): """ @@ -62,71 +83,106 @@ def update_car_status_on_reservation_delete(sender, instance, **kwargs): # Create Entity @receiver(post_save, sender=models.Dealer) def create_ledger_entity(sender, instance, created, **kwargs): - if created: - entity, created = EntityModel.objects.get_or_create( - name=instance.get_root_dealer.name, - admin=instance.get_root_dealer.user, - # address_1=instance.address, - accrual_method=False, - fy_start_month=1, - depth=0, - ) - print(entity) - if created: - default_coa = entity.create_chart_of_accounts(assign_as_default=True, - commit=True, - coa_name=_("Chart of Accounts")) - if default_coa: - entity.populate_default_coa(activate_accounts=True, coa_model=default_coa) - print(f"Ledger entity created for Dealer: {instance.name}") + if created: + root_dealer = instance.get_root_dealer + if not root_dealer.entity: + entity_name = f"{root_dealer.name}-{root_dealer.joined_at.date()}" + entity = EntityModel.create_entity( + name=entity_name, + admin=root_dealer.user, + use_accrual_method=False, + fy_start_month=1, + ) - # entity.create_account( - # coa_model=coa, - # code=1010, - # role='asset_ca_cash', - # name=_('Cash'), - # balance_type="debit", - # ) - # entity.create_account( - # coa_model=coa, - # code=1100, - # role='asset_ca_recv', - # name=_('Accounts Receivable'), - # balance_type="debit", - # ) - # entity.create_account( - # coa_model=coa, - # code=1200, - # role='asset_ca_inv', - # name=_('Inventory'), - # balance_type="debit", - # active=True) - # - # entity.create_account( - # coa_model=coa, - # code=2010, - # role='lia_cl_acc_payable', - # name=_('Accounts Payable'), - # balance_type="credit", - # active=True) - # - # entity.create_account( - # coa_model=coa, - # code=4010, - # role='in_operational', - # name=_('Sales Income'), - # balance_type="credit", - # active=True) - # - # entity.create_account( - # coa_model=coa, - # code=5010, - # role='cogs_regular', - # name=_('Cost of Goods Sold'), - # balance_type="debit", - # active=True) + if entity: + instance.entity = entity + instance.save() + coa = entity.create_chart_of_accounts( + assign_as_default=True, commit=True, coa_name=_(f"{entity_name}-COA") + ) + if coa: + # entity.populate_default_coa(activate_accounts=True, coa_model=coa) + print(f"Ledger entity created for Dealer: {instance.name}") + # Create Cash Account + entity.create_account( + coa_model=coa, + code="1010", + role=roles.ASSET_CA_CASH, + name=_("Cash"), + balance_type="debit", + active=True, + ) + # Create Accounts Receivable Account + entity.create_account( + coa_model=coa, + code="1020", + role=roles.ASSET_CA_RECEIVABLES, + name=_("Accounts Receivable"), + balance_type="debit", + active=True, + ) + + # Create Inventory Account + entity.create_account( + coa_model=coa, + code="1030", + role=roles.ASSET_CA_INVENTORY, + name=_("Inventory"), + balance_type="debit", + active=True, + ) + + # Create Accounts Payable Account + entity.create_account( + coa_model=coa, + code="2010", + role=roles.LIABILITY_CL_ACC_PAYABLE, + name=_("Accounts Payable"), + balance_type="credit", + active=True, + ) + + # Create Sales Revenue Account + entity.create_account( + coa_model=coa, + code="4010", + role=roles.INCOME_OPERATIONAL, + name=_("Sales Revenue"), + balance_type="credit", + active=True, + ) + + # Create Cost of Goods Sold Account + entity.create_account( + coa_model=coa, + code="5010", + role=roles.COGS, + name=_("Cost of Goods Sold"), + balance_type="debit", + active=True, + ) + + # Create Rent Expense Account + entity.create_account( + coa_model=coa, + code="6010", + role=roles.EXPENSE_OPERATIONAL, + name=_("Rent Expense"), + balance_type="debit", + active=True, + ) + + # Create Utilities Expense Account + entity.create_account( + coa_model=coa, + code="6020", + role=roles.EXPENSE_OPERATIONAL, + name=_("Utilities Expense"), + balance_type="debit", + active=True, + ) # uom_name = _("Unit") # unit_abbr = _("U") @@ -175,7 +231,7 @@ def create_customer(sender, instance, created, **kwargs): "sales_tax_rate": 0.15, "active": True, "hidden": False, - "additional_info": {} + "additional_info": {}, } ) @@ -183,65 +239,31 @@ def create_customer(sender, instance, created, **kwargs): # Create Item -# @receiver(post_save, sender=models.Car) -# def create_item_model(sender, instance, created, **kwargs): -# item_name = f"{instance.year} - {instance.id_car_make} - {instance.id_car_model} - {instance.id_car_trim}" -# uom_name = _("Car") -# unit_abbr = _("C") -# -# uom, uom_created = UnitOfMeasureModel.objects.get_or_create( -# name=uom_name, -# unit_abbr=unit_abbr -# ) -# -# if uom_created: -# print(f"UOM created: {uom_name}") -# else: -# print(f"Using existing UOM: {uom_name}") -# -# entity = EntityModel.objects.filter(name=instance.dealer.name).first() -# -# inventory_account = AccountModel.objects.first() -# cogs_account = AccountModel.objects.first() -# earnings_account = AccountModel.objects.first() -# -# entity.create_item_product( -# item_name=item_name, -# item_role=ItemModelAbstract.ITEM_ROLE_PRODUCT, -# item_type=ItemModelAbstract.ITEM_TYPE_MATERIAL, -# item_id=instance.vin, -# sold_as_unit=True, -# inventory_received=1.00, -# inventory_received_value=0.00, -# inventory_account=inventory_account, -# for_inventory=True,) -# -# item = ItemModel.objects.create( -# entity=entity, -# uom=uom, -# name=item_name, -# item_role=ItemModelAbstract.ITEM_ROLE_INVENTORY, -# item_type=ItemModelAbstract.ITEM_TYPE_MATERIAL, -# item_id=instance.vin, -# sold_as_unit=True, -# inventory_received=1.00, -# inventory_received_value=0.00, -# inventory_account=inventory_account, -# for_inventory=True, -# is_product_or_service=True, -# cogs_account=cogs_account, -# earnings_account=earnings_account, -# is_active=True, -# additional_info={ -# "remarks": instance.remarks, -# "status": instance.status, -# "stock_type": instance.stock_type, -# "mileage": instance.mileage, -# }, -# ) -# -# print(f"ItemModel {'created' if created else 'updated'} for Car: {item.name}") -# +@receiver(post_save, sender=models.Car) +def create_item_model(sender, instance, created, **kwargs): + item_name = f"{instance.year} - {instance.id_car_make} - {instance.id_car_model} - {instance.id_car_trim}" + dealer = instance.dealer + entity = dealer.entity + + if not entity: + return + + uom_name = _("Car") + unit_abbr = _("C") + uom = entity.get_uom_all().filter(name=uom_name, unit_abbr=unit_abbr).first() + if not uom: + uom = entity.create_uom( + name=uom_name, + unit_abbr=unit_abbr + ) + + entity.create_item_product( + name=item_name, + uom_model=uom, + item_type=ItemModel.ITEM_TYPE_MATERIAL) + + print(f"ItemModel for Car:") + # # # update price - CarFinance # @receiver(post_save, sender=CarFinance) diff --git a/inventory/utilities/financials.py b/inventory/utilities/financials.py index be0bfa25..ffbb56de 100644 --- a/inventory/utilities/financials.py +++ b/inventory/utilities/financials.py @@ -23,22 +23,12 @@ def get_financial_value(name,vat=False): def get_total_financials(instance,vat=False): total = 0 - if instance.additional_services.exists(): - total = sum(x.price for x in instance.additional_services.all()) + instance.selling_price - + if instance.additional_services.count() != 0: + total = sum(x.price for x in instance.additional_services) + instance.selling_price if vat: total = (total * settings.VAT_RATE).quantize(Decimal('0.01')) + total - # price_after_discount = get_financial_value(instance,"selling_price",vat) - get_financial_value(instance,"discount_amount",vat) - # subtotal = ( - # price_after_discount + - # get_financial_value("registration_fee") + - # get_financial_value("administration_fee",vat) + - # get_financial_value("transportation_fee",vat) + - # get_financial_value("custom_card_fee",vat)) - return total - + def get_total(instance): - total = get_total_financials(instance,vat=True) - # total_vat = get_total_financials(instance,vat=True) + total = get_total_financials(instance,vat=True) return total \ No newline at end of file diff --git a/inventory/views.py b/inventory/views.py index 44d01d4f..8df32453 100644 --- a/inventory/views.py +++ b/inventory/views.py @@ -759,8 +759,8 @@ class QuotationCreateView(LoginRequiredMixin, PermissionRequiredMixin, CreateVie permission_required = ("inventory.add_salequotation",) def form_valid(self, form): - dealer = self.request.user.dealer.get_root_dealer - form.instance.dealer = dealer + dealer = self.request.user.dealer.get_root_dealer + form.instance.dealer = dealer quotation = form.save() selected_cars = form.cleaned_data.get("cars") for car in selected_cars: @@ -810,12 +810,12 @@ class QuotationDetailView(LoginRequiredMixin, PermissionRequiredMixin, DetailVie def generate_invoice(request, pk): quotation = get_object_or_404(models.SaleQuotation, pk=pk) dealer = request.user.dealer.get_root_dealer + entity = dealer.entity if not quotation.is_approved: messages.error( request, "Quotation must be approved before converting to an invoice." ) else: - entity = dealer.entity coa_qs, coa_map = entity.get_all_coa_accounts() cash_account = coa_qs.first().get_coa_accounts().filter(name="Cash") recivable_account = coa_qs.first().get_coa_accounts().filter(name="Accounts Receivable") @@ -956,10 +956,11 @@ def generate_invoice(request, pk): @login_required def post_quotation(request, pk): qoutation = get_object_or_404(models.SaleQuotation, pk=pk) + dealer = request.user.dealer.get_root_dealer + entity = dealer.entity if qoutation.posted: messages.error(request, "Quotation is already posted") - return redirect("quotation_detail", pk=pk) - entity = qoutation.entity + return redirect("quotation_detail", pk=pk) coa_qs, coa_map = entity.get_all_coa_accounts() cash_account = coa_qs.first().get_coa_accounts().filter(name="Cash") recivable_account = coa_qs.first().get_coa_accounts().filter(name="Accounts Receivable") @@ -1011,7 +1012,8 @@ def post_quotation(request, pk): def mark_quotation(request, pk): qoutation = get_object_or_404(models.SaleQuotation, pk=pk) status = request.GET.get("status") - entity = qoutation.entity + dealer = request.user.dealer.get_root_dealer + entity = dealer.entity date = datetime.datetime.now() customer = entity.get_customers().filter(customer_name=qoutation.customer.get_full_name).first() invoice_model = entity.get_invoices().filter(customer=customer) @@ -1355,9 +1357,9 @@ def download_quotation_pdf(request, quotation_id): @login_required def invoice_detail(request,pk): - quotation = get_object_or_404(models.SaleQuotation, pk=pk) - dealer = request.user.dealer.get_root_dealer - entity = dealer.entity + quotation = get_object_or_404(models.SaleQuotation, pk=pk) + dealer = request.user.dealer.get_root_dealer + entity = dealer.entity customer = entity.get_customers().filter(customer_name=quotation.customer.get_full_name).first() invoice_model = entity.get_invoices() @@ -1366,9 +1368,9 @@ def invoice_detail(request,pk): return redirect('quotation_detail', pk=pk) @login_required def payment_invoice(request,pk): - quotation = get_object_or_404(models.SaleQuotation, pk=pk) - dealer = request.user.dealer.get_root_dealer - entity = dealer.entity + quotation = get_object_or_404(models.SaleQuotation, pk=pk) + dealer = request.user.dealer.get_root_dealer + entity = dealer.entity customer = entity.get_customers().filter(customer_name=quotation.customer.get_full_name).first() invoice_model = entity.get_invoices() invoice = invoice_model.filter(customer=customer,date_draft=quotation.date_draft).first() @@ -1403,7 +1405,9 @@ def payment_create(request, pk): if form.is_valid(): form.instance.quotation = quotation insatnce = form.save() - entity = dealer.entity + + dealer = request.user.dealer.get_root_dealer + entity = dealer.entity customer = entity.get_customers().filter(customer_name=quotation.customer.get_full_name).first() coa_qs, coa_map = entity.get_all_coa_accounts() cash_account = coa_qs.first().get_coa_accounts().filter(name="Cash") diff --git a/templates/base.html b/templates/base.html index 0f1dda53..27c7b2ab 100644 --- a/templates/base.html +++ b/templates/base.html @@ -41,6 +41,7 @@ + {% if LANGUAGE_CODE == 'ar' %}