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 00000000..e6195034
Binary files /dev/null and b/i.mosa_BalanceSheetStatement_20250101.pdf differ
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 00000000..c0522195
Binary files /dev/null and b/ismail_BalanceSheetStatement_20250101.pdf differ
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 @@
+ {% if request.user.is_authenticated %}
+ {% else %}
+
+ {% endif %}
As of {{ to_date | date:'m/d/Y' }}