This commit is contained in:
Marwan Alwali 2025-01-30 13:13:07 +03:00
commit e3c1810b4a
16 changed files with 842 additions and 546 deletions

View File

@ -0,0 +1,20 @@
# Generated by Django 4.2.17 on 2025-01-29 12:59
from django.db import migrations, models
import django.utils.timezone
class Migration(migrations.Migration):
dependencies = [
('inventory', '0011_alter_saleorder_estimate'),
]
operations = [
migrations.AddField(
model_name='saleorder',
name='created',
field=models.DateTimeField(auto_now_add=True, default=django.utils.timezone.now),
preserve_default=False,
),
]

View File

@ -0,0 +1,17 @@
# Generated by Django 4.2.17 on 2025-01-29 13:02
from django.db import migrations
class Migration(migrations.Migration):
dependencies = [
('inventory', '0012_saleorder_created'),
]
operations = [
migrations.AlterModelOptions(
name='saleorder',
options={'ordering': ['created']},
),
]

View File

@ -1562,6 +1562,10 @@ class SaleOrder(models.Model):
])
comments = models.TextField(blank=True, null=True)
formatted_order_id = models.CharField(max_length=10, unique=True, editable=False)
created = models.DateTimeField(auto_now_add=True)
class Meta:
ordering = ['-created']
def save(self, *args, **kwargs):
if not self.formatted_order_id:

View File

@ -1,7 +1,7 @@
from django import template
from calendar import month_abbr
from django.urls import reverse
from django_ledger.io.io_core import get_localdate
from django_ledger.io.io_core import get_localdate,validate_activity
register = template.Library()
@ -146,3 +146,35 @@ def balance_sheet_statement(context, io_model, to_date=None):
'user_model': user_model,
'tx_digest': io_digest.get_io_data(),
}
@register.inclusion_tag('django_ledger/financial_statements/tags/income_statement.html', takes_context=True)
def income_statement_table(context, io_model, from_date=None, to_date=None):
user_model = context['user']
activity = context['request'].GET.get('activity')
activity = validate_activity(activity, raise_404=True)
entity_slug = context['view'].kwargs.get('entity_slug')
if not from_date:
from_date = context['from_date']
if not to_date:
to_date = context['to_date']
io_digest = io_model.digest(
activity=activity,
user_model=user_model,
entity_slug=entity_slug,
unit_slug=context['unit_slug'],
by_unit=context['by_unit'],
from_date=from_date,
to_date=to_date,
equity_only=True,
process_groups=True,
income_statement=True,
signs=True
)
return {
'entity_slug': entity_slug,
'user_model': user_model,
'tx_digest': io_digest.get_io_data()
}

View File

@ -546,7 +546,23 @@ urlpatterns = [
path('entity/<slug:entity_slug>/balance-sheet/date/<int:year>/<int:month>/<int:day>/',
views.DateBalanceSheetView.as_view(),
name='entity-bs-date'),
# INCOME STATEMENT Reports ----
# Entity .....
path('entity/<slug:entity_slug>/income-statement/',
views.BaseIncomeStatementRedirectViewBase.as_view(),
name='entity-ic'),
path('entity/<slug:entity_slug>/income-statement/year/<int:year>/',
views.FiscalYearIncomeStatementViewBase.as_view(),
name='entity-ic-year'),
# path('entity/<slug:entity_slug>/income-statement/quarter/<int:year>/<int:quarter>/',
# views.QuarterlyIncomeStatementView.as_view(),
# name='entity-ic-quarter'),
# path('entity/<slug:entity_slug>/income-statement/month/<int:year>/<int:month>/',
# views.MonthlyIncomeStatementView.as_view(),
# name='entity-ic-month'),
# path('entity/<slug:entity_slug>/income-statement/date/<int:year>/<int:month>/<int:day>/',
# views.MonthlyIncomeStatementView.as_view(),
# name='entity-ic-date'),
]

View File

