From 90518409043b7106339998add2cbb8808a0e6250 Mon Sep 17 00:00:00 2001 From: gitea Date: Tue, 24 Dec 2024 13:49:36 +0000 Subject: [PATCH] update --- car_inventory/settings.py | 16 +- ...4_remove_additionalservices_arabic_name.py | 17 ++ .../0005_additionalservices_arabic_name.py | 19 ++ ...6_remove_additionalservices_arabic_name.py | 17 ++ .../0007_additionalservices_arabic_name.py | 19 ++ .../migrations/0008_salequotation_status.py | 18 ++ ...09_salequotation_date_approved_and_more.py | 48 +++ ...er_salequotation_date_approved_and_more.py | 43 +++ .../migrations/0011_salequotation_posted.py | 18 ++ .../0012_salequotation_payment_id.py | 18 ++ .../migrations/0013_salequotation_is_paid.py | 18 ++ inventory/models.py | 23 +- inventory/signals.py | 26 +- inventory/urls.py | 4 +- inventory/views.py | 287 +++++++++++++++--- templates/sales/invoice/invoice_detail.html | 77 +++++ templates/sales/quotation_detail.html | 36 ++- templates/sales/quotation_list.html | 12 +- 18 files changed, 622 insertions(+), 94 deletions(-) create mode 100644 inventory/migrations/0004_remove_additionalservices_arabic_name.py create mode 100644 inventory/migrations/0005_additionalservices_arabic_name.py create mode 100644 inventory/migrations/0006_remove_additionalservices_arabic_name.py create mode 100644 inventory/migrations/0007_additionalservices_arabic_name.py create mode 100644 inventory/migrations/0008_salequotation_status.py create mode 100644 inventory/migrations/0009_salequotation_date_approved_and_more.py create mode 100644 inventory/migrations/0010_alter_salequotation_date_approved_and_more.py create mode 100644 inventory/migrations/0011_salequotation_posted.py create mode 100644 inventory/migrations/0012_salequotation_payment_id.py create mode 100644 inventory/migrations/0013_salequotation_is_paid.py create mode 100644 templates/sales/invoice/invoice_detail.html diff --git a/car_inventory/settings.py b/car_inventory/settings.py index f834f341..f955b516 100644 --- a/car_inventory/settings.py +++ b/car_inventory/settings.py @@ -107,13 +107,23 @@ WSGI_APPLICATION = 'car_inventory.wsgi.application' DATABASES = { "default": { "ENGINE": "django_prometheus.db.backends.postgresql", - "NAME": "murad_haikal", - "USER": "f95166", - "PASSWORD": "Kfsh&rc9788", + "NAME": "haikal", + "USER": "haikal", + "PASSWORD": "haikal", "HOST": "localhost", "PORT": 5432, } } +# DATABASES = { +# "default": { +# "ENGINE": "django_prometheus.db.backends.postgresql", +# "NAME": "murad_haikal", +# "USER": "f95166", +# "PASSWORD": "Kfsh&rc9788", +# "HOST": "localhost", +# "PORT": 5432, +# } +# } # Password validation # https://docs.djangoproject.com/en/5.0/ref/settings/#auth-password-validators diff --git a/inventory/migrations/0004_remove_additionalservices_arabic_name.py b/inventory/migrations/0004_remove_additionalservices_arabic_name.py new file mode 100644 index 00000000..622ecaca --- /dev/null +++ b/inventory/migrations/0004_remove_additionalservices_arabic_name.py @@ -0,0 +1,17 @@ +# Generated by Django 4.2.17 on 2024-12-24 08:07 + +from django.db import migrations + + +class Migration(migrations.Migration): + + dependencies = [ + ('inventory', '0003_remove_additionalservices_display_name_and_more'), + ] + + operations = [ + migrations.RemoveField( + model_name='additionalservices', + name='arabic_name', + ), + ] diff --git a/inventory/migrations/0005_additionalservices_arabic_name.py b/inventory/migrations/0005_additionalservices_arabic_name.py new file mode 100644 index 00000000..b9056578 --- /dev/null +++ b/inventory/migrations/0005_additionalservices_arabic_name.py @@ -0,0 +1,19 @@ +# Generated by Django 4.2.17 on 2024-12-24 08:09 + +from django.db import migrations, models + + +class Migration(migrations.Migration): + + dependencies = [ + ('inventory', '0004_remove_additionalservices_arabic_name'), + ] + + operations = [ + migrations.AddField( + model_name='additionalservices', + name='arabic_name', + field=models.CharField(default='-', max_length=255, verbose_name='Arabic Name'), + preserve_default=False, + ), + ] diff --git a/inventory/migrations/0006_remove_additionalservices_arabic_name.py b/inventory/migrations/0006_remove_additionalservices_arabic_name.py new file mode 100644 index 00000000..a2ac801f --- /dev/null +++ b/inventory/migrations/0006_remove_additionalservices_arabic_name.py @@ -0,0 +1,17 @@ +# Generated by Django 4.2.17 on 2024-12-24 08:10 + +from django.db import migrations + + +class Migration(migrations.Migration): + + dependencies = [ + ('inventory', '0005_additionalservices_arabic_name'), + ] + + operations = [ + migrations.RemoveField( + model_name='additionalservices', + name='arabic_name', + ), + ] diff --git a/inventory/migrations/0007_additionalservices_arabic_name.py b/inventory/migrations/0007_additionalservices_arabic_name.py new file mode 100644 index 00000000..013a875e --- /dev/null +++ b/inventory/migrations/0007_additionalservices_arabic_name.py @@ -0,0 +1,19 @@ +# Generated by Django 4.2.17 on 2024-12-24 08:11 + +from django.db import migrations, models + + +class Migration(migrations.Migration): + + dependencies = [ + ('inventory', '0006_remove_additionalservices_arabic_name'), + ] + + operations = [ + migrations.AddField( + model_name='additionalservices', + name='arabic_name', + field=models.CharField(default='-', max_length=255, verbose_name='Arabic Name'), + preserve_default=False, + ), + ] diff --git a/inventory/migrations/0008_salequotation_status.py b/inventory/migrations/0008_salequotation_status.py new file mode 100644 index 00000000..cf5450ac --- /dev/null +++ b/inventory/migrations/0008_salequotation_status.py @@ -0,0 +1,18 @@ +# Generated by Django 4.2.17 on 2024-12-24 08:35 + +from django.db import migrations, models + + +class Migration(migrations.Migration): + + dependencies = [ + ('inventory', '0007_additionalservices_arabic_name'), + ] + + operations = [ + migrations.AddField( + model_name='salequotation', + name='status', + field=models.CharField(choices=[('DRAFT', 'Draft'), ('CONFIRMED', 'Confirmed'), ('CANCELED', 'Canceled')], default='DRAFT', max_length=10, verbose_name='Status'), + ), + ] diff --git a/inventory/migrations/0009_salequotation_date_approved_and_more.py b/inventory/migrations/0009_salequotation_date_approved_and_more.py new file mode 100644 index 00000000..8b31c0e9 --- /dev/null +++ b/inventory/migrations/0009_salequotation_date_approved_and_more.py @@ -0,0 +1,48 @@ +# Generated by Django 4.2.17 on 2024-12-24 09:48 + +from django.db import migrations, models + + +class Migration(migrations.Migration): + + dependencies = [ + ('inventory', '0008_salequotation_status'), + ] + + operations = [ + migrations.AddField( + model_name='salequotation', + name='date_approved', + field=models.DateField(blank=True, null=True, verbose_name='Approved Date'), + ), + migrations.AddField( + model_name='salequotation', + name='date_canceled', + field=models.DateField(blank=True, null=True, verbose_name='Canceled Date'), + ), + migrations.AddField( + model_name='salequotation', + name='date_draft', + field=models.DateField(blank=True, null=True, verbose_name='Draft Date'), + ), + migrations.AddField( + model_name='salequotation', + name='date_in_review', + field=models.DateField(blank=True, null=True, verbose_name='In Review Date'), + ), + migrations.AddField( + model_name='salequotation', + name='date_paid', + field=models.DateField(blank=True, null=True, verbose_name='Paid Date'), + ), + migrations.AddField( + model_name='salequotation', + name='date_void', + field=models.DateField(blank=True, null=True, verbose_name='Void Date'), + ), + migrations.AlterField( + model_name='salequotation', + name='status', + field=models.CharField(choices=[('Draft', 'Draft'), ('Approved', 'Approved'), ('In Review', 'In Review'), ('Paid', 'Paid')], default='Draft', max_length=10, verbose_name='Status'), + ), + ] diff --git a/inventory/migrations/0010_alter_salequotation_date_approved_and_more.py b/inventory/migrations/0010_alter_salequotation_date_approved_and_more.py new file mode 100644 index 00000000..6a994ae2 --- /dev/null +++ b/inventory/migrations/0010_alter_salequotation_date_approved_and_more.py @@ -0,0 +1,43 @@ +# Generated by Django 4.2.17 on 2024-12-24 09:49 + +from django.db import migrations, models + + +class Migration(migrations.Migration): + + dependencies = [ + ('inventory', '0009_salequotation_date_approved_and_more'), + ] + + operations = [ + migrations.AlterField( + model_name='salequotation', + name='date_approved', + field=models.DateTimeField(blank=True, null=True, verbose_name='Approved Date'), + ), + migrations.AlterField( + model_name='salequotation', + name='date_canceled', + field=models.DateTimeField(blank=True, null=True, verbose_name='Canceled Date'), + ), + migrations.AlterField( + model_name='salequotation', + name='date_draft', + field=models.DateTimeField(blank=True, null=True, verbose_name='Draft Date'), + ), + migrations.AlterField( + model_name='salequotation', + name='date_in_review', + field=models.DateTimeField(blank=True, null=True, verbose_name='In Review Date'), + ), + migrations.AlterField( + model_name='salequotation', + name='date_paid', + field=models.DateTimeField(blank=True, null=True, verbose_name='Paid Date'), + ), + migrations.AlterField( + model_name='salequotation', + name='date_void', + field=models.DateTimeField(blank=True, null=True, verbose_name='Void Date'), + ), + ] diff --git a/inventory/migrations/0011_salequotation_posted.py b/inventory/migrations/0011_salequotation_posted.py new file mode 100644 index 00000000..450c6708 --- /dev/null +++ b/inventory/migrations/0011_salequotation_posted.py @@ -0,0 +1,18 @@ +# Generated by Django 4.2.17 on 2024-12-24 10:47 + +from django.db import migrations, models + + +class Migration(migrations.Migration): + + dependencies = [ + ('inventory', '0010_alter_salequotation_date_approved_and_more'), + ] + + operations = [ + migrations.AddField( + model_name='salequotation', + name='posted', + field=models.BooleanField(default=False), + ), + ] diff --git a/inventory/migrations/0012_salequotation_payment_id.py b/inventory/migrations/0012_salequotation_payment_id.py new file mode 100644 index 00000000..24758c73 --- /dev/null +++ b/inventory/migrations/0012_salequotation_payment_id.py @@ -0,0 +1,18 @@ +# Generated by Django 4.2.17 on 2024-12-24 13:46 + +from django.db import migrations, models + + +class Migration(migrations.Migration): + + dependencies = [ + ('inventory', '0011_salequotation_posted'), + ] + + operations = [ + migrations.AddField( + model_name='salequotation', + name='payment_id', + field=models.CharField(blank=True, max_length=10, null=True, verbose_name='Payment ID'), + ), + ] diff --git a/inventory/migrations/0013_salequotation_is_paid.py b/inventory/migrations/0013_salequotation_is_paid.py new file mode 100644 index 00000000..c3f313d5 --- /dev/null +++ b/inventory/migrations/0013_salequotation_is_paid.py @@ -0,0 +1,18 @@ +# Generated by Django 4.2.17 on 2024-12-24 13:46 + +from django.db import migrations, models + + +class Migration(migrations.Migration): + + dependencies = [ + ('inventory', '0012_salequotation_payment_id'), + ] + + operations = [ + migrations.AddField( + model_name='salequotation', + name='is_paid', + field=models.BooleanField(default=False), + ), + ] diff --git a/inventory/models.py b/inventory/models.py index b06f04db..e66afb3c 100644 --- a/inventory/models.py +++ b/inventory/models.py @@ -645,9 +645,10 @@ class SaleQuotation(models.Model): quotation_number = models.CharField(max_length=10, unique=True) STATUS_CHOICES = [ - ("DRAFT", _("Draft")), - ("CONFIRMED", _("Confirmed")), - ("CANCELED", _("Canceled")), + ("Draft", _("Draft")), + ("Approved", _("Approved")), + ("In Review", _("In Review")), + ("Paid", _("Paid")), ] dealer = models.ForeignKey( Dealer, on_delete=models.CASCADE, related_name="sales", null=True @@ -667,11 +668,21 @@ class SaleQuotation(models.Model): ) remarks = models.TextField(blank=True, null=True, verbose_name=_("Remarks")) is_approved = models.BooleanField(default=False) - # status = models.CharField( - # max_length=10, choices=STATUS_CHOICES, default="DRAFT", verbose_name=_("Status") - # ) + status = models.CharField( + max_length=10, choices=STATUS_CHOICES, default="Draft", verbose_name=_("Status") + ) created_at = models.DateTimeField(auto_now_add=True, verbose_name=_("Created At")) updated_at = models.DateTimeField(auto_now=True, verbose_name=_("Updated At")) + + posted = models.BooleanField(default=False) + payment_id = models.CharField(max_length=10, null=True, blank=True, verbose_name=_("Payment ID")) + is_paid = models.BooleanField(default=False) + date_draft = models.DateTimeField(null=True, blank=True, verbose_name=_('Draft Date')) + date_in_review = models.DateTimeField(null=True, blank=True, verbose_name=_('In Review Date')) + date_approved = models.DateTimeField(null=True, blank=True, verbose_name=_('Approved Date')) + date_paid = models.DateTimeField(null=True, blank=True, verbose_name=_('Paid Date')) + date_void = models.DateTimeField(null=True, blank=True, verbose_name=_('Void Date')) + date_canceled = models.DateTimeField(null=True, blank=True, verbose_name=_('Canceled Date')) @property def total_quantity(self): diff --git a/inventory/signals.py b/inventory/signals.py index ea00a82e..7a8241b5 100644 --- a/inventory/signals.py +++ b/inventory/signals.py @@ -2,7 +2,7 @@ from random import randint from django.db.models.signals import post_save, post_delete, pre_delete from django.dispatch import receiver -<<<<<<< HEAD +from django_ledger.views import JournalEntryCreateView from django_ledger.models import ( EntityModel, VendorModel, @@ -13,9 +13,6 @@ from django_ledger.models import ( ItemModel ) from django.contrib.auth import get_user_model -======= -from django_ledger.models import EntityModel ->>>>>>> d57702ea7abaa3ad61315b9d593fbd8c32e314e2 from django.utils.translation import gettext_lazy as _ @@ -126,7 +123,6 @@ def update_car_status_on_reservation_delete(sender, instance, **kwargs): @receiver(post_save, sender=models.Dealer) def create_ledger_entity(sender, instance, created, **kwargs): if created: -<<<<<<< HEAD # Group.objects.get(name=instance.dealer_type.lower()).user_set.add(instance.user) try: entity, created = EntityModel.objects.get_or_create( @@ -146,24 +142,6 @@ def create_ledger_entity(sender, instance, created, **kwargs): activate_accounts=True, coa_model=default_coa ) print(f"Ledger entity created for Dealer: {instance.name}") -======= - 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}") ->>>>>>> d57702ea7abaa3ad61315b9d593fbd8c32e314e2 entity.create_account( coa_model=default_coa, @@ -228,7 +206,7 @@ def create_ledger_vendor(sender, instance, created, **kwargs): entity = EntityModel.objects.filter(name=instance.dealer.name).first() entity.create_vendor( - name=instance.name, + vendor_name=instance.name, vendor_number=instance.crn, address_1=instance.address, phone=instance.phone_number, diff --git a/inventory/urls.py b/inventory/urls.py index 64aa7450..fbc16ee6 100644 --- a/inventory/urls.py +++ b/inventory/urls.py @@ -78,7 +78,9 @@ urlpatterns = [ path('sales/orders/detail//', views.SalesOrderDetailView.as_view(), name='order_detail'), path('quotation//pdf/', views.download_quotation_pdf, name='quotation_pdf'), path('generate_invoice//', views.generate_invoice, name='generate_invoice'), - + path('sales/quotations//mark_quotation/', views.mark_quotation, name='mark_quotation'), + path('sales/quotations//post_quotation/', views.post_quotation, name='post_quotation'), + path('sales/quotations//invoice_detail/', views.invoice_detail, name='invoice_detail'), # Users URLs path('user/create/', views.UserCreateView.as_view(), name='user_create'), diff --git a/inventory/views.py b/inventory/views.py index 71f5a81b..280e3872 100644 --- a/inventory/views.py +++ b/inventory/views.py @@ -1,8 +1,10 @@ from django_ledger.models import EntityModel, InvoiceModel,EntityUnitModel,LedgerModel import logging import json +import datetime from decimal import Decimal -from django_ledger.models import TransactionModel, AccountModel,JournalEntryModelfrom .pdf_generator import generate_quotation_pdf +from django_ledger.models import TransactionModel, AccountModel,JournalEntryModel +from .pdf_generator import generate_quotation_pdf from django.shortcuts import HttpResponse from django.template.loader import render_to_string # from weasyprint import HTML @@ -817,18 +819,21 @@ def generate_invoice(request, pk): entity = quotation.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") customer = entity.get_customers().filter(customer_name=quotation.customer.get_full_name).first() invoice_model = entity.create_invoice( customer_model=customer, - terms=InvoiceModel.TERMS_NET_30, + terms=InvoiceModel.TERMS_ON_RECEIPT, cash_account=cash_account.first(), + prepaid_account=recivable_account.first(), coa_model=coa_qs.first(), ) name_list = [f"{instance.car.year} {instance.car.id_car_make} {instance.car.id_car_model} {instance.car.id_car_trim}" for instance in quotation.quotation_cars.all()] invoices_item_models = invoice_model.get_item_model_qs().filter(name__in=name_list) + invoice_itemtxs = { im.item_number: { "unit_cost": im.default_amount, @@ -841,66 +846,229 @@ def generate_invoice(request, pk): invoice_itemtxs = invoice_model.migrate_itemtxs( itemtxs=invoice_itemtxs, commit=True, operation=InvoiceModel.ITEMIZE_APPEND ) - - ledger = entity.create_ledger( - name=f"Payment Ledger for Invoice {invoice_model}", - posted=True + ledger = entity.get_ledgers().filter(name=f"Payment Ledger for Invoice {invoice_model}").first() + if not ledger: + ledger = entity.create_ledger(name=f"Payment Ledger for Invoice {invoice_model}",posted=True) + journal_entry = JournalEntryModel.objects.create( + posted=False, + description=f"Payment for Invoice {invoice_model}", + ledger=ledger, + locked=False, + origin="Payment", ) - entity_unit,created = EntityUnitModel.objects.get_or_create( - name="Sales Department", - entity=entity, - document_prefix="SD" - ) + quotation.payment_id = journal_entry.pk + quotation.is_approved = True + date = datetime.datetime.now() + quotation.date_draft = date + invoice_model.date_draft = date + invoice_model.save() + quotation.save() + + # invoice_model = invoice_model.filter(date_draft=quotation.date_draft).first() + # if not invoice_model.can_review(): + # messages.error(request, "Quotation is not ready for review") + # return redirect("quotation_detail", pk=pk) + + # invoice_model.mark_as_review() + # invoice_model.date_in_review = date + # qoutation.date_in_review = date + # qoutation.status = "In Review" + # invoice_model.save() + # qoutation.save() + # messages.success(request, _("Quotation Reviewed")) + # elif status == "approved": + # if qoutation.status == "Approved": + # messages.error(request, "Quotation is already approved") + # return redirect("quotation_detail", pk=pk) - journal_entry = JournalEntryModel.objects.create( - entity_unit=entity_unit, - posted=False, - description=f"Payment for Invoice {invoice_model}", - ledger=ledger, - locked=False, - origin="Payment", - ) + # invoice_model = invoice_model.filter(date_in_review=qoutation.date_in_review).first() + # if not invoice_model.can_approve(): + # messages.error(request, "Quotation is not ready for approval") + # return redirect("quotation_detail", pk=pk) + + # invoice_model.mark_as_approved(entity_slug=entity.slug, user_model=request.user.dealer.get_root_dealer.user) + # invoice_model.date_approved = date + # qoutation.date_approved = date + # invoice_model.save() + # qoutation.status = "Approved" + # qoutation.save() + # messages.success(request, _("Quotation Approved")) + # ledger = entity.create_ledger( + # name=f"Payment Ledger for Invoice {invoice_model}", + # posted=True + # ) + + # entity_unit,created = EntityUnitModel.objects.get_or_create( + # name="Sales Department", + # entity=entity, + # document_prefix="SD" + # ) + + # journal_entry = JournalEntryModel.objects.create( + # entity_unit=entity_unit, + # posted=False, + # description=f"Payment for Invoice {invoice_model}", + # ledger=ledger, + # locked=False, + # origin="Payment", + # ) - accounts_receivable = coa_qs.first().get_coa_accounts().filter(name="Accounts Receivable").first() - if not accounts_receivable: - accounts_receivable = entity.create_account( - code="AR", - role="asset", - name="Accounts Receivable", - coa_model=coa_qs.first(), - balance_type="credit" - ) + # accounts_receivable = coa_qs.first().get_coa_accounts().filter(name="Accounts Receivable").first() + # if not accounts_receivable: + # accounts_receivable = entity.create_account( + # code="AR", + # role="asset", + # name="Accounts Receivable", + # coa_model=coa_qs.first(), + # balance_type="credit" + # ) - TransactionModel.objects.create( - journal_entry=journal_entry, - account=cash_account.first(), # Debit Cash - amount=invoice_model.amount_due, # Payment amount - tx_type='debit', - description="Payment Received", - ) + # TransactionModel.objects.create( + # journal_entry=journal_entry, + # account=cash_account.first(), # Debit Cash + # amount=invoice_model.amount_due, # Payment amount + # tx_type='debit', + # description="Payment Received", + # ) - TransactionModel.objects.create( - journal_entry=journal_entry, - account=accounts_receivable, # Credit Accounts Receivable - amount=invoice_model.amount_due, # Payment amount - tx_type='credit', - description="Payment Received", - ) - invoice_model.mark_as_review() - print("reviewed") - invoice_model.mark_as_approved(entity_slug=entity.slug, user_model=request.user.dealer.get_root_dealer.user) - print("approved") - invoice_model.mark_as_paid(entity_slug=entity.slug, user_model=request.user.dealer.get_root_dealer.user) - print("paid") + # TransactionModel.objects.create( + # journal_entry=journal_entry, + # account=accounts_receivable, # Credit Accounts Receivable + # amount=invoice_model.amount_due, # Payment amount + # tx_type='credit', + # description="Payment Received", + # ) + # invoice_model.mark_as_review() + # print("reviewed") + # invoice_model.mark_as_approved(entity_slug=entity.slug, user_model=request.user.dealer.get_root_dealer.user) + # print("approved") + # invoice_model.mark_as_paid(entity_slug=entity.slug, user_model=request.user.dealer.get_root_dealer.user) + # print("paid") + # invoice_model.save() messages.success(request, "Invoice created") return redirect("quotation_detail", pk=pk) # return redirect('django_ledger:invoice-detail', entity_slug=quotation.entity.slug, invoice_pk=invoice.uuid) +@login_required +def post_quotation(request, pk): + qoutation = get_object_or_404(models.SaleQuotation, pk=pk) + if qoutation.posted: + messages.error(request, "Quotation is already posted") + return redirect("quotation_detail", pk=pk) + entity = qoutation.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") + customer = entity.get_customers().filter(customer_name=qoutation.customer.get_full_name).first() + invoice_model = entity.get_invoices().filter(customer=customer,date_paid=qoutation.date_paid).first() + ledger = entity.get_ledgers().filter(name=f"Payment Ledger for Invoice {invoice_model}").first() + return + # if not ledger: + # ledger = entity.create_ledger(name=f"Payment Ledger for Invoice {invoice_model}",posted=True) + + entity_unit,created = EntityUnitModel.objects.get_or_create( + name="Sales Department", + entity=entity, + document_prefix="SD" + ) + + journal_entry = JournalEntryModel.objects.create( + entity_unit=entity_unit, + posted=False, + description=f"Payment for Invoice {invoice_model}", + ledger=ledger, + locked=False, + origin="Payment", + ) + + TransactionModel.objects.create( + journal_entry=journal_entry, + account=cash_account.first(), # Debit Cash + amount=invoice_model.amount_due, # Payment amount + tx_type='debit', + description="Payment Received", + ) + + TransactionModel.objects.create( + journal_entry=journal_entry, + account=recivable_account.first(), # Credit Accounts Receivable + amount=invoice_model.amount_due, # Payment amount + tx_type='credit', + description="Payment Received", + ) + journal_entry.posted = True + qoutation.posted = True + qoutation.save() + journal_entry.save() + messages.success(request, "Invoice posted") + return redirect("quotation_detail", pk=pk) + +@login_required +def mark_quotation(request, pk): + qoutation = get_object_or_404(models.SaleQuotation, pk=pk) + status = request.GET.get("status") + entity = qoutation.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) + # if status == "in_review": + # if qoutation.status == "In Review": + # messages.error(request, "Quotation is already in review") + # return redirect("quotation_detail", pk=pk) + + # invoice_model = invoice_model.filter(date_draft=qoutation.date_draft).first() + # if not invoice_model.can_review(): + # messages.error(request, "Quotation is not ready for review") + # return redirect("quotation_detail", pk=pk) + + # invoice_model.mark_as_review() + # invoice_model.date_in_review = date + # qoutation.date_in_review = date + # qoutation.status = "In Review" + # invoice_model.save() + # qoutation.save() + # messages.success(request, _("Quotation Reviewed")) + # elif status == "approved": + # if qoutation.status == "Approved": + # messages.error(request, "Quotation is already approved") + # return redirect("quotation_detail", pk=pk) + + # invoice_model = invoice_model.filter(date_in_review=qoutation.date_in_review).first() + # if not invoice_model.can_approve(): + # messages.error(request, "Quotation is not ready for approval") + # return redirect("quotation_detail", pk=pk) + + # invoice_model.mark_as_approved(entity_slug=entity.slug, user_model=request.user.dealer.get_root_dealer.user) + # invoice_model.date_approved = date + # qoutation.date_approved = date + # invoice_model.save() + # qoutation.status = "Approved" + # qoutation.save() + # messages.success(request, _("Quotation Approved")) + # elif status == "paid": + # if qoutation.status == "Paid": + # messages.error(request, "Quotation is already paid") + # return redirect("quotation_detail", pk=pk) + + # invoice_model = invoice_model.filter(date_approved=qoutation.date_approved).first() + # if not invoice_model.can_pay(): + # messages.error(request, "Quotation is not ready for payment") + # return redirect("quotation_detail", pk=pk) + + # invoice_model.mark_as_paid(entity_slug=entity.slug, user_model=request.user.dealer.get_root_dealer.user) + # invoice_model.date_paid = date + # qoutation.date_paid = date + # invoice_model.save() + # qoutation.status = "Paid" + # qoutation.save() + # messages.success(request, _("Quotation Paid")) + return redirect("quotation_detail", pk=pk) + @login_required def confirm_quotation(request, pk): quotation = get_object_or_404(models.SaleQuotation, pk=pk) @@ -1197,4 +1365,25 @@ def download_quotation_pdf(request, quotation_id): return response except models.SaleQuotation.DoesNotExist: - return HttpResponse("Quotation not found", status=404) \ No newline at end of file + return HttpResponse("Quotation not found", status=404) + +@login_required +def invoice_detail(request,pk): + quotation = get_object_or_404(models.SaleQuotation, pk=pk) + entity = quotation.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() + + return redirect('quotation_detail', pk=pk) +@login_required +def payment_invoice(request,pk): + quotation = get_object_or_404(models.SaleQuotation, pk=pk) + entity = quotation.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() + + + return redirect('quotation_detail', pk=pk) \ No newline at end of file diff --git a/templates/sales/invoice/invoice_detail.html b/templates/sales/invoice/invoice_detail.html new file mode 100644 index 00000000..91e4d832 --- /dev/null +++ b/templates/sales/invoice/invoice_detail.html @@ -0,0 +1,77 @@ +{% extends "base.html" %} +{% load i18n %} + +{% block title %}{{ _("View Customer") }}{% endblock title %} + +{% block content %} + + + +
+
+
+

