added the logic for car_purchase and sale report

This commit is contained in:
Faheedkhan 2025-07-22 12:00:49 +03:00
parent f5feee2372
commit d5d895789a
13 changed files with 427 additions and 37 deletions

View File

@ -639,6 +639,7 @@ class Car(Base):
default=CarStatusChoices.AVAILABLE,
verbose_name=_("Status"),
)
stock_type = models.CharField(
max_length=10,
choices=CarStockTypeChoices.choices,
@ -648,6 +649,7 @@ class Car(Base):
remarks = models.TextField(blank=True, null=True, verbose_name=_("Remarks"))
mileage = models.IntegerField(blank=True, null=True, verbose_name=_("Mileage"))
receiving_date = models.DateTimeField(verbose_name=_("Receiving Date"))
sold_date=models.DateTimeField(verbose_name=_("Sold Date"))
hash = models.CharField(
max_length=64, blank=True, null=True, verbose_name=_("Hash")
)
@ -763,6 +765,7 @@ class Car(Base):
"remarks": self.remarks,
"mileage": self.mileage,
"receiving_date": self.receiving_date.strftime("%Y-%m-%d %H:%M:%S"),
"sold_date":self.sold_date.strftime("%Y-%m-%d %H:%M:%S"),
"hash": self.get_hash,
"id": str(self.id),
}
@ -901,7 +904,8 @@ class CarFinance(models.Model):
verbose_name=_("Discount Amount"),
default=Decimal("0.00"),
)
is_sold = models.BooleanField(default=False)
# is_sold = models.BooleanField(default=False)
@property
def total(self):

View File