@ -1,3 +1,4 @@
from django.core.exceptions import ObjectDoesNotExist
import json
import random
import datetime
@ -283,15 +284,11 @@ def set_invoice_payment(dealer, entity, invoice, amount, payment_method):
vat_amount = 0
total_amount = 0
if invoice.terms == "on_receipt":
for x in invoice.get_itemtxs_data()[0].all():
# vat_amount += models.Car.objects.get(
# vin=x.item_model.name
# ).finances.vat_amount * Decimal(x.quantity)
total_amount += Decimal(x.unit_cost) * Decimal(x.quantity)
# grand_total = total_amount - Decimal(vat_amount)
total_amount
calculator = CarFinanceCalculator(invoice)
finance_data = calculator.get_finance_data()
# if invoice.terms == "on_receipt":
# for x in invoice.get_itemtxs_data()[0].all():
# total_amount += Decimal(x.unit_cost) * Decimal(x.quantity)
ledger = LedgerModel.objects.filter(
name__icontains=str(invoice.pk), entity=entity
@ -322,28 +319,26 @@ def set_invoice_payment(dealer, entity, invoice, amount, payment_method):
TransactionModel.objects.create(
journal_entry=journal,
account=debit_account, # Debit Cash
amount=amount, # Payment amount
amount=finance_data["grand_total"], # Payment amount
tx_type="debit",
description="Payment Received",
)
# if total_amount + invoice.
TransactionModel.objects.create(
journal_entry=journal,
account=credit_account, # Credit Accounts Receivable
amount=total_amount, # Payment amount
amount=finance_data["total_price"], # Payment amount
tx_type="credit",
description="Payment Received",
)
if vat_amount > 0:
TransactionModel.objects.create(
journal_entry=journal,
account=vat_payable_account, # Credit VAT Payable
amount=vat_amount,
tx_type="credit",
description="VAT Payable on Invoice",
TransactionModel.objects.create(
journal_entry=journal,
account=vat_payable_account, # Credit VAT Payable
amount=finance_data["total_vat_amount"],
tx_type="credit",
description="VAT Payable on Invoice",
)
invoice.make_payment(amount)
@ -578,5 +573,99 @@ def to_dict(obj):
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('trim'),
"mileage": car_info.get('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_vat_amount": totals['total_vat_amount'],
"total_discount": totals['total_discount'],
"grand_total": totals['grand_total'],
"additionals": self.additional_services,
"vat": self.vat_rate,
}

File diff suppressed because it is too large Load Diff

View File

@ -184,6 +184,9 @@ python3-saml
pytweening
pytz
pyvin
pywa
pywhat
pywhatkit
PyYAML
pyzbar
qrcode

View File

@ -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
]
)
calculator = CarFinanceCalculator(estimate)
finance_data = calculator.get_finance_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,
}
invoice_itemtxs = {
i.get("item_number"): {
"unit_cost": i.get("total_price"),
"quantity": i.get("quantity"),
"total_amount": i.get("total_vat"),
}
for i in finance_data.get("cars")
}
print(cars_info)
print(finance_data)

View File