{{ _("Customer Details") }}

+
+
+
+
+

{{ _("First Name") }}: {{ customer.first_name }}

+

{{ _("Middle Name") }}: {{ customer.middle_name }}

+

{{ _("Last Name") }}: {{ customer.last_name }}

+
+
+

{{ _("Email") }}: {{ customer.email }}

+

{{ _("National ID") }}: {{ customer.national_id }}

+

{{ _("Phone Number") }}: {{ customer.phone_number }}

+

{{ _("Address") }}: {{ customer.address }}

+
+
+
+ +
+
+{% endblock %} diff --git a/templates/sales/quotation_detail.html b/templates/sales/quotation_detail.html index e9082ce8..0cf54d37 100644 --- a/templates/sales/quotation_detail.html +++ b/templates/sales/quotation_detail.html @@ -2,6 +2,7 @@ {% load custom_filters %} {% load i18n %} {% block content %} + @@ -119,22 +131,32 @@ diff --git a/templates/sales/quotation_list.html b/templates/sales/quotation_list.html index c49714ea..2d55d773 100644 --- a/templates/sales/quotation_list.html +++ b/templates/sales/quotation_list.html @@ -28,10 +28,14 @@ {{ quotation.quotation_cars.count }} {{ quotation.total_vat }} - {% if quotation.is_approved %} - Approved - {% else %} - Pending For Approval + {% if quotation.status == 'Draft' %} + {{quotation.status|capfirst}} + {% elif quotation.status == 'In Review' %} + {{quotation.status|capfirst}} + {% elif quotation.status == 'Approved' %} + {{quotation.status|capfirst}} + {% elif quotation.status == 'Paid' %} + {{quotation.status|capfirst}} {% endif %} {{ quotation.created_at|date:"d/m/Y H:i" }}