From a3cb3c5f556051c14e53d27af5281ce0c1dbf6dd Mon Sep 17 00:00:00 2001 From: gitea Date: Tue, 28 Jan 2025 12:28:05 +0000 Subject: [PATCH] update --- Pipfile | 208 ++++++++++++++++++ i.mosa_BalanceSheetStatement_20250101.pdf | Bin 0 -> 11511 bytes inventory/forms.py | 20 +- inventory/signals.py | 12 +- inventory/utils.py | 96 ++++++++ inventory/views.py | 54 +++-- ismail_BalanceSheetStatement_20250101.pdf | Bin 0 -> 11512 bytes scripts/run.py | 101 ++++----- templates/header.html | 6 +- templates/ledger/reports/balance_sheet.html | 3 +- .../reports/tags/balance_sheet_statement.html | 4 +- .../sales/estimates/estimate_detail.html | 19 +- templates/sales/invoices/invoice_detail.html | 14 +- 13 files changed, 424 insertions(+), 113 deletions(-) create mode 100644 Pipfile create mode 100644 i.mosa_BalanceSheetStatement_20250101.pdf create mode 100644 ismail_BalanceSheetStatement_20250101.pdf diff --git a/Pipfile b/Pipfile new file mode 100644 index 00000000..042ec440 --- /dev/null +++ b/Pipfile @@ -0,0 +1,208 @@ +[[source]] +url = "https://pypi.org/simple" +verify_ssl = true +name = "pypi" + +[packages] +aiohappyeyeballs = "==2.4.4" +aiohttp = "==3.11.11" +aiohttp-retry = "==2.8.3" +aiosignal = "==1.3.2" +alabaster = "==1.0.0" +albucore = "==0.0.23" +albumentations = "==2.0.0" +annotated-types = "==0.7.0" +anyio = "==4.8.0" +arabic-reshaper = "==3.0.0" +asgiref = "==3.8.1" +astor = "==0.8.1" +astroid = "==3.3.8" +attrs = "==23.2.0" +autopep8 = "==2.3.2" +babel = "==2.15.0" +beautifulsoup4 = "==4.12.3" +bleach = "==6.2.0" +blinker = "==1.9.0" +brotli = "==1.1.0" +certifi = "==2024.12.14" +cffi = "==1.17.1" +chardet = "==5.2.0" +charset-normalizer = "==3.4.1" +click = "==8.1.8" +colorama = "==0.4.6" +commonmark = "==0.9.1" +contourpy = "==1.3.1" +crispy-bootstrap5 = "==2024.10" +cryptography = "==44.0.0" +cssselect2 = "==0.7.0" +ctranslate2 = "==4.5.0" +cycler = "==0.12.1" +cython = "==3.0.11" +decorator = "==5.1.1" +desert = "==2020.11.18" +dill = "==0.3.9" +distro = "==1.9.0" +dj-rest-auth = "==7.0.1" +dj-shop-cart = "==7.1.1" +django = "==5.1.5" +django-allauth = "==65.3.1" +django-appointment = "==3.7.4" +django-autoslug = "==1.9.9" +django-bootstrap5 = "==24.3" +django-classy-tags = "==4.1.0" +django-cors-headers = "==4.6.0" +django-countries = "==7.6.1" +django-crispy-forms = "==2.3" +django-debug-toolbar = "==5.0.1" +django-extensions = "==3.2.3" +django-filter = "==24.3" +django-formtools = "==2.5.1" +django-ledger = "==0.7.3" +django-money = "==3.5.3" +django-nine = "==0.2.7" +django-nonefield = "==0.4" +django-phonenumber-field = "==8.0.0" +django-picklefield = "==3.2" +django-prometheus = "==2.3.1" +django-q2 = "==1.7.6" +django-sekizai = "==4.1.0" +django-silk = "==5.3.2" +django-sms = "==0.7.0" +django-sslserver = "==0.22" +django-tables2 = "==2.7.5" +django-treebeard = "==4.7.1" +django-view-breadcrumbs = "==2.5.1" +djangocms-admin-style = "==3.3.1" +djangorestframework = "==3.15.2" +djangorestframework-simplejwt = "==5.4.0" +djangoviz = "==0.1.1" +docutils = "==0.21.2" +easy-thumbnails = "==2.10" +et-xmlfile = "==2.0.0" +faker = "==33.3.1" +filelock = "==3.16.1" +fire = "==0.7.0" +flask = "==3.1.0" +fonttools = "==4.55.3" +frozenlist = "==1.5.0" +fsspec = "==2024.12.0" +gprof2dot = "==2024.6.6" +graphqlclient = "==0.2.4" +greenlet = "==3.1.1" +h11 = "==0.14.0" +h2 = "==4.1.0" +hpack = "==4.0.0" +hstspreload = "==2025.1.1" +httpcore = "==1.0.7" +httpx = "==0.28.1" +hyperframe = "==6.0.1" +idna = "==3.10" +imageio = "==2.37.0" +imagesize = "==1.4.1" +imgaug = "==0.4.0" +iso4217 = "==1.12.20240625" +isodate = "==0.7.2" +isort = "==5.13.2" +itsdangerous = "==2.2.0" +jinja2 = "==3.1.5" +jiter = "==0.8.2" +joblib = "==1.4.2" +kiwisolver = "==1.4.8" +lazy-loader = "==0.4" +ledger = "==1.0.1" +libretranslatepy = "==2.1.4" +lmdb = "==1.6.2" +lxml = "==5.3.0" +markdown = "==3.7" +markdown-it-py = "==3.0.0" +markupsafe = "==3.0.2" +marshmallow = "==3.25.1" +matplotlib = "==3.10.0" +mccabe = "==0.7.0" +mdurl = "==0.1.2" +mouseinfo = "==0.1.3" +mpmath = "==1.3.0" +multidict = "==6.1.0" +mypy-extensions = "==1.0.0" +networkx = "==3.4.2" +newrelic = "==10.4.0" +nltk = "==3.9.1" +libquadmath = "==2.2.2" +oauthlib = "==3.2.2" +ofxtools = "==0.9.5" +openai = "==1.59.8" +opencv-contrib-python = "==4.11.0.86" +opencv-python = "==4.11.0.86" +opencv-python-headless = "==4.11.0.86" +openpyxl = "==3.1.5" +opt-einsum = "==3.4.0" +outcome = "==1.3.0.post0" +packaging = "==24.2" +pandas = "==2.2.3" +pango = "==0.0.1" +pdfkit = "==1.0.0" +phonenumbers = "==8.13.53" +pillow = "==11.1.0" +platformdirs = "==4.3.6" +prometheus-client = "==0.21.1" +propcache = "==0.2.1" +protobuf = "==5.29.3" +psycopg = "==3.2.4" +psycopg-binary = "==3.2.4" +psycopg-c = "==3.2.4" +py-moneyed = "==3.0" +pyautogui = "==0.9.54" +pyclipper = "==1.3.0.post6" +pycodestyle = "==2.12.1" +pycparser = "==2.22" +pydantic = "==2.10.5" +pydantic-core = "==2.27.2" +pydotplus = "==2.0.2" +pydyf = "==0.11.0" +pygetwindow = "==0.0.9" +pygments = "==2.19.1" +pyjwt = "==2.10.1" +pylint = "==3.3.3" +pymsgbox = "==1.0.9" +pymysql = "==1.1.1" +pyobjc-core = "==11.0" +pyobjc-framework-cocoa = "==11.0" +pyobjc-framework-quartz = "==11.0" +pyparsing = "==3.2.1" +pyperclip = "==1.9.0" +pyphen = "==0.17.0" +pypng = "==0.20220715.0" +pyrect = "==0.2.0" +pyscreeze = "==1.0.1" +pyserial = "==3.5" +pysocks = "==1.7.1" +python-bidi = "==0.6.3" +python-dateutil = "==2.9.0.post0" +python-docx = "==1.1.2" +python-openid = "==2.2.5" +python3-saml = "==1.16.0" +pytweening = "==1.2.0" +pytz = "==2024.2" +pyvin = "==0.0.2" +pywa = "==2.7.0" +pywhat = "==5.1.0" +pywhatkit = "==5.4" +pyyaml = "==6.0.2" +pyzbar = "==0.1.9" +qrcode = "==8.0" +rapidfuzz = "==3.11.0" +regex = "==2024.11.6" +reportlab = "==4.2.5" +requests = "==2.32.3" +requests-oauthlib = "==2.0.0" +rfc3986 = "==2.0.0" +rich = "==13.9.4" +rubicon-objc = "==0.5.0" +sacremoses = "==0.1.1" +scikit-image = "==0.25.0" +scikit-learn = "*" + +[dev-packages] + +[requires] +python_version = "3.12" diff --git a/i.mosa_BalanceSheetStatement_20250101.pdf b/i.mosa_BalanceSheetStatement_20250101.pdf new file mode 100644 index 0000000000000000000000000000000000000000..e61950347ba494f01709036e3d0ceabc4f080725 GIT binary patch literal 11511 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