@ -236,13 +236,38 @@
<span class="nav-link-icon"><span data-feather="package"></span></span><span class="nav-link-text">{% trans 'bills'|capfirst %}</span>
</div>
</a>
{% if request.user.dealer.entity.slug %}
<a class="nav-link" href="{% url 'entity-bs' request.user.dealer.entity.slug %}">
</li>
</ul>
</div>
</div>
<div class="nav-item-wrapper">
<a class="nav-link dropdown-indicator label-1" href="#nv-reports" role="button" data-bs-toggle="collapse" aria-expanded="false" aria-controls="nv-reports">
<div class="d-flex align-items-center">
<div class="dropdown-indicator-icon-wrapper"><span class="fas fa-caret-right dropdown-indicator-icon"></span></div>
<span class="nav-link-icon"><span class="fas fa-money-check-alt"></span></span><span class="nav-link-text">{% trans 'Reports' %}</span>
</div>
</a>
<div class="parent-wrapper label-1">
<ul class="nav collapse parent" data-bs-parent="#navbarVerticalCollapse" id="nv-reports">
<li class="nav-item">
{% if request.user.is_authenticated %}
<a class="nav-link" href="{% url 'entity-ic' request.user.dealer.entity.slug %}">
{% else %}
<a class="nav-link" href="#">
{% endif %}
<div class="d-flex align-items-center">
<span class="nav-link-icon"><span class="fas fa-newspaper"></span></span><span class="nav-link-text">{% trans 'Balance Sheet' %}</span>
<span class="nav-link-icon"><span data-feather="package"></span></span><span class="nav-link-text">{% trans 'Income Statement'|capfirst %}</span>
</div>
</a>
{% if request.user.is_authenticated %}
<a class="nav-link" href="{% url 'entity-bs' request.user.dealer.entity.slug %}">
{% else %}
<a class="nav-link" href="#">
{% endif %}
<div class="d-flex align-items-center">
<span class="nav-link-icon"><span data-feather="package"></span></span><span class="nav-link-text">{% trans 'Balance Sheet'|capfirst %}</span>
</div>
</a>
{% endif %}
</li>
</ul>
</div>

View File

@ -0,0 +1,63 @@
{% extends 'base.html' %}
{% load i18n %}
{% load static %}
{% load custom_filters %}
{% block period_navigation %}
{% if unit_model %}
<div class="column is-12">{% period_navigation 'unit-ic' %}</div>
{% elif entity %}
<div class="column is-12">{% period_navigation 'entity-ic' %}</div>
{% elif ledger %}
<div class="column is-12">{% period_navigation 'ledger-ic' %}</div>
{% endif %}
{% endblock %}
{% block content %}
<div class="card">
<div class="card-content has-text-centered">
<div class="container mb-4">
<div class="columns">
<div class="column">
{% if entity %}
<h1 class="is-size-2 has-text-weight-light">{{ entity.name }}</h1>
{% elif ledger %}
<h1 class="is-size-2 has-text-weight-light">{{ ledger.name }}</h1>
{% endif %}
{% if unit_model %}
<h3 class="is-size-4 has-text-weight-medium is-italic">{{ unit_model.name }} {% trans 'Unit' %}</h3>
{% endif %}
<h1 class="is-size-2 has-text-weight-bold">{% trans 'Income Statement' %}</h1>
<h2 class="is-size-2 has-text-weight-light">
{% if quarter %}{{ year }} | Q{{ quarter }}
{% elif month %}{{ start_date | date:'F, Y' }}
{% else %}Fiscal Year {{ year }}
{% endif %}</h2>
<h3 class="is-size-4 is-italic has-font-weight-light">
{{ from_date | date:'m/d/Y' }} - {{ to_date | date:'m/d/Y' }}
</h3>
</div>
</div>
</div>
{% income_statement_table io_model=object %}
{% if ledger %}
<a class="button is-fullwidth is-dark my-2"
href="{% url 'django_ledger:ledger-list' entity_slug=view.kwargs.entity_slug %}">Go
Back</a>
{% elif entity %}
<a class="button is-fullwidth is-dark my-2"
href="{% url 'django_ledger:entity-dashboard' entity_slug=view.kwargs.entity_slug %}">Go
Back</a>
{% endif %}
<a class="button is-fullwidth is-light my-2"
href="?by_unit=1">{% trans 'By Unit' %}</a>
<a class="button is-fullwidth is-link my-2"
href="{{ request.path }}?format=pdf">{% trans 'Download PDF' %}</a>
</div>
</div>
{% endblock %}

View File

