This commit is contained in:
Marwan Alwali 2025-01-15 19:52:16 +03:00
parent c69ebb8164
commit 5442d5a539
22 changed files with 1901 additions and 1137 deletions

View File

@ -336,6 +336,7 @@ class CarReservation(models.Model):
reserved_at = models.DateTimeField(auto_now_add=True, verbose_name=_("Reserved At"))
reserved_until = models.DateTimeField(verbose_name=_("Reserved Until"))
@property
def is_active(self):
return self.reserved_until > now()

View File

@ -12,6 +12,7 @@ from django_ledger.models import (
VendorModel,
)
from . import models
from django.utils.timezone import now
User = get_user_model()
@ -816,4 +817,39 @@ def notify_assigned_staff(sender, instance, created, **kwargs):
models.Notification.objects.create(
user=instance.assigned.user,
message=f"You have been assigned a new lead: {instance.first_name} {instance.last_name}."
)
)
@receiver(post_save, sender=models.CarReservation)
def update_car_status_on_reservation_create(sender, instance, created, **kwargs):
"""
Signal to update the car status to 'reserved' when a reservation is created.
"""
if created:
car = instance.car
car.status = models.CarStatusChoices.RESERVED
car.save()
@receiver(post_delete, sender=models.CarReservation)
def update_car_status_on_reservation_delete(sender, instance, **kwargs):
"""
Signal to update the car status to 'available' when a reservation is deleted.
"""
car = instance.car
# Check if there are no active reservations for the car
if not car.reservations.filter(reserved_until__gt=now()).exists():
car.status = models.CarStatusChoices.AVAILABLE
car.save()
@receiver(post_save, sender=models.CarReservation)
def update_car_status_on_reservation_update(sender, instance, **kwargs):
"""
Signal to update the car status based on the reservation's active status.
"""
car = instance.car
if instance.is_active:
car.status = models.CarStatusChoices.RESERVED
else:
if not car.reservations.filter(reserved_until__gt=now()).exists():
car.status = models.CarStatusChoices.AVAILABLE
car.save()

View File