(;7d5+)b2SNcl=v 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 F{ukt3&`|&Y literal 0 HcmV?d00001 diff --git a/inventory/forms.py b/inventory/forms.py index 3664c702..c8cd6695 100644 --- a/inventory/forms.py +++ b/inventory/forms.py @@ -9,6 +9,8 @@ from phonenumber_field.phonenumber import PhoneNumber from .mixins import AddClassMixin from django.forms.models import inlineformset_factory from django_ledger.forms.invoice import InvoiceModelCreateForm as InvoiceModelCreateFormBase +from django_ledger.forms.estimate import EstimateModelCreateForm as EstimateModelCreateFormBase + from django_ledger.forms.bill import BillModelCreateForm as BillModelCreateFormBase from django_ledger.forms.vendor import VendorModelForm from .models import ( @@ -675,7 +677,10 @@ class InvoiceModelCreateForm(InvoiceModelCreateFormBase): self.fields['prepaid_account'].widget = forms.HiddenInput() self.fields['unearned_account'].widget = forms.HiddenInput() self.fields['date_draft'] = forms.DateField(widget=DateInput(attrs={'type': 'date'})) - + + def get_customer_queryset(self): + if 'customer' in self.fields: + self.fields['customer'].queryset = self.USER_MODEL.dealer.entity.get_customers() class BillModelCreateForm(BillModelCreateFormBase): def __init__(self, *args, **kwargs): @@ -692,4 +697,15 @@ class SaleOrderForm(forms.ModelForm): fields = ['estimate','payment_method', 'comments'] widgets = { 'comments': forms.Textarea(attrs={'rows': 3}), - } \ No newline at end of file + } + +class EstimateModelCreateForm(EstimateModelCreateFormBase): + def __init__(self, *args, entity_slug, user_model, **kwargs): + super(EstimateModelCreateForm, self).__init__(*args, entity_slug=entity_slug, user_model=user_model, **kwargs) + self.ENTITY_SLUG = entity_slug + self.USER_MODEL = user_model + self.fields['customer'].queryset = self.get_customer_queryset() + + def get_customer_queryset(self): + return self.USER_MODEL.dealer.entity.get_customers() + diff --git a/inventory/signals.py b/inventory/signals.py index 4422d3ae..c2332aea 100644 --- a/inventory/signals.py +++ b/inventory/signals.py @@ -87,13 +87,13 @@ def create_ledger_entity(sender, instance, created, **kwargs): use_accrual_method=False, fy_start_month=1, ) - - if entity: + + 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: # Create unit of measures entity.create_uom(name="Unit", unit_abbr="unit") @@ -688,9 +688,10 @@ def create_item_model(sender, instance, created, **kwargs): name=instance.vin, item_type=ItemModel.ITEM_TYPE_MATERIAL, uom_model=uom, - coa_model=coa, - additional_info={} + coa_model=coa, ) + product.additional_info = {} + product.save() product = entity.get_items_all().filter(name=instance.vin).first() product.additional_info.update({'car_info': instance.to_dict()}) @@ -702,6 +703,7 @@ def update_item_model_cost(sender, instance, created, **kwargs): entity = instance.car.dealer.entity product = entity.get_items_all().filter(name=instance.car.vin).first() + product.default_amount = instance.selling_price product.additional_info = {} product.additional_info.update({"car_finance":instance.to_dict()}) diff --git a/inventory/utils.py b/inventory/utils.py index c73ed419..62857d74 100644 --- a/inventory/utils.py +++ b/inventory/utils.py @@ -1,3 +1,4 @@ +from django.core.exceptions import ObjectDoesNotExist import json import random import datetime @@ -577,3 +578,98 @@ def to_dict(obj): else: obj_dict[key] = str(value) return obj_dict + +class CarFinanceCalculator: + VAT_OBJ_NAME = 'vat_rate' + CAR_FINANCE_KEY = 'car_finance' + CAR_INFO_KEY = 'car_info' + ADDITIONAL_SERVICES_KEY = 'additional_services' + + def __init__(self, model): + 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(is_active=True).first() + if not vat: + raise ObjectDoesNotExist("No active VAT rate found") + return vat.rate + + def _get_item_transactions(self): + return self.model.get_itemtxs_data()[0].all() + + @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_finance = self._get_nested_value(item, self.CAR_FINANCE_KEY) + car_info = self._get_nested_value(item, self.CAR_INFO_KEY) + unit_price = Decimal(car_finance.get('selling_price', 0)) + + return { + "item_number": item.item_model.item_number, + "vin": car_info.get('vin'), + "make": car_info.get('make'), + "model": car_info.get('model'), + "year": car_info.get('year'), + "trim": car_info.get('mileage'), # Verify if this should actually be mileage + "cost_price": car_finance.get('cost_price'), + "selling_price": car_finance.get('selling_price'), + "discount": car_finance.get('discount_amount'), + "quantity": quantity, + "unit_price": unit_price, + "total": unit_price * Decimal(quantity), + "total_vat": car_finance.get('total_vat'), + "additional_services": self._get_nested_value(item, self.ADDITIONAL_SERVICES_KEY), + } + + def _get_additional_services(self): + return [ + {"name": service.name, "price": service.price} + for item in self.item_transactions + for service in self._get_nested_value(item, self.ADDITIONAL_SERVICES_KEY) or [] + ] + + def calculate_totals(self): + total_price = sum( + Decimal(self._get_nested_value(item, self.CAR_FINANCE_KEY, 'selling_price')) * + Decimal(self._get_quantity(item)) + for item in self.item_transactions + ) + + total_vat_amount = total_price * self.vat_rate + total_discount = sum( + Decimal(self._get_nested_value(item, self.CAR_FINANCE_KEY, 'discount_amount')) + for item in self.item_transactions + ) + + return { + "total_price": total_price, + "total_vat_amount": total_vat_amount, + "total_discount": total_discount, + "grand_total": (total_price + total_vat_amount) - total_discount, + } + + 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": totals['total_price'], + "total_vat": totals['total_vat_amount'] + totals['total_price'], + "total_discount": totals['total_discount'], + "grand_total": totals['grand_total'], + "additionals": self.additional_services, + "vat": self.vat_rate, + } \ No newline at end of file diff --git a/inventory/views.py b/inventory/views.py index 70dc11c1..2581940f 100644 --- a/inventory/views.py +++ b/inventory/views.py @@ -1,3 +1,4 @@ +from rich import print from decimal import Decimal from django.core.paginator import Paginator from django.forms import DateField, DateInput, HiddenInput, TextInput @@ -34,7 +35,8 @@ from django_ledger.forms.invoice import ( PaidInvoiceModelUpdateForm, ) from django_ledger.forms.account import AccountModelCreateForm, AccountModelUpdateForm -from django_ledger.forms.estimate import EstimateModelCreateForm +# from django_ledger.forms.estimate import EstimateModelCreateForm + from django_ledger.forms.invoice import InvoiceModelCreateForm from django_ledger.forms.item import ( ServiceCreateForm, @@ -82,6 +84,7 @@ from django.contrib.auth.mixins import PermissionRequiredMixin from django.contrib.messages.views import SuccessMessageMixin from django.contrib.auth.models import Group from .utils import ( + CarFinanceCalculator, calculate_vat_amount, get_calculations, get_car_finance_data, @@ -2349,7 +2352,7 @@ def create_estimate(request): } ) - form = EstimateModelCreateForm(entity_slug=entity.slug, user_model=entity.admin) + form = forms.EstimateModelCreateForm(entity_slug=entity.slug, user_model=entity.admin) form.fields["customer"].queryset = entity.get_customers().filter(active=True) car_list = models.Car.objects.filter( dealer=dealer, finances__selling_price__gt=0 @@ -2378,14 +2381,17 @@ class EstimateDetailView(LoginRequiredMixin, DetailView): def get_context_data(self, **kwargs): estimate = kwargs.get("object") if estimate.get_itemtxs_data(): - data = get_financial_values(estimate) - - kwargs["vat_amount"] = data["vat_amount"] - kwargs["total"] = data["grand_total"] - kwargs["discount_amount"] = data["discount_amount"] - kwargs["vat"] = data["vat"] - kwargs["car_and_item_info"] = data["car_and_item_info"] - kwargs["additional_services"] = data["additional_services"] + # data = get_financial_values(estimate) + calculator = CarFinanceCalculator(estimate) + finance_data = calculator.get_finance_data() + # kwargs["vat_amount"] = data["vat_amount"] + # kwargs["total"] = data["grand_total"] + # kwargs["discount_amount"] = data["discount_amount"] + # kwargs["vat"] = data["vat"] + # kwargs["car_and_item_info"] = data["car_and_item_info"] + # kwargs["additional_services"] = data["additional_services"] + kwargs['data'] = finance_data + print(finance_data) kwargs["invoice"] = ( InvoiceModel.objects.all().filter(ce_model=estimate).first() ) @@ -2400,8 +2406,8 @@ def create_sale_order(request, pk): form = forms.SaleOrderForm(request.POST) if form.is_valid(): form.save() - if not estimate.is_completed(): - estimate.mark_as_completed() + if not estimate.is_approved(): + estimate.mark_as_approved() estimate.save() messages.success(request, "Sale Order created successfully") return redirect("estimate_detail", pk=pk) @@ -2409,11 +2415,13 @@ def create_sale_order(request, pk): form = forms.SaleOrderForm() form.fields["estimate"].queryset = EstimateModel.objects.filter(pk=pk) form.initial['estimate'] = estimate - data = get_car_finance_data(estimate) + # data = get_car_finance_data(estimate) + calculator = CarFinanceCalculator(estimate) + finance_data = calculator.get_finance_data() return render( request, "sales/estimates/sale_order_form.html", - {"form": form, "estimate": estimate, "items": items,"data": data}, + {"form": form, "estimate": estimate, "items": items,"data": finance_data}, ) def preview_sale_order(request,pk): @@ -2510,14 +2518,16 @@ class InvoiceDetailView(LoginRequiredMixin, DetailView): invoice = kwargs.get("object") if invoice.get_itemtxs_data(): - data = get_financial_values(invoice) - - kwargs["vat_amount"] = data["vat_amount"] - kwargs["total"] = data["grand_total"] - kwargs["discount_amount"] = data["discount_amount"] - kwargs["vat"] = data["vat"] - kwargs["car_and_item_info"] = data["car_and_item_info"] - kwargs["additional_services"] = data["additional_services"] + # data = get_financial_values(invoice) + calculator = CarFinanceCalculator(invoice) + finance_data = calculator.get_finance_data() + # kwargs["vat_amount"] = data["vat_amount"] + # kwargs["total"] = data["grand_total"] + # kwargs["discount_amount"] = data["discount_amount"] + # kwargs["vat"] = data["vat"] + # kwargs["car_and_item_info"] = data["car_and_item_info"] + # kwargs["additional_services"] = data["additional_services"] + kwargs["data"] = finance_data kwargs["payments"] = JournalEntryModel.objects.filter( ledger=invoice.ledger ).all() diff --git a/ismail_BalanceSheetStatement_20250101.pdf b/ismail_BalanceSheetStatement_20250101.pdf new file mode 100644 index 0000000000000000000000000000000000000000..c0522195972db108b2cc6836df3891311226e957 GIT binary patch literal 11512 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 Kt(3Af#{U8tm(!d8 literal 0 HcmV?d00001 diff --git a/scripts/run.py b/scripts/run.py index 5350fcd8..7b0b7c9f 100644 --- a/scripts/run.py +++ b/scripts/run.py @@ -1,70 +1,45 @@ from decimal import Decimal -from django_ledger.models import EstimateModel +from django_ledger.models import EstimateModel,EntityModel from rich import print - +from datetime import date from inventory.models import VatRate +from inventory.utils import CarFinanceCalculator def run(): + # estimate = EstimateModel.objects.first() + # calculator = CarFinanceCalculator(estimate) + # finance_data = calculator.get_finance_data() + + # print(finance_data) + # entity = EntityModel.objects.get(name="ismail") + # bs_report = entity.get_balance_sheet_statement( + # to_date=date(2025, 1, 1), + # save_pdf=False, + # filepath='./' + # ) + + # ic_report = entity.get_income_statement( + # from_date=date(2022, 1, 1), + # to_date=date(2022, 12, 31), + # save_pdf=False, + # filepath='./' + # ) + + # # print(bs_report) + # print(ic_report.get_report_data()) estimate = EstimateModel.objects.first() - vat = VatRate.objects.filter(is_active=True).first() - data = estimate.get_itemtxs_data()[0].all() - total = sum( - [ - Decimal(item.item_model.additional_info["car_finance"]["selling_price"]) - * Decimal(item.ce_quantity or item.quantity) - for item in data - ] - ) - - additional_services = [] - - for i in data: - if i.item_model.additional_info["additional_services"]: - additional_services.extend( - [ - {"name": x.name, "price": x.price} - for x in i.item_model.additional_info["additional_services"] - ] - ) - cars_info = { - "cars": [ - { - "vin": x.item_model.additional_info["car_info"]["vin"], - "make": x.item_model.additional_info["car_info"]["make"], - "model": x.item_model.additional_info["car_info"]["model"], - "year": x.item_model.additional_info["car_info"]["year"], - "trim": x.item_model.additional_info["car_info"]["mileage"], - "cost_price": x.item_model.additional_info["car_finance"]["cost_price"], - "selling_price": x.item_model.additional_info["car_finance"][ - "selling_price" - ], - "discount": x.item_model.additional_info["car_finance"][ - "discount_amount" - ], - "total": x.item_model.additional_info["car_finance"]["total"], - "additional_services": x.item_model.additional_info[ - "additional_services" - ], - } - for x in data - ], - "quantity": data.count(), - "total_price": total, - "total__vat": (total * vat.rate) + total, - "total_discount": sum( - Decimal(x.item_model.additional_info["car_finance"]["discount_amount"]) - for x in data - ), - "grand_total": Decimal(total * vat.rate) - + total - - Decimal( - sum( - Decimal(x.item_model.additional_info["car_finance"]["discount_amount"]) - for x in data - ) - ), - "additionals": additional_services, - } - - print(cars_info) + calculator = CarFinanceCalculator(estimate) + finance_data = calculator.get_finance_data() + + + invoice_itemtxs = { + i.get("item_number"): { + "unit_cost": i.get("total"), + "quantity": i.get("quantity"), + "total_amount": i.get("total_vat"), + } + for i in finance_data.get("cars") + } + + print(invoice_itemtxs) \ No newline at end of file diff --git a/templates/header.html b/templates/header.html index ad9c6581..d0a49a30 100644 --- a/templates/header.html +++ b/templates/header.html @@ -236,9 +236,13 @@ {% trans 'bills'|capfirst %} + {% if request.user.is_authenticated %} + {% else %} + + {% endif %}

diff --git a/templates/ledger/reports/balance_sheet.html b/templates/ledger/reports/balance_sheet.html index 53d30094..8f371ddd 100644 --- a/templates/ledger/reports/balance_sheet.html +++ b/templates/ledger/reports/balance_sheet.html @@ -21,7 +21,7 @@ {% endblock %} -{% block content %} +{% block content %}
@@ -44,6 +44,7 @@

As of {{ to_date | date:'m/d/Y' }}

+
diff --git a/templates/ledger/reports/tags/balance_sheet_statement.html b/templates/ledger/reports/tags/balance_sheet_statement.html index 581c24e2..33440c65 100644 --- a/templates/ledger/reports/tags/balance_sheet_statement.html +++ b/templates/ledger/reports/tags/balance_sheet_statement.html @@ -58,14 +58,14 @@ {% icon 'bi:arrow-down' 24 %}
- {% comment %}
diff --git a/templates/sales/estimates/estimate_detail.html b/templates/sales/estimates/estimate_detail.html index 2b5a6fbe..43b5cc9a 100644 --- a/templates/sales/estimates/estimate_detail.html +++ b/templates/sales/estimates/estimate_detail.html @@ -45,8 +45,7 @@ {% elif estimate.status == 'in_review' %} {% elif estimate.status == 'approved' %} - {% trans 'Create Sale Order' %} - {% elif estimate.status == 'completed' %} + {% trans 'Create Sale Order' %} {% trans 'Create Invoice' %} {% trans 'Preview Sale Order' %} {% elif estimate.status == 'in_review' %} @@ -128,31 +127,31 @@ - {% for item in car_and_item_info %} + {% for item in data.cars %} {{forloop.counter}} - {{item.info.make}} + {{item.make}} {{item.quantity}} - {{item.finances.selling_price}} + {{item.unit_price}} {{item.total}} {% endfor %} {% trans "Discount Amount" %} - - {{discount_amount}} + - {{data.total_discount}} - {% trans "Vat" %} ({{vat}}%) + {% trans "Vat" %} ({{data.vat}}%) - + {{vat_amount}} + {% trans "Additional Services" %} - {% for service in additional_services %} + {% for service in data.additional_services %} + {{service.name}} - {{service.price}}
{% endfor %} @@ -160,7 +159,7 @@ {% trans "Grand Total" %} - {{total}} + {{data.grand_total}} diff --git a/templates/sales/invoices/invoice_detail.html b/templates/sales/invoices/invoice_detail.html index d65994a9..90ac447f 100644 --- a/templates/sales/invoices/invoice_detail.html +++ b/templates/sales/invoices/invoice_detail.html @@ -219,23 +219,23 @@ - {% for item in car_and_item_info %} + {% for item in data.cars %} {{forloop.counter}} - {{item.info.make}} + {{item.make}} {{item.quantity}} - {{item.finances.selling_price}} + {{item.selling_price}} {{item.total}} {% endfor %} {% trans "Discount Amount" %} - - {{discount_amount}} + - {{data.total_discount}} - {% trans "VAT" %} ({{vat}}%) + {% trans "VAT" %} ({{data.vat}}%) + {{vat_amount}} @@ -243,7 +243,7 @@ {% trans "Additional Services" %} - {% for service in additional_services %} + {% for service in data.additional_services %} + {{service.name}} - {{service.price}}
{% endfor %} @@ -251,7 +251,7 @@ {% trans "Grand Total" %} - {{total}} + {{data.grand_total}}