@ -45,10 +45,12 @@
{% elif estimate.status == 'in_review' %}
<button id="accept_estimate" onclick="setFormAction('approved')" class="btn btn-phoenix-secondary" data-bs-toggle="modal" data-bs-target="#confirmModal"><span class="d-none d-sm-inline-block">{% trans 'Mark As Accept' %}</span></button>
{% elif estimate.status == 'approved' %}
<a href="{% url 'create_sale_order' estimate.pk %}" class="btn btn-phoenix-primary"><span class="d-none d-sm-inline-block">{% trans 'Create Sale Order' %}</span></a>
{% elif estimate.status == 'completed' %}
{% if estimate.sale_orders.first %}
<a href="{% url 'invoice_create' estimate.pk %}" class="btn btn-phoenix-primary"><span class="d-none d-sm-inline-block">{% trans 'Create Invoice' %}</span></a>
{% else %}
<a href="{% url 'create_sale_order' estimate.pk %}" class="btn btn-phoenix-primary"><span class="d-none d-sm-inline-block">{% trans 'Create Sale Order' %}</span></a>
<a href="{% url 'preview_sale_order' estimate.pk %}" class="btn btn-phoenix-primary"><span class="d-none d-sm-inline-block">{% trans 'Preview Sale Order' %}</span></a>
{% endif %}
{% elif estimate.status == 'in_review' %}
<a href="{% url 'estimate_preview' estimate.pk %}" class="btn btn-phoenix-primary"><span class="d-none d-sm-inline-block">{% trans 'Preview' %}</span></a>
{% endif %}
@ -128,31 +130,31 @@
</tr>
</thead>
<tbody>
{% for item in car_and_item_info %}
{% for item in data.cars %}
<tr>
<td class="">{{forloop.counter}}</td>
<td class="">{{item.info.make}}</td>
<td class="">{{item.make}}</td>
<td class="align-middle">{{item.quantity}}</td>
<td class="align-middle ps-5">{{item.finances.selling_price}}</td>
<td class="align-middle ps-5">{{item.unit_price}}</td>
<td class="align-middle text-body-tertiary fw-semibold">{{item.total}}</td>
</tr>
{% endfor %}
<tr class="bg-body-secondary total-sum">
<td class="align-middle ps-4 fw-semibold text-body-highlight" colspan="4">{% trans "Discount Amount" %}</td>
<td class="align-middle ps-4 fw-semibold text-body-highlight" colspan="4">{% trans "Vat" %} ({{data.vat}}%)</td>
<td class="align-middle text-start fw-semibold">
<span id="grand-total">- {{discount_amount}}</span>
<span id="grand-total">+ {{data.total_vat_amount}}</span>
</td>
</tr>
<tr class="bg-body-secondary total-sum">
<td class="align-middle ps-4 fw-semibold text-body-highlight" colspan="4">{% trans "Vat" %} ({{vat}}%)</td>
<td class="align-middle text-start fw-semibold">
<span id="grand-total">+ {{vat_amount}}</span>
<td class="align-middle ps-4 fw-semibold text-body-highlight" colspan="4">{% trans "Discount Amount" %}</td>
<td class="align-middle text-start text-danger fw-semibold ">
<span id="grand-total">- {{data.total_discount}}</span>
</td>
</tr>
<tr class="bg-body-secondary total-sum">
<td class="align-middle ps-4 fw-semibold text-body-highlight" colspan="4">{% trans "Additional Services" %}</td>
<td class="align-middle text-start fw-semibold">
{% for service in additional_services %}
{% for service in data.additional_services %}
<small><span class="fw-semibold">+ {{service.name}} - {{service.price}}</span></small><br>
{% endfor %}
</td>
@ -160,7 +162,7 @@
<tr class="bg-body-secondary total-sum">
<td class="align-middle ps-4 fw-bolder text-body-highlight" colspan="4">{% trans "Grand Total" %}</td>
<td class="align-middle text-start fw-bolder">
<span id="grand-total">{{total}}</span>
<span id="grand-total">{{data.grand_total}}</span>
</td>
</tr>
</tbody>

View File