@ -292,7 +292,8 @@ def update_item_model_cost(sender, instance, created, **kwargs):
:param kwargs: Additional keyword arguments passed during the signal invocation.
:return: None
"""
if created and not instance.is_sold:
# if created and not instance.is_sold:
if created:
entity = instance.car.dealer.entity
coa = entity.get_default_coa()
inventory_account = (

View File

@ -1107,6 +1107,22 @@ urlpatterns = [
views.PurchaseOrderMarkAsVoidView.as_view(),
name="po-action-mark-as-void",
),
# reports
path(
"<slug:dealer_slug>/purchase-report/",
views.purchase_report_view,
name="po-report",
),
path('purchase-report/<slug:dealer_slug>/csv/', views.purchase_report_csv_export, name='purchase-report-csv-export'),
path(
"<slug:dealer_slug>/car-sale-report/",
views.car_sale_report_view,
name="car-sale-report",
),
path('car-sale-report/<slug:dealer_slug>/csv/', views.car_sale_report_csv_export, name='car-sale-report-csv-export'),
]
handler404 = "inventory.views.custom_page_not_found_view"

View File

@ -1470,7 +1470,6 @@ class CarFinanceCreateView(LoginRequiredMixin, PermissionRequiredMixin, CreateVi
permission_required = ["inventory.add_carfinance"]
def dispatch(self, request, *args, **kwargs):
print(self.car)
self.car = get_object_or_404(models.Car, slug=self.kwargs["slug"])
return super().dispatch(request, *args, **kwargs)
@ -4233,22 +4232,16 @@ def sales_list_view(request, dealer_slug):
:rtype: HttpResponse
"""
dealer = get_object_or_404(models.Dealer, slug=dealer_slug)
entity = dealer.entity
print(entity)
dealer = get_object_or_404(models.Dealer, slug=dealer_slug)
staff = getattr(request.user.staffmember, "staff", None)
print(staff)
qs = []
try:
if any([request.is_dealer, request.is_manager, request.is_accountant]):
qs = models.ExtraInfo.get_sale_orders(staff=staff,is_dealer=True)
print(qs)
elif request.is_staff:
qs = models.ExtraInfo.get_sale_orders(staff=staff)
print(qs)
except Exception as e:
print(e)
print(qs)
pass
paginator = Paginator(qs, 30)
@ -4322,15 +4315,13 @@ class EstimateListView(LoginRequiredMixin, PermissionRequiredMixin, ListView):
context = super().get_context_data(**kwargs)
dealer = get_object_or_404(models.Dealer, slug=self.kwargs["dealer_slug"])
staff = getattr(self.request.user.staffmember, "staff", None)
if any([self.request.is_dealer ,self.request.is_manager ,self.request.is_accountant]):
qs = models.ExtraInfo.objects.filter(
dealer=dealer,
content_type=ContentType.objects.get_for_model(EstimateModel),
related_content_type=ContentType.objects.get_for_model(models.Staff),
)
eqs=qs
print(qs)
)
elif self.request.is_staff and self.request.is_sales:
qs = models.ExtraInfo.objects.filter(
dealer=dealer,
@ -4338,19 +4329,15 @@ class EstimateListView(LoginRequiredMixin, PermissionRequiredMixin, ListView):
related_content_type=ContentType.objects.get_for_model(models.Staff),
related_object_id=staff.pk,
)
context["staff_estimates"] = qs
context["staff_estimates"] = EstimateModel.objects.filter(pk__in=[x.content_object.pk for x in qs])
return context
def get_queryset(self):
dealer = get_user_type(self.request)
entity = dealer.entity
status = self.request.GET.get("status")
queryset = entity.get_estimates()
if status:
queryset = queryset.filter(status=status)
return queryset
@ -4462,7 +4449,7 @@ def create_estimate(request, dealer_slug, slug=None):
for item in items_list:
car_instance = models.Car.objects.filter(
hash=item.get("item_id"),
finances__is_sold=False,
# finances__is_sold=False,
colors__isnull=False,
finances__isnull=False,
finances__marked_price__gt=1,
@ -10444,3 +10431,155 @@ def bulk_update_car_price(request):
class InventoryListView(InventoryListViewBase):
template_name = "inventory/list.html"
permission_required = ["django_ledger.view_purchaseordermodel"]
@login_required
def purchase_report_view(request,dealer_slug):
pos = request.entity.get_purchase_orders()
data = []
total_po_amount=0
total_po_cars=0
for po in pos:
items = [{"total":x.total_amount,"q":x.quantity} for x in po.get_itemtxs_data()[0].all()]
po_amount=0
po_quantity=0
for item in items:
po_amount+=item["total"]
po_quantity+=item["q"]
total_po_amount+=po_amount
total_po_cars+=po_quantity
bills=po.get_po_bill_queryset()
vendors=set([bill.vendor.vendor_name for bill in bills])
vendors_str = ", ".join(sorted(list(vendors))) if vendors else "N/A"
data.append({"po_number":po.po_number,"po_created":po.created,"po_status":po.po_status,"po_fulfilled_date":po.date_fulfilled,"po_amount":po_amount,
"po_quantity":po_quantity,"vendors_str":vendors_str})
current_time = timezone.now().strftime("%Y-%m-%d %H:%M:%S")
context={
"dealer":request.entity.name,
"time":current_time,
"data":data,
"total_po_amount":total_po_amount,
"total_po_cars":total_po_cars,
"current_time":current_time
}
return render(request,'ledger/reports/purchase_report.html',context)
def purchase_report_csv_export(request,dealer_slug):
response = HttpResponse(content_type='text/csv')
current_time = timezone.now().strftime("%Y-%m-%d_%H%M%S")
filename = f"purchase_report_{dealer_slug}_{current_time}.csv"
response['Content-Disposition'] = f'attachment; filename="{filename}"'
writer = csv.writer(response)
header = [
'PO Number',
'Created Date',
'Status',
'Fulfilled Date',
'PO Amount',
'PO Quantity',
'Vendors'
]
writer.writerow(header)
pos = request.entity.get_purchase_orders()
for po in pos:
po_amount = 0
po_quantity = 0
items = [{"total":x.total_amount,"q":x.quantity} for x in po.get_itemtxs_data()[0].all()]
for item in items:
po_amount += item["total"]
po_quantity += item["q"]
bills = po.get_po_bill_queryset()
vendors = set([bill.vendor.vendor_name for bill in bills ])
vendors_str = ", ".join(sorted(list(vendors))) if vendors else "N/A"
writer.writerow([
po.po_number,
po.created.strftime("%Y-%m-%d %H:%M:%S") if po.created else '',
po.get_po_status_display(),
po.date_fulfilled.strftime("%Y-%m-%d") if po.date_fulfilled else '',
f"{po_amount:.2f}",
po_quantity,
vendors_str
])
return response
@login_required
def car_sale_report_view(request,dealer_slug):
dealer = get_object_or_404(models.Dealer, slug=dealer_slug)
cars_sold = models.Car.objects.filter(dealer=dealer,status='sold')
current_time = timezone.now().strftime("%Y-%m-%d %H:%M:%S")
context={'cars_sold':cars_sold,'current_time':current_time }
return render(request,'ledger/reports/car_sale_report.html',context)
def car_sale_report_csv_export(request,dealer_slug):
response = HttpResponse(content_type='text/csv')
current_time = timezone.now().strftime("%Y-%m-%d %H:%M:%S")
filename = f"sales_report_{dealer_slug}_{current_time}.csv"
response['Content-Disposition'] = f'attachment; filename="{filename}"'
writer = csv.writer(response)
header=[
'Make',
'VIN',
'Model',
'Year',
'Serie',
'Trim',
'Mileage',
'Stock Type',
'Created Date',
'Sold Date',
'Cost Price',
'Marked Price',
'Discount Amount',
'Selling Price',
'Tax Amount',
'Invoice Number',
]
writer.writerow(header)
dealer=get_object_or_404(models.Dealer,slug=dealer_slug)
cars_sold=models.Car.objects.filter(dealer=dealer,status='sold')
for car in cars_sold:
writer.writerow([
car.vin,
car.id_car_make.name,
car.id_car_model.name,
car.year,
car.id_car_serie.name,
car.id_car_trim.name,
car.mileage,
car.stock_type,
car.created_at.strftime("%Y-%m-%d %H:%M:%S") if car.created_at else '',
car.sold_date.strftime("%Y-%m-%d %H:%M:%S") if car.created_at else '',
car.finances.cost_price,
car.finances.marked_price,
car.finances.discount_amount,
car.finances.selling_price,
car.finances.vat_amount,
car.item_model.invoicemodel_set.first().invoice_number
])
return response

View File

@ -5,7 +5,7 @@
<div class="content">
<h2 class="mb-5">{{ _("Notifications") }}</h2>
<div class="d-flex justify-content-end mb-3">
<a href="{% url 'mark_all_notifications_as_read' %}" class="btn btn-primary"><i class="far fa-envelope fs-8 me-2"></i>{{ _("Mark all as read") }}</a>
<a href="{% url 'mark_all_notifications_as_read' %}" class="btn btn-phoenix-primary"><i class="far fa-envelope fs-8 me-2"></i>{{ _("Mark all as read") }}</a>
</div>
{% if notifications %}
<div class="mx-n4 mx-lg-n6 mb-5 border-bottom">

View File

@ -9,7 +9,7 @@
{% endif %}
{% endblock %}
{% block content %}
<div class="container-fluid">
<div class="container-fluid mt-4 mb-3">
<div class="row g-3 mb-4 align-items-center">
<div class="col">
<h2 class="mb-0">

View File

@ -12,8 +12,8 @@
<div class="col-lg-8 col-md-10">
<div class="card shadow-sm border-0 rounded-3">
<div class="card-header bg-gray-200 py-3 border-0 rounded-top-3">
<h3 class="mb-0 fs-4 text-center text-white">
{{ _("Update Dealer Information") }}
<h3 class="mb-0 fs-4 text-center">
{{ _("Update Dealer Information") }}<span class="fas fa-car ms-2 text-primary"></span>
</h3>
</div>
<div class="card-body bg-light-subtle">

View File

@ -16,7 +16,7 @@
</div>
</footer> {% endcomment %}
<footer class="footer position-absolute fs-9 bg-info-subtle">
{% comment %} <footer class="footer position-absolute fs-9 bg-info-subtle">
<div class="row g-0 justify-content-between align-items-center h-100">
<div class="col-12 col-sm-auto text-center text-warning">
<span class="text-body "> © 2025 {{ _("All right reserved")}}</span>
@ -31,9 +31,9 @@
<span class="fas fa-registered fs-10 fw-light text-opacity-85 text-warning"></span>
</div>
</div>
</footer>
</footer> {% endcomment %}
{% comment %} <footer class="footer position-absolute fs-9 bg-gray-900 text-white">
<footer class="footer position-absolute fs-9 bg-white text-secondary">
<div class="row g-0 justify-content-between align-items-center h-100">
<div class="col-12 col-sm-auto text-center">
<span class="text-body"> © 2025 {{ _("All right reserved")}}</span>
@ -43,11 +43,11 @@
<div class="col-12 col-sm-auto text-center ">
<span>{{ _("Powered by")}} </span>
<span>
<a class="mx-1 text-white" href="https://tenhal.sa">
<a class="mx-1 text-secondary" href="https://tenhal.sa">
<span>TENHAL</span>&nbsp;|&nbsp;<span>تنحل</span>
</a>
<span>
<span class="uil-trademark-circle fs-10"></span>
<span class="fas fa-registered fs-10 fw-light text-opacity-85 text-secondary"></span>
</div>
</div>
</footer> {% endcomment %}
</footer>

View File

@ -346,6 +346,28 @@
<i class="fa-solid fa-scale-balanced"></i><span class="nav-link-text">{% trans 'Balance Sheet'|capfirst %}</span>
</div>
</a>
<!--car purchase report-->
{% if request.user.is_authenticated %}
<a class="nav-link" href="{% url 'po-report' request.dealer.slug %}">
{% else %}
<a class="nav-link" href="#">
{% endif %}
<div class="d-flex align-items-center">
<i class="fas fa-shopping-cart"></i><span class="nav-link-text">{% trans 'Car purchase Report'|capfirst %}</span>
</div>
</a>
<!--car sale report-->
{% if request.user.is_authenticated %}
<a class="nav-link" href="{% url 'car-sale-report' request.dealer.slug %}">
{% else %}
<a class="nav-link" href="#">
{% endif %}
<div class="d-flex align-items-center">
<i class="fas fa-car"></i><span class="nav-link-text">{% trans 'Car Sale Report'|capfirst %}</span>
</div>
</a>
</li>
</ul>
</div>

View File

@ -0,0 +1,111 @@
{% extends 'base.html' %}
{% load i18n %}
{% load static %}
{% block title %}
{{ _("Car Sale Report") |capfirst }}
{% endblock title %}
{% block content%}
<div class="container-fluid report-container">
<header class="report-header text-center">
<h1 class="display-4">Car Sale Report <span class="fas fa-car mx-2 text-primary"></span><span class="fas fa-money-bill mx-2 text-primary"></span></h1>
<p class="lead text-muted"><strong>{{dealer}}</strong></p>
<p class="text-muted">Report Date: {{current_time}}</p>
</header>
<main>
<section id="summary" class="mb-1">
<h2 class="section-heading mb-2">Report Summary</h2>
<div class="row ">
<div class="col-md-6 col-lg-4 mb-2">
<div class="card summary-card">
<div class="card-body">
<h5 class="card-title text-primary">Total Revenue Amount<span class="fas fa-solid fa-dollar-sign mx-1"></span><span class="fas fa-car"></span></h5>
<p class="card-text fs-4 fw-bold">120000000</p>
</div>
</div>
</div>
<div class="col-md-6 col-lg-4 mb-2">
<div class="card summary-card">
<div class="card-body">
<h5 class="card-title text-primary">Total Vat Amount<span class="fas fa-solid fa-percent mx-1"></span><span class="fas fa-car"></span></h5>
<p class="card-text fs-4 fw-bold">12000</p>
</div>
</div>
</div>
<div class="col-md-6 col-lg-4 mb-2">
<div class="card summary-card">
<div class="card-body">
<h5 class="card-title text-primary">Total Discount Amount <span class="fas fa-solid fa-tag mx-1"></span><span class="fas fa-car"></span></h5>
<p class="card-text fs-4 fw-bold">12000</p>
</div>
</div>
</div>
</div>
</section>
<section id="purchase-details" class="mb-3">
<h2 class="section-heading">Detailed Purchase List</h2>
<div class="d-flex justify-content-end mb-3 d-print-none">
<a href="{% url 'car-sale-report-csv-export' request.dealer.slug %}" class="btn btn-phoenix-primary">
<i class="bi bi-download me-2"></i> Download as CSV
</a>
</div>
<div class="table-responsive">
<table class="table table-striped table-hover table-bordered table-sm">
<thead >
<tr>
<th>VIN</th>
<th>Make</th>
<th>Model</th>
<th>Year</th>
<th>Serie</th>
<th>Trim</th>
<th>Mileage</th>
<th>Stock Type</th>
<th>Created Date</th>
<th>Sold Date</th>
<th>Cost Price</th>
<th>Marked Price</th>
<th>Discount Amount</th>
<th>Selling Price</th>
<th>Tax Amount</th>
<th>Invoice Number</th>
</tr>
</thead>
<tbody>
{% for car in cars_sold%}
<tr>
<td class="ps-1">{{car.vin}}</td>
<td>{{car.id_car_make.name}}</td>
<td>{{car.id_car_model.name}}</td>
<td>{{car.year}}</td>
<td>{{car.id_car_serie.name}}</td>
<td>{{car.id_car_trim.name}}</td>
<td>{{car.mileage}}</td>
<td>{{car.stock_type}}</td>
<td>{{car.created_at}}</td>
<td>{{car.sold_date}}</td>
<td>{{car.finances.cost_price}}</td>
<td>{{car.finances.marked_price}}</td>
<td>{{car.finances.discount_amount}}</td>
<td>{{car.finances.selling_price}}</td>
<td>{{car.finances.vat_amount}}</td>
<td>{{car.item_model.invoicemodel_set.first.invoice_number}}</td>
</tr>
{% endfor %}
</tbody>
</table>
</div>
</section>
</main>
</div>
{% endblock %}

View File

@ -0,0 +1,97 @@
{% extends 'base.html' %}
{% load i18n %}
{% load static %}
{% block title %}
{{ _("Car Purchase Report") |capfirst }}
{% endblock title %}
{% block content%}
<div class="container-fluid report-container">
<header class="report-header text-center">
<h1 class="display-4">Car Purchase Report <span class="fas fa-car mx-2 text-primary"></span><span class="fas fa-chart-bar mx-2 text-primary"></span></h1>
<p class="lead text-muted"><strong>{{dealer}}</strong></p>
<p class="text-muted">Report Date: {{current_time}}</p>
</header>
<main>
<section id="summary" class="mb-1">
<h2 class="section-heading mb-2">Report Summary</h2>
<div class="row ">
<div class="col-md-6 col-lg-4 mb-2">
<div class="card summary-card">
<div class="card-body">
<h5 class="card-title text-primary">Total Purchase Amount<span class="fas fa-money-bill ms-1"><span></h5>
<p class="card-text fs-4 fw-bold">{{total_po_amount}}</p>
</div>
</div>
</div>
<div class="col-md-6 col-lg-4 mb-2">
<div class="card summary-card">
<div class="card-body">
<h5 class="card-title text-primary">Total Cars Purchased<span class="fas fa-shopping-bag mx-1"></span><span class="fas fa-car"></span></h5>
<p class="card-text fs-4 fw-bold">{{total_po_cars}}</p>
</div>
</div>
</div>
</div>
</section>
<section id="purchase-details" class="mb-3">
<h2 class="section-heading">Detailed Purchase List</h2>
<div class="d-flex justify-content-end mb-3 d-print-none">
<a href="{% url 'purchase-report-csv-export' request.dealer.slug %}" class="btn btn-phoenix-primary">
<i class="bi bi-download me-2"></i> Download as CSV
</a>
</div>
<div class="table-responsive">
<table class="table table-striped table-hover table-bordered table-sm">
<thead>
<tr>
<th>Purchase ID</th>
<th>Date Created</th>
<th>Status</th>
<th>PO Amount</th>
<th>Date Fulfilled</th>
<th>Created By</th>
<th>Cars Purchased</th>
<th>Vendor</th>
</tr>
</thead>
<tbody>
{% for po in data %}
<tr>
<td class="ps-1">{{po.po_number}}</td>
<td>{{po.po_created}}</td>
<td>{{po.po_status}}</td>
<td>{{po.po_amount}}</td>
<td>{{po.date_fulfilled}}</td>
<td>staff</td>
<td>{{po.po_quantity}}</td>
<td>
{{po.vendors_str}}
</td>
</tr>
{% endfor %}
</tbody>
<tfoot>
<tr>
<td colspan="6" class="text-end fw-bold">Total Purchase:</td>
<td class="fw-bold text-primary">{{total_po_amount}}</td>
</tr>
</tfoot>
</table>
</div>
</section>
</main>
</div>
{% endblock %}

View File

@ -32,7 +32,7 @@
</a>
<!-- Dropdown menu -->
<div class="dropdown-menu dropdown-menu-end notification-dropdown-menu py-0 shadow border navbar-dropdown-caret" id="navbarDropdownNotfication">
<div class="dropdown-menu dropdown-menu-end notification-dropdown-menu py-1 shadow border navbar-dropdown-caret" id="navbarDropdownNotfication">
<div class="card position-relative border-0">
<div class="card-header p-2">
<div class="d-flex justify-content-between">
@ -50,8 +50,8 @@
</div>
</div>
<div class="card-footer p-0 border-top border-translucent border-0">
<div class="my-2 text-center fw-bold fs-10 text-body-tertiary text-opactity-85">
<a class="fw-bolder" href="{% url 'notifications_history' %}">Notification history</a>
<div class="my-3 text-center fw-bold fs-9 text-body-tertiary text-opactity-85">
<a class="fw-bolder" href="{% url 'notifications_history' %}">Notification history<i class="fa-solid fa-history ms-1"></i></a>
</div>
</div>
</div>
@ -181,7 +181,7 @@
</button>
<div class="dropdown-menu py-2">
<a class="dropdown-item mark-as-read" href="#"
data-notification-id="${data.id}">Mark as read</a>
data-notification-id="${data.id}">Mark as read<i class="fa-solid fa-check ms-1"></i></a>
</div>
</div>
</div>

View File

@ -30,8 +30,8 @@
<div class="col-lg-8 col-md-10">
<div class="card shadow-sm border-0 rounded-3">
<div class="card-header bg-gray-200 py-3 border-0 rounded-top-3">
<h3 class="mb-0 fs-4 text-center text-white">
{% trans "Provide billing data"|upper %}
<h3 class="mb-0 fs-4 text-center">
{% trans "Provide billing data"|upper %}<span class="fas fa-file-invoice-dollar ms-2 text-primary"></span>
</h3>
</div>
<div class="card-body bg-light-subtle">