@ -195,6 +195,44 @@ class Login(views.LoginView):
class HomeView(TemplateView):
template_name = "index.html"
def dispatch(self, request, *args, **kwargs):
if (
# not any(hasattr(request.user, attr) for attr in ["dealer", "subdealer"])
not request.user.is_authenticated
):
# messages.error(request, _("You are not associated with any dealer."))
return redirect("welcome")
return super().dispatch(request, *args, **kwargs)
def get_context_data(self, **kwargs):
context = super().get_context_data(**kwargs)
dealer = get_user_type(self.request)
total_cars = models.Car.objects.filter(dealer=dealer).count()
total_reservations = models.CarReservation.objects.filter(
reserved_until__gte=timezone.now()
).count()
cars_in_house = models.CarLocation.objects.filter(
owner=dealer,
).count()
cars_outside = total_cars - cars_in_house
stats = models.CarFinance.objects.aggregate(
total_cost_price=Sum("cost_price"),
total_selling_price=Sum("selling_price"),
)
total_cost_price = stats["total_cost_price"] or 0
total_selling_price = stats["total_selling_price"] or 0
total_profit = total_selling_price - total_cost_price
context["dealer"] = dealer
context["total_cars"] = total_cars
context["cars_in_house"] = cars_in_house
context["cars_outside"] = cars_outside
context["total_reservations"] = total_reservations
context["total_cost_price"] = total_cost_price
context["total_selling_price"] = total_selling_price
context["total_profit"] = total_profit
return context
class TestView(TemplateView):
template_name = "test.html"
@ -529,15 +567,20 @@ class CarColorCreate(LoginRequiredMixin, CreateView):
def inventory_stats_view(request):
dealer = get_user_type(request)
# Base queryset for cars belonging to the dealer
cars = models.Car.objects.filter(dealer=dealer)
# Count for total, reserved, showroom, and unreserved cars
total_cars = cars.count()
reserved_cars = models.CarReservation.objects.count()
# showroom_cars = cars.filter(location='showroom').count()
# unreserved_cars = total_cars - reserved_cars
# Annotate total cars by make, model, and trim
cars = (
models.Car.objects.filter(dealer=dealer)
.select_related("id_car_make", "id_car_model", "id_car_trim")
.annotate(
make_total=Count("id_car_make"),
model_total=Count("id_car_model"),
trim_total=Count("id_car_trim"),
)
cars = cars.select_related("id_car_make", "id_car_model", "id_car_trim").annotate(
make_total=Count("id_car_make"),
model_total=Count("id_car_model"),
trim_total=Count("id_car_trim"),
)
inventory = {}
@ -584,7 +627,8 @@ def inventory_stats_view(request):
except Exception as e:
print(e)
result = {
"total_cars": cars.count(),
"total_cars": total_cars,
"reserved_cars": reserved_cars,
"makes": [
{
"make_id": make_data["make_id"],
@ -1898,6 +1942,7 @@ class EstimateListView(LoginRequiredMixin, ListView):
model = EstimateModel
template_name = "sales/estimates/estimate_list.html"
context_object_name = "estimates"
paginate_by = 10
def get_queryset(self):
dealer = get_user_type(self.request)
@ -2161,6 +2206,7 @@ class InvoiceListView(LoginRequiredMixin, ListView):
model = InvoiceModel
template_name = "sales/invoices/invoice_list.html"
context_object_name = "invoices"
paginate_by = 10
def get_queryset(self):
dealer = get_user_type(self.request)

Binary file not shown.

File diff suppressed because it is too large Load Diff

View File

@ -7,4 +7,11 @@
text-align: center;
/*vertical-align: middle;*/
/*line-height: 22px;*/
}
}
.rtl .fa-chevron-left {
transform: scaleX(-1);
}
.rtl .fa-chevron-right {
transform: scaleX(-1);
}

Binary file not shown.

View File

@ -7,7 +7,7 @@
<div class="row align-items-center justify-content-between g-3 mb-4">
<div class="col-12 col-md-auto">
<h2 class="mb-0">O{{ _("pportunity details")}}</h2>
<h2 class="mb-0">O{{ _("Opportunity details")}}</h2>
</div>
<div class="col-12 col-md-auto d-flex">
<button class="btn btn-phoenix-secondary px-3 px-sm-5 me-2"><span class="fa-solid fa-edit me-sm-2"></span><span class="d-none d-sm-inline">Edit</span></button>

View File

@ -136,42 +136,7 @@
<div class="row align-items-center justify-content-end py-4 pe-0 fs-9">
<!-- Optional: Pagination -->
{% if is_paginated %}
<nav aria-label="Page navigation">
<ul class="pagination pagination-sm justify-content-center">
{% if page_obj.has_previous %}
<li class="page-item py-0">
<a class="page-link" href="?page={{ page_obj.previous_page_number }}" aria-label="Previous">
<span aria-hidden="true">&laquo;</span>
</a>
</li>
{% else %}
<li class="page-item disabled">
<a class="page-link" href="#" aria-label="Previous">
<span aria-hidden="true">&laquo;</span>
</a>
</li>
{% endif %}
{% for num in page_obj.paginator.page_range %}
{% if page_obj.number == num %}
<li class="page-item active"><a class="page-link" href="?page={{ num }}">{{ num }}</a></li>
{% else %}
<li class="page-item"><a class="page-link" href="?page={{ num }}">{{ num }}</a></li>
{% endif %}
{% endfor %} {% if page_obj.has_next %}
<li class="page-item">
<a class="page-link" href="?page={{ page_obj.next_page_number }}" aria-label="Next">
<span aria-hidden="true">&raquo;</span>
</a>
</li>
{% else %}
<li class="page-item disabled">
<a class="page-link" href="#" aria-label="Next">
<span aria-hidden="true">&raquo;</span>
</a>
</li>
{% endif %}
</ul>
</nav>
{% include 'partials/pagination.html' %}
{% endif %}
</div>
</div>

View File

@ -87,14 +87,6 @@
<div class="parent-wrapper label-1">
<ul class="nav collapse parent" data-bs-parent="#navbarVerticalCollapse" id="nv-sales">
<li class="collapsed-nav-item-title d-none">{% trans 'sales'|capfirst %}</li>
<li class="nav-item">
<a class="nav-link" href="{% url 'vendor_list' %}">
<div class="d-flex align-items-center">
<span class="nav-link-icon"><span data-feather="package"></span></span><span class="nav-link-text">{% trans 'vendors'|capfirst %}</span>
</div>
</a>
</li>
<li class="nav-item">
<a class="nav-link" href="{% url 'estimate_create' %}">
<div class="d-flex align-items-center">
@ -165,7 +157,7 @@
<li class="nav-item">
<a class="nav-link" href="{% url 'organization_list' %}">
<div class="d-flex align-items-center">
<span class="nav-link-icon"><span data-feather="activity"></span></span><span class="nav-link-text">{% trans "Organizations"|capfirst %}</span>
<span class="nav-link-icon"><span class="fas fa-city"></span></span><span class="nav-link-text">{% trans "Organizations"|capfirst %}</span>
</div>
</a>
<!-- more inner pages-->
@ -194,23 +186,23 @@
<a class="nav-link dropdown-indicator label-1" href="#nv-financial" role="button" data-bs-toggle="collapse" aria-expanded="false" aria-controls="nv-financial">
<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 data-feather="phone"></span></span><span class="nav-link-text">{% trans 'Financials' %}</span>
<span class="nav-link-icon"><span class="fas fa-money-check-alt"></span></span><span class="nav-link-text">{% trans 'Financials' %}</span>
</div>
</a>
<div class="parent-wrapper label-1">
<ul class="nav collapse parent" data-bs-parent="#navbarVerticalCollapse" id="nv-financial">
<li class="collapsed-nav-item-title d-none">{% trans 'crm'|upper %}</li>
<li class="collapsed-nav-item-title d-none">{% trans 'Financials' %}</li>
<li class="nav-item">
<a class="nav-link" href="{% url 'account_list' %}">
<div class="d-flex align-items-center">
<span class="nav-link-icon"><span data-feather="users"></span></span><span class="nav-link-text">{% trans 'Chart of Accounts'|capfirst %}</span>
<span class="nav-link-icon"><span class="fas fa-book-open"></span></span><span class="nav-link-text">{% trans 'Chart of Accounts'|capfirst %}</span>
</div>
</a>
</li>
<li class="nav-item">
<a class="nav-link" href="{% url 'bank_account_list' %}">
<div class="d-flex align-items-center">
<span class="nav-link-icon"><span data-feather="users"></span></span><span class="nav-link-text">{% trans 'Bank Accounts'|capfirst %}</span>
<span class="nav-link-icon"><span data-feather="credit-card"></span></span><span class="nav-link-text">{% trans 'Bank Accounts'|capfirst %}</span>
</div>
</a>
</li>
@ -230,7 +222,13 @@
</a>
<!-- more inner pages-->
</li>
<li class="nav-item">
<a class="nav-link" href="{% url 'vendor_list' %}">
<div class="d-flex align-items-center">
<span class="nav-link-icon"><span data-feather="package"></span></span><span class="nav-link-text">{% trans 'vendors'|capfirst %}</span>
</div>
</a>
</li>
</ul>
</div>
</div>

View File

@ -3,8 +3,101 @@
{% block content %}
<div class="row">
<div class="px-3 mb-6">
<div class="row justify-content-between">
<div class="col-12 text-start pb-4 my-3"></div>
</div>
<div class="row justify-content-between">
<div class="col-4 text-center pb-4 pb-xxl-0 align-content-center">
<h1 class="fs-5 pt-3">{{ dealer.get_local_name }}</h1>
</div>
<div class="col-2 text-center border-translucent border-start-xxl border-end-xxl-0 border-bottom-xxl-0 border-end border-bottom pb-4 pb-xxl-0 align-content-center">
<span class="uil fs-5 lh-1 uil-car text-primary"></span>
<h1 class="fs-5 pt-3">{{ total_cars }}</h1>
<p class="fs-9 mb-0">{% trans "Total Cars in Inventory" %}</p>
</div>
<div class="col-2 text-center border-translucent border-start-xxl border-end-xxl-0 border-bottom-xxl-0 border-end-md border-bottom pb-4 pb-xxl-0 align-content-center">
<span class="uil fs-5 lh-1 uil-lock-alt text-info"></span>
<h1 class="fs-5 pt-3">{{ total_reservations }}</h1>
<p class="fs-9 mb-0">{{ _("Reserved")}}</p>
</div>
<div class="col-2 text-center border-translucent border-start-xxl border-bottom-xxl-0 border-bottom border-end border-end-md-0 pb-4 pb-xxl-0 pt-4 pt-md-0 align-content-center">
<span class="uil fs-5 lh-1 uil-car-wash text-success-dark"></span>
<h1 class="fs-5 pt-3">{{ cars_in_house}}</h1>
<p class="fs-9 mb-0">{{ _("In Our Showroom")}}</p>
</div>
<div class="col-2 text-center border-translucent border-start-xxl border-end-md border-end-xxl-0 border-bottom border-bottom-md-0 pb-4 pb-xxl-0 pt-4 pt-xxl-0 align-content-center">
<span class="uil fs-5 lh-1 uil-car-slash text-danger-dark"></span>
<h1 class="fs-5 pt-3">{{ cars_outside }}</h1>
<p class="fs-9 mb-0">{{ _("Outside Showroom")}}</p>
</div>
</div>
</div>
<div class="row gx-3">
<div class="col-xxl-6">
<div class="row gx-7 pe-xxl-3">
<div class="col-12 col-xl-7 col-xxl-12">
<div class="row g-3 mb-3">
<div class="col-sm-6 col-md-4 col-xl-3 col-xxl-4 align-content-center">
<div class="card h-75">
<div class="card-body">
<div class="d-flex d-sm-block justify-content-between">
<div class="border-translucent mb-sm-4">
<div class="d-flex align-items-center">
<div class="d-flex align-items-center icon-wrapper-sm shadow-primary-100" style="transform: rotate(-7.45deg);"><span class="fa-solid fa-phone-alt text-primary fs-7 z-1 ms-2"></span></div>
<p class="text-body-tertiary fs-9 mb-0 ms-2 mt-3">Outgoing call</p>
</div>
<p class="text-primary mt-2 fs-6 fw-bold mb-0 mb-sm-4">3 <span class="fs-8 text-body lh-lg">Leads Today</span></p>
</div>
</div>
</div>
</div>
</div>
<div class="col-sm-6 col-md-4 col-xl-3 col-xxl-4 align-content-center">
<div class="card h-75">
<div class="card-body">
<div class="d-flex d-sm-block justify-content-between">
<div class="border-translucent mb-sm-4">
<div class="d-flex align-items-center">
<div class="d-flex align-items-center icon-wrapper-sm shadow-info-100" style="transform: rotate(-7.45deg);"><span class="fa-solid fa-calendar text-info fs-7 z-1 ms-2"></span></div>
<p class="text-body-tertiary fs-9 mb-0 ms-2 mt-3">Outgoing meeting</p>
</div>
<p class="text-info mt-2 fs-6 fw-bold mb-0 mb-sm-4">12 <span class="fs-8 text-body lh-lg">This Week</span></p>
</div>
</div>
</div>
</div>
</div>
</div>
</div>
</div>
</div>
<div class="col-xxl-6 align-content-center">
<div class="row g-0">
<div class="card mb-3">
<div class="bg-holder bg-card" style="background-image: url({% static 'images/spot-illustrations/39.png' %}); background-position: bottom right 0px; background-size: auto;"></div>
{% endblock %}
<div class="card-body">
<div>
<div class="col-6 col-xl-12 col-xxl-6 py-4 ps-4 ps-sm-5 ps-xl-0 ps-xxl-5">
<h5 class="text-body mb-4">{% trans 'inventory value'|capfirst %}</h5>
<div class="d-md-flex flex-between-center">
<div class="d-md-flex align-items-center gap-2 order-sm-0 order-md-1">
<span class="fa-solid fa-money-bill-trend-up fs-5 text-warning-light dark__text-opacity-75" data-bs-theme="light"></span>
<div class="d-flex d-md-block gap-2 align-items-center mt-1 mt-md-0">
<p class="fs-9 mb-0 mb-md-2 text-body-tertiary text-nowrap">{% trans "Profits"|upper %}</p>
<h4 class="text-body-highlight mb-0">{{ total_profit }} {% trans 'SAR' %}</h4>
</div>
</div>
<div class="mt-3 mt-md-0">
<h3 class="text-body-highlight mb-2">{{ total_selling_price }} {% trans 'SAR' %}</h3>
</div>
</div>
</div>
</div>
</div>
</div>
</div>
</div>
</div>
{% endblock %}

View File

@ -16,11 +16,11 @@
<script src="https://unpkg.com/@zxing/library@latest"></script>
<script src="https://cdn.jsdelivr.net/npm/tesseract.js@5/dist/tesseract.min.js"></script>
<div class="row-lg">
<div class="row g-3">
<form method="post" id="carForm" class="form needs-validation" novalidate>
{% csrf_token %}
<div class="d-flex flex-column min-vh-100">
<div class="d-flex flex-column flex-sm-grow-1 ms-sm-14 p-4">
<div class="d-flex flex-column flex-sm-grow-1 p-4">
<main class="d-grid gap-4 p-1">
<div class="row g-4">
<!-- VIN -->

View File

@ -6,34 +6,7 @@
{% endblock %}
{% block content %}
<div class="px-3 mb-6">
<div class="row justify-content-between">
<div class="col-6 col-md-4 col-xxl-2 text-center border-translucent border-start-xxl border-end-xxl-0 border-bottom-xxl-0 border-end border-bottom pb-4 pb-xxl-0 "><span class="uil fs-5 lh-1 uil-car-sideview text-primary"></span>
<h1 class="fs-5 pt-3">{{ inventory.total_cars }}</h1>
<p class="fs-9 mb-0">{% trans "Total Cars in Inventory" %}</p>
</div>
<div class="col-6 col-md-4 col-xxl-2 text-center border-translucent border-start-xxl border-end-xxl-0 border-bottom-xxl-0 border-end-md border-bottom pb-4 pb-xxl-0"><span class="uil fs-5 lh-1 uil-lock-alt text-info"></span>
<h1 class="fs-5 pt-3">2</h1>
<p class="fs-9 mb-0">{{ _("Reserved")}}</p>
</div>
<div class="col-6 col-md-4 col-xxl-2 text-center border-translucent border-start-xxl border-bottom-xxl-0 border-bottom border-end border-end-md-0 pb-4 pb-xxl-0 pt-4 pt-md-0"><span class="uil fs-5 lh-1 uil-envelopes text-primary"></span>
<h1 class="fs-5 pt-3">1,366</h1>
<p class="fs-9 mb-0">Emails Delivered</p>
</div>
<div class="col-6 col-md-4 col-xxl-2 text-center border-translucent border-start-xxl border-end-md border-end-xxl-0 border-bottom border-bottom-md-0 pb-4 pb-xxl-0 pt-4 pt-xxl-0"><span class="uil fs-5 lh-1 uil-envelope-open text-info"></span>
<h1 class="fs-5 pt-3">1,200</h1>
<p class="fs-9 mb-0">Emails Opened</p>
</div>
<div class="col-6 col-md-4 col-xxl-2 text-center border-translucent border-start-xxl border-end border-end-xxl-0 pb-md-4 pb-xxl-0 pt-4 pt-xxl-0"><span class="uil fs-5 lh-1 uil-envelope-check text-success"></span>
<h1 class="fs-5 pt-3">900</h1>
<p class="fs-9 mb-0">Emails Clicked</p>
</div>
<div class="col-6 col-md-4 col-xxl-2 text-center border-translucent border-start-xxl border-end-xxl pb-md-4 pb-xxl-0 pt-4 pt-xxl-0"><span class="uil fs-5 lh-1 uil-envelope-block text-danger"></span>
<h1 class="fs-5 pt-3">500</h1>
<p class="fs-9 mb-0">Emails Bounce</p>
</div>
</div>
</div>
<div class="row mt-4">
<!-- Total Cars -->

View File

@ -10,7 +10,7 @@
<div class="card">
<div class="card-header">
{% if service.pk %}
{{ _("UpdateService") }}
{{ _("Update Service") }}
{% else %}
{{ _("Add Service") }}
{% endif %}

View File

@ -37,7 +37,7 @@
</tr>
{% empty %}
<tr>
<td colspan="6" class="text-center">{% trans "No Invoice Found" %}</td>
<td colspan="6" class="text-center">{% trans "No Bank Accounts Found" %}</td>
</tr>
{% endfor %}
</tbody>

View File

@ -180,7 +180,7 @@
<div class="row mt-4 mx-4">
<div class="d-flex justify-content-between mb-2 p-6">
<span></span>
<h3 class="text-center">{% trans "Accounts" %}</h3>
<h3 class="text-center">{% trans "Chart of Accounts" %}</h3>
<a href="{% url 'account_create' %}" class="btn btn-sm btn-success ">{% trans "Add Account" %}</a>
</div>
<div class="mx-n4 px-4 mx-lg-n6 px-lg-6 bg-body-emphasis pt-7 border-y">

View File

@ -1,16 +1,30 @@
{% load i18n static %}
<div class="card-footer bg-light">
<div class="pagination my-3">
<nav aria-label="Page navigation">
<ul class="pagination pagination-sm justify-content-center mb-0">
{% if page_obj.has_previous %}
<li class="page-item">
<a class="page-link" href="?page={{ page_obj.previous_page_number }}" aria-label="{% trans 'Previous' %}">
<span aria-hidden="true">&laquo;</span>
<span aria-hidden="true">
{% if LANGUAGE_CODE == 'ar' %}
<span class="fa fa-chevron-right"></span>
{% else %}
<span class="fa fa-chevron-left"></span>
{% endif %}
</span>
</a>
</li>
{% else %}
<li class="page-item disabled">
<span class="page-link" aria-hidden="true">&laquo;</span>
<span class="page-link" aria-hidden="true">
{% if LANGUAGE_CODE == 'ar' %}
<span class="fa fa-chevron-right"></span>
{% else %}
<span class="fa fa-chevron-left"></span>
{% endif %}
</span>
</li>
{% endif %}
{% for num in page_obj.paginator.page_range %}
@ -27,12 +41,24 @@
{% if page_obj.has_next %}
<li class="page-item">
<a class="page-link" href="?page={{ page_obj.next_page_number }}" aria-label="{% trans 'Next' %}">
<span aria-hidden="true">&raquo;</span>
<span aria-hidden="true">
{% if LANGUAGE_CODE == 'ar' %}
<span class="fa fa-chevron-left"></span>
{% else %}
<span class="fa fa-chevron-right"></span>
{% endif %}
</span>
</a>
</li>
{% else %}
<li class="page-item disabled">
<span class="page-link" aria-hidden="true">&raquo;</span>
<span class="page-link" aria-hidden="true">
{% if LANGUAGE_CODE == 'ar' %}
<span class="fa fa-chevron-left"></span>
{% else %}
<span class="fa fa-chevron-right"></span>
{% endif %}
</span>
</li>
{% endif %}
</ul>

View File

@ -59,7 +59,10 @@
</tbody>
</table>
</div>
<div class="d-flex justify-content-center">
<div class="d-flex justify-content-center">
{% if is_paginated %}
{% include 'partials/pagination.html' %}
{% endif %}
</div>
</div>
</div>

View File

@ -69,7 +69,10 @@
</tbody>
</table>
</div>
<div class="d-flex justify-content-center">
<div class="d-flex justify-content-center">
{% if is_paginated %}
{% include 'partials/pagination.html' %}
{% endif %}
</div>
</div>
</div>