@ -84,6 +84,20 @@
<p class="ps-6 ps-sm-0 fw-semibold mb-0">{{ estimate.customer.address_1 }}</p>
</td>
</tr>
<tr>
<td class="py-2">
<div class="d-flex align-items-center">
<div class="d-flex bg-info-subtle rounded-circle flex-center me-3" style="width:24px; height:24px">
<span class="text-info-dark" data-feather="trending-up" style="width:16px; height:16px"></span>
</div>
<p class="fw-bold mb-0">Total Discount</p>
</div>
</td>
<td class="py-2 d-none d-sm-block pe-sm-2">:</td>
<td class="py-2">
<p class="ps-6 ps-sm-0 fw-semibold mb-0">${{ data.total_discount }}</p>
</td>
</tr>
<tr>
<td class="py-2">
<div class="d-flex align-items-center">
@ -98,6 +112,7 @@
<p class="ps-6 ps-sm-0 fw-semibold mb-0">${{ data.grand_total }}</p>
</td>
</tr>
</table>
</div>
</div>
@ -143,7 +158,7 @@
<td class="create_by align-middle white-space-nowrap fw-semibold text-body-highlight">{{car.year}}</td>
<td class="last_activity align-middle text-center py-2">
<div class="d-flex align-items-center flex-1">
<span class="fw-bold fs-9 text-body">${{car.total_vat}}</span>
<span class="fw-bold fs-9 text-body">${{car.total}}</span>
</div>
</td>
</tr>

View File

@ -237,7 +237,7 @@
<tr class="bg-body-secondary total-sum">
<td class="align-middle ps-4 fw-semibold text-body-highlight" colspan="4">{% trans "VAT" %} ({{vat}}%)</td>
<td class="align-middle text-start fw-semibold">
<span id="grand-total">+ {{vat_amount}}</span>
<span id="grand-total">+ {{data.total_vat_amount}}</span>
</td>
</tr>
<tr class="bg-body-secondary total-sum">

View File

@ -208,11 +208,11 @@
</tr>
</thead>
<tbody>
{% for item in car_and_item_info %}
{% for item in data.cars %}
<tr>
<td class="">{{item.info.make}}</td>
<td class="">{{item.make}}</td>
<td class="align-middle">{{item.quantity}}</td>
<td class="align-middle ps-5">{{item.finances.selling_price}}</td>
<td class="align-middle ps-5">{{item.selling_price}}</td>
<td class="align-middle text-body-tertiary fw-semibold">{{item.total}}</td>
</tr>
{% endfor %}
@ -222,10 +222,10 @@
<!-- Additional Charges (VAT and Services) -->
<div class="additional-charges">
<p><strong>VAT/ضريبة القيمة المضافة ({{vat}}%):</strong> <span class="highlight">{{vat_amount}}&nbsp;{{ _("SAR") }}</span></p>
<p><strong>VAT/ضريبة القيمة المضافة ({{vat}}%):</strong> <span class="highlight">{{data.vat}}&nbsp;{{ _("SAR") }}</span></p>
<p><strong>Additional Services/ الخدمات الإضافية</strong>
<br>
{% for service in additional_services %}
{% for service in data.additional_services %}
<span class="highlight">{{service.name}} - {{service.price}}&nbsp;{{ _("SAR") }}</span><br>
{% endfor %}
</p>
@ -233,7 +233,7 @@
<!-- Total -->
<div class="invoice-total">
<p><strong>Total/الإجمالي</strong> <span class="highlight">{{total}}&nbsp;{{ _("SAR") }}</span></p>
<p><strong>Total/الإجمالي</strong> <span class="highlight">{{data.grand_total}}&nbsp;{{ _("SAR") }}</span></p>
</div>
<!-- Footer Note -->

View File

@ -21,8 +21,11 @@
<tr class="hover-actions-trigger btn-reveal-trigger position-static">
<td class="align-middle product white-space-nowrap py-0">{{ order.formatted_order_id }}</td>
<td class="align-middle product white-space-nowrap py-0">{{ order.estimate.customer.customer_name }}</td>
<td class="align-middle product white-space-nowrap">{{ order.estimate }}</td>
<td class="align-middle product white-space-nowrap">
<a href="{% url 'estimate_detail' order.estimate.pk %}">
{{ order.estimate }}
</a>
</td>
<td class="text-center">
{% comment %} <a href="{% url 'estimate_detail' estimate.pk %}"
class="btn btn-sm btn-phoenix-success">