Merge pull request 'Message in the header' (#194) from frontend into main

Reviewed-on: #194
This commit is contained in:
ismail 2025-08-25 11:51:43 +03:00
commit 2c6249d74c
18 changed files with 126 additions and 59 deletions

View File

@ -177,7 +177,7 @@ class DealerForm(forms.ModelForm):
""" """
phone_number = SaudiPhoneNumberField(label=_("Phone Number")) # phone_number = SaudiPhoneNumberField(label=_("Phone Number"))
class Meta: class Meta:
model = Dealer model = Dealer
@ -193,7 +193,7 @@ class DealerForm(forms.ModelForm):
class CustomerForm(forms.ModelForm): class CustomerForm(forms.ModelForm):
phone_number = SaudiPhoneNumberField(label=_("Phone Number")) # phone_number = SaudiPhoneNumberField(label=_("Phone Number"))
class Meta: class Meta:
model = Customer model = Customer
@ -563,7 +563,7 @@ class VendorForm(forms.ModelForm):
:type Meta: Type[VendorForm.Meta] :type Meta: Type[VendorForm.Meta]
""" """
phone_number = SaudiPhoneNumberField(label=_("Phone Number")) # phone_number = SaudiPhoneNumberField(label=_("Phone Number"))
contact_person = forms.CharField(label=_("Contact Person")) contact_person = forms.CharField(label=_("Contact Person"))
class Meta: class Meta:
@ -652,7 +652,7 @@ class RepresentativeForm(forms.ModelForm):
:type Meta.fields: list of str :type Meta.fields: list of str
""" """
phone_number = SaudiPhoneNumberField(label=_("Phone Number")) # phone_number = SaudiPhoneNumberField(label=_("Phone Number"))
class Meta: class Meta:
model = Representative model = Representative

View File

@ -443,7 +443,7 @@ urlpatterns = [
path( path(
"<slug:dealer_slug>/inventory/<slug:entity_slug>/list/", "<slug:dealer_slug>/inventory/<slug:entity_slug>/list/",
views.InventoryListView.as_view(), views.InventoryListView.as_view(),
name="inventort_list", name="inventory_list",
), ),
# Sales URLs quotation_create # Sales URLs quotation_create
# path( # path(

View File

@ -1588,7 +1588,7 @@ def _post_sale_and_cogs(invoice, dealer):
net_car_price = Decimal(data['discounted_price']) net_car_price = Decimal(data['discounted_price'])
net_additionals_price = Decimal(data['additional_services']['total']) net_additionals_price = Decimal(data['additional_services']['total'])
vat_amount = Decimal(data['vat_amount']) vat_amount = Decimal(data['vat_amount'])
grand_total = net_car_price + net_additionals_price + vat_amount grand_total = net_car_price + car.get_additional_services_amount_ + vat_amount
cost_total = Decimal(car.cost_price) cost_total = Decimal(car.cost_price)
discount_amount =Decimal(data['discount_amount']) discount_amount =Decimal(data['discount_amount'])
@ -1608,7 +1608,8 @@ def _post_sale_and_cogs(invoice, dealer):
journal_entry=je_sale, journal_entry=je_sale,
account=cash_acc, account=cash_acc,
amount=grand_total, amount=grand_total,
tx_type='debit' tx_type='debit',
description='Debit to Cash on Hand'
) )
# # Cr A/R (clear the receivable) # # Cr A/R (clear the receivable)
@ -1624,7 +1625,8 @@ def _post_sale_and_cogs(invoice, dealer):
journal_entry=je_sale, journal_entry=je_sale,
account=vat_acc, account=vat_acc,
amount=vat_amount, amount=vat_amount,
tx_type='credit' tx_type='credit',
description="Credit to Tax Payable"
) )
# Cr Sales Car # Cr Sales Car
@ -1633,7 +1635,7 @@ def _post_sale_and_cogs(invoice, dealer):
account=car_rev, account=car_rev,
amount=net_car_price, amount=net_car_price,
tx_type='credit', tx_type='credit',
description="Car Sale" description=" Credit to Car Sales"
) )
if car.get_additional_services_amount > 0: if car.get_additional_services_amount > 0:
@ -1642,14 +1644,16 @@ def _post_sale_and_cogs(invoice, dealer):
journal_entry=je_sale, journal_entry=je_sale,
account=add_rev, account=add_rev,
amount=car.get_additional_services_amount, amount=car.get_additional_services_amount,
tx_type='credit' tx_type='credit',
description="Credit to After-Sales Services"
) )
TransactionModel.objects.create( TransactionModel.objects.create(
journal_entry=je_sale, journal_entry=je_sale,
account=vat_acc, account=vat_acc,
amount=car.get_additional_services_vat, amount=car.get_additional_services_vat,
tx_type='credit', tx_type='credit',
description="Additional Services VAT" description="Credit to Tax Payable (Additional Services)"
) )
# ------------------------------------------------------------------ # ------------------------------------------------------------------
@ -1668,7 +1672,7 @@ def _post_sale_and_cogs(invoice, dealer):
journal_entry=je_cogs, journal_entry=je_cogs,
account=cogs_acc, account=cogs_acc,
amount=cost_total, amount=cost_total,
tx_type='debit' tx_type='debit',
) )
# Cr Inventory # Cr Inventory

View File

@ -3,7 +3,7 @@ from django.utils.translation import gettext_lazy as _
import re import re
class SaudiPhoneNumberValidator(RegexValidator): class SaudiPhoneNumberValidator(RegexValidator):
def __init__(self): def __init__(self, *args, **kwargs):
super().__init__( super().__init__(
regex=r"^(\+9665|05)[0-9]{8}$", regex=r"^(\+9665|05)[0-9]{8}$",
message=_("Enter a valid Saudi phone number (05XXXXXXXX or +9665XXXXXXXX)"), message=_("Enter a valid Saudi phone number (05XXXXXXXX or +9665XXXXXXXX)"),

View File

@ -10617,10 +10617,12 @@ class PurchaseOrderListView(LoginRequiredMixin, PermissionRequiredMixin, ListVie
query = self.request.GET.get("q") query = self.request.GET.get("q")
qs = self.model.objects.filter(entity=dealer.entity) qs = self.model.objects.filter(entity=dealer.entity)
if query: if query:
qs = apply_search_filters(qs, query) qs=qs.filter(Q(po_number__icontains=query)|Q(po_status__icontains=query)|Q(po_title__icontains=query))
return qs
return qs return qs
def get_context_data(self, **kwargs): def get_context_data(self, **kwargs):
context = super().get_context_data(**kwargs)
dealer = get_user_type(self.request) dealer = get_user_type(self.request)
vendors=models.Vendor.objects.filter(dealer=dealer) vendors=models.Vendor.objects.filter(dealer=dealer)
context = super().get_context_data(**kwargs) context = super().get_context_data(**kwargs)

View File

@ -4,6 +4,13 @@ from decimal import Decimal
def run(): def run():
# Create quotas first # Create quotas first
free_quota = Quota.objects.create(
codename="free_quota",
name="Free Features",
description="Free plan features",
is_boolean=True,
url="pricing",
)
basic_quota = Quota.objects.create( basic_quota = Quota.objects.create(
codename="basic_quota", codename="basic_quota",
name="Basic Features", name="Basic Features",
@ -27,6 +34,18 @@ def run():
is_boolean=True, is_boolean=True,
url="pricing", url="pricing",
) )
# Create the plans
free_plan = Plan.objects.create(
name="Free",
description="Free plan with limited features",
price=Decimal("0.00"), # 0
period=7, # 1 week
default=True,
available=True,
visible=True,
order=1,
)
free_plan.quotas.add(free_quota)
# Create the plans # Create the plans
basic_plan = Plan.objects.create( basic_plan = Plan.objects.create(
@ -39,7 +58,7 @@ def run():
visible=True, visible=True,
order=1, order=1,
) )
basic_plan.quotas.add(basic_quota) basic_plan.quotas.add(basic_quota,free_quota)
pro_plan = Plan.objects.create( pro_plan = Plan.objects.create(
name="Professional", name="Professional",
@ -50,7 +69,7 @@ def run():
visible=True, visible=True,
# order=2 # order=2
) )
pro_plan.quotas.add(basic_quota, pro_quota) pro_plan.quotas.add(free_quota,basic_quota, pro_quota)
premium_plan = Plan.objects.create( premium_plan = Plan.objects.create(
name="Premium", name="Premium",
@ -61,4 +80,4 @@ def run():
visible=True, visible=True,
order=3, order=3,
) )
premium_plan.quotas.add(basic_quota, pro_quota, premium_quota) premium_plan.quotas.add(free_quota,basic_quota, pro_quota, premium_quota)

View File

@ -138,7 +138,8 @@ html[dir="rtl"] .form-icon-container .form-control {
top: 50%; top: 50%;
left: 50%; left: 50%;
transform: translate(-50%, -50%); transform: translate(-50%, -50%);
z-index: 10; z-index: 9999;
pointer-events: none;
} }
#spinner-bg { #spinner-bg {
@ -150,11 +151,14 @@ html[dir="rtl"] .form-icon-container .form-control {
background-color: rgba(255, 255, 255, 0.7); background-color: rgba(255, 255, 255, 0.7);
opacity: 0; opacity: 0;
transition: opacity 500ms ease-in; transition: opacity 500ms ease-in;
z-index: 5; visibility: hidden;
z-index: 10000;
pointer-events: none;
} }
#spinner-bg.htmx-request { #spinner-bg.htmx-request {
opacity: .8; opacity: .8;
visibility: visible;
} }

Binary file not shown.

After

Width:  |  Height:  |  Size: 4.5 MiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 21 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 4.4 MiB

View File

@ -8,7 +8,7 @@
{% trans "Aging Inventory" %} {% trans "Aging Inventory" %}
<i class="fas fa-box-open text-danger ms-2"></i> <i class="fas fa-box-open text-danger ms-2"></i>
</h2> </h2>
<h4 class="text-muted mb-3 ">{% trans "Aging Inventory Total" %} :: <span class=" text-danger">{{total_aging_inventory_value}}<span class="icon-saudi_riyal"></span></span></h4> <h4 class="text-muted mb-3 ">{% trans "Aging Inventory Total" %} :: <span class=" text-danger">{{total_aging_inventory_value|default:0.00}}<span class="icon-saudi_riyal"></span></span></h4>
<p class="text-muted mb-0">{% trans "Cars in inventory for more than 60 days." %}</p> <p class="text-muted mb-0">{% trans "Cars in inventory for more than 60 days." %}</p>
</div> </div>

View File

@ -138,11 +138,11 @@
</div> {% endcomment %} </div> {% endcomment %}
<div class="d-flex justify-content-end gap-2"> <div class="d-flex justify-content-end gap-2">
{% if not dealer.user.userplan %} {% if not dealer.user.userplan %}
<a href="{% url 'pricing_page' request.dealer.slug %}" class="btn btn-success"><span class="fas fa-cart-plus me-2"></span>{{ _("Subscribe Now") }}</a> <a href="{% url 'pricing_page' request.dealer.slug %}" class="btn btn-outline-primary"><span class="fas fa-cart-plus me-2"></span>{{ _("Subscribe Now") }}</a>
{% elif dealer.user.userplan.is_expired %} {% elif dealer.user.userplan.is_expired %}
<a href="{% url 'pricing_page' request.dealer.slug %}" class="btn btn-warning"><span class="fas fa-redo-alt me-2"></span>{{ _("Renew") }}</a> <a href="{% url 'pricing_page' request.dealer.slug %}" class="btn btn-outline-warning"><span class="fas fa-redo-alt me-2"></span>{{ _("Renew") }}</a>
{% elif dealer.user.userplan.plan.name != "Enterprise" %} {% elif dealer.user.userplan.plan.name != "Enterprise" %}
<a href="{% url 'pricing_page' request.dealer.slug %}" class="btn btn-primary"><span class="fas fa-rocket me-2"></span>{{ _("Upgrade Plan") }}</a> <a href="{% url 'pricing_page' request.dealer.slug %}" class="btn btn-outline-primary"><span class="fas fa-rocket me-2"></span>{{ _("Upgrade Plan") }}</a>
{% endif %} {% endif %}
</div> </div>
</div> </div>

View File

@ -6,7 +6,7 @@
<ul class="navbar-nav flex-column" id="navbarVerticalNav" hx-boost="true" hx-target="#main_content" hx-select="#main_content" hx-swap="outerHTML" hx-select-oob="#toast-container" hx-indicator="#spinner"> <ul class="navbar-nav flex-column" id="navbarVerticalNav" hx-boost="true" hx-target="#main_content" hx-select="#main_content" hx-swap="outerHTML" hx-select-oob="#toast-container" hx-indicator="#spinner">
<li class="nav-item"> <li class="nav-item">
<p class="navbar-vertical-label text-primary fs-8 text-truncate">{{request.dealer|default:"Apps"}}</p> <p class="navbar-vertical-label text-primary fs-8 text-truncate">{{request.dealer|default:"Apps"}}</p>
<hr class="navbar-vertical-line" /> <hr class="navbar-vertical-line">
{% if perms.inventory.can_view_inventory %} {% if perms.inventory.can_view_inventory %}
<div class="nav-item-wrapper"> <div class="nav-item-wrapper">
<a id="inventory-nav" class="nav-link dropdown-indicator label-1 inventory-nav" href="#nv-inventory" role="button" data-bs-toggle="collapse" aria-expanded="false" aria-controls="nv-inventory"> <a id="inventory-nav" class="nav-link dropdown-indicator label-1 inventory-nav" href="#nv-inventory" role="button" data-bs-toggle="collapse" aria-expanded="false" aria-controls="nv-inventory">
@ -20,13 +20,13 @@
<li class="collapsed-nav-item-title d-none">{% trans "Inventory"|capfirst %}</li> <li class="collapsed-nav-item-title d-none">{% trans "Inventory"|capfirst %}</li>
{% if perms.inventory.add_car %} {% if perms.inventory.add_car %}
<li class="nav-item"> <li class="nav-item">
<a hx-boost="false" id="btn-add-car" class="nav-link btn-add-car" href="{% url 'car_add' request.dealer.slug %}"> <a hx-boost="false" id="btn-add-car" class="nav-link btn-add-car" href="{% url 'car_add' request.dealer.slug %}">
<div class="d-flex align-items-center"> <div class="d-flex align-items-center">
<span class="nav-link-icon"><span class="fas fa-plus-circle"></span></span><span class="nav-link-text">{% trans "add car"|capfirst %}</span> <span class="nav-link-icon"><span class="fas fa-plus-circle"></span></span><span class="nav-link-text">{% trans "add car"|capfirst %}</span>
</div> </div>
</a> </a>
</li>
</li> </li>
{% endif %} {% endif %}
{% if perms.inventory.view_car%} {% if perms.inventory.view_car%}
@ -64,7 +64,7 @@
</a> </a>
</li> </li>
<li class="nav-item"> <li class="nav-item">
<a class="nav-link" href="{% url 'inventort_list' request.dealer.slug request.dealer.entity.slug %}"> <a class="nav-link" href="{% url 'inventory_list' request.dealer.slug request.dealer.entity.slug %}">
<div class="d-flex align-items-center"> <div class="d-flex align-items-center">
<span class="nav-link-icon"><span class="fas fa-boxes"></span></span><span class="nav-link-text">{% trans "Inventory List"|capfirst %}</span> <span class="nav-link-icon"><span class="fas fa-boxes"></span></span><span class="nav-link-text">{% trans "Inventory List"|capfirst %}</span>
</div> </div>
@ -129,7 +129,6 @@
<span class="nav-link-icon"><span class="fas fa-city"></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> </div>
</a> </a>
</li> </li>
{% endif %} {% endif %}
{% comment %} <li class="nav-item"> {% comment %} <li class="nav-item">
@ -425,7 +424,7 @@
aria-label="Toggle Navigation"> aria-label="Toggle Navigation">
<span class="navbar-toggle-icon"><span class="toggle-line"></span></span> <span class="navbar-toggle-icon"><span class="toggle-line"></span></span>
</button> </button>
<a class="navbar-brand me-1 me-sm-3" href="{% url 'home' %}"> <a class="navbar-brand me-1 me-sm-3" href="{% url 'home'%}">
<div class="d-flex align-items-center"> <div class="d-flex align-items-center">
<img class="logo-img d-dark-none" src="{% static 'images/logos/logo-d.png' %}" alt="haikal" width="27" /> <img class="logo-img d-dark-none" src="{% static 'images/logos/logo-d.png' %}" alt="haikal" width="27" />
<img class="logo-img d-light-none" src="{% static 'images/logos/logo.png' %}" alt="haikal" width="27" /> <img class="logo-img d-light-none" src="{% static 'images/logos/logo.png' %}" alt="haikal" width="27" />
@ -434,13 +433,19 @@
</a> </a>
</div> </div>
{% if request.user.is_authenticated%} {% if request.user.is_authenticated%}
<div class="d-flex mx-4 px-4">
<div class="navbar-logo"> <div class="navbar-logo">
<div class="d-flex align-items-center"> <div class="d-flex align-items-center">
<h5 class="text-warning ms-2 d-none d-sm-block">{% trans 'Hello, ' %}{{ request.user.first_name|default:request.dealer.name }} {{ request.user.last_name }}</h5> {% with name_to_display=request.user.first_name|default:request.dealer.name %}
<h6 class="text-info ms-2 d-none d-sm-block fs-7"
data-bs-toggle="tooltip"
data-bs-placement="bottom"
title="{% trans 'Logged in as ' %}{{request.user.username }}">
{% trans 'Hello, ' %}{{ name_to_display }}
</h6>
{% endwith %}
</div>
</div> </div>
</a>
</div>
{% endif %} {% endif %}
<ul class="navbar-nav navbar-nav-icons flex-row gap-2" hx-boost="false"> <ul class="navbar-nav navbar-nav-icons flex-row gap-2" hx-boost="false">

View File

@ -15,7 +15,7 @@
} }
.moving-car { .moving-tenhal {
position: absolute; position: absolute;
font-size: 3rem; font-size: 3rem;
font-weight: bold; font-weight: bold;
@ -39,14 +39,10 @@
{% if inventory.total_cars > 0 %} {% if inventory.total_cars > 0 %}
<div class="row justify-content-between"> <div class="row justify-content-between">
<div class="col-sm-12 "> <div class="col-sm-12 ">
<div class="card border h-100 w-100 p-lg-10"> <div class="card border h-100 w-100 p-lg-10" style="">
{% comment %} <div class="road-container">
<img src="https://encrypted-tbn0.gstatic.com/images?q=tbn:ANd9GcSY0ESBb8625a12EguCyY8j4eL93sY3ibEAuQ&s" alt="Car" id="car" class="img-fluid" style="height:30px; width:30px;">
</div> {% endcomment %}
<div class="road"> <div class="road">
<p class="moving-car ">Powered By Tenhal&nbsp;&nbsp;&nbsp;&nbsp;</p> <p class="moving-tenhal ">&nbsp;&nbsp;&nbsp;&nbsp;{% trans "Powered By Tenhal" %}&nbsp;&nbsp;&nbsp;&nbsp;</p>
</div> </div>
<div class="bg-holder bg-card" <div class="bg-holder bg-card"

View File

@ -36,8 +36,26 @@
<td class="align-middle product white-space-nowrap"> <td class="align-middle product white-space-nowrap">
{% if perms.django_ledger.change_itemmodel %} {% if perms.django_ledger.change_itemmodel %}
<a href="{% url 'item_expense_update' request.dealer.slug expense.pk %}" <div class="btn-reveal-trigger position-static">
class="btn btn-sm btn-phoenix-primary"><li class="fa fa-edit me-1"></li>{% trans "Update" %}</a> <button class="btn btn-sm dropdown-toggle dropdown-caret-none transition-none btn-reveal fs-10"
type="button"
data-bs-toggle="dropdown"
data-boundary="window"
aria-haspopup="true"
aria-expanded="false"
data-bs-reference="parent">
<span class="fas fa-ellipsis-h fs-10"></span>
</button>
<div class="dropdown-menu dropdown-menu-end py-2">
<a class="dropdown-item" href="{% url 'item_expense_update' request.dealer.slug expense.pk %}" >
<i class="fa fa-edit me-2"></i>{% trans "Update" %}
</a>
<a class="text-danger dropdown-item" href="#" >
<i class="fa fa-trash me-2"></i>{% trans "Delete" %}
</a>
</div>
</div>
{% endif %} {% endif %}
</td> </td>

View File

@ -38,8 +38,28 @@
<td class="align-middle product white-space-nowrap">{{ service.item.co }}</td> <td class="align-middle product white-space-nowrap">{{ service.item.co }}</td>
<td class="align-middle white-space-nowrap text-start"> <td class="align-middle white-space-nowrap text-start">
{% if perms.inventory.add_additionalservices %} {% if perms.inventory.add_additionalservices %}
<a href="{% url 'item_service_update' request.dealer.slug service.pk %}"
class="btn btn-sm btn-phoenix-primary"><li class="fa fa-edit me-1"></li>{% trans "Update" %}</a>
<div class="btn-reveal-trigger position-static">
<button class="btn btn-sm dropdown-toggle dropdown-caret-none transition-none btn-reveal fs-10"
type="button"
data-bs-toggle="dropdown"
data-boundary="window"
aria-haspopup="true"
aria-expanded="false"
data-bs-reference="parent">
<span class="fas fa-ellipsis-h fs-10"></span>
</button>
<div class="dropdown-menu dropdown-menu-end py-2">
<a class="dropdown-item" href="{% url 'item_service_update' request.dealer.slug service.pk %}" >
<i class="fa fa-edit me-2"></i>{% trans "Update" %}
</a>
<a class="text-danger dropdown-item" href="#" >
<i class="fa fa-trash me-2"></i>{% trans "Delete" %}
</a>
</div>
</div>
{% endif %} {% endif %}
</td> </td>
</tr> </tr>

View File

@ -10,7 +10,7 @@
{% endblock %} {% endblock %}
{% block content %} {% block content %}
<main class="d-flex align-items-center justify-content-center min-vh-100 py-5"> <main class="d-flex align-items-center justify-content-center min-vh-50 py-5">
<div class="col-12 col-lg-8 col-xl-7"> <div class="col-12 col-lg-8 col-xl-7">
<div class="card shadow-lg border-0 rounded-4 overflow-hidden animate__animated animate__fadeInUp"> <div class="card shadow-lg border-0 rounded-4 overflow-hidden animate__animated animate__fadeInUp">
<div class="card-header bg-gradient py-4 border-0 rounded-top-4"> <div class="card-header bg-gradient py-4 border-0 rounded-top-4">

View File

@ -11,18 +11,17 @@
{% if messages %} {% if messages %}
{% for message in messages %}<div class="alert alert-success">{{ message }}</div>{% endfor %} {% for message in messages %}<div class="alert alert-success">{{ message }}</div>{% endfor %}
{% endif %} {% endif %}
<h2 class="">
{{ _("Purchase Orders") |capfirst }} <li class="fas fa-cart-plus text-primary ms-2"></li>
</h2>
<div class="row g-3 justify-content-between mb-4"> <div class="row g-3 justify-content-between mb-4">
<div class="col-auto"> <div class="d-flex justify-content-between mb-2">
<div class="d-md-flex justify-content-between"> <h2 class="">
{% if perms.django_ledger.add_purchaseordermodel %} {{ _("Purchase Orders") |capfirst }} <li class="fas fa-cart-plus text-primary ms-2"></li>
</h2>
{% if perms.django_ledger.add_purchaseordermodel %}
<a href="{% url 'purchase_order_create' request.dealer.slug request.dealer.entity.slug %}" <a href="{% url 'purchase_order_create' request.dealer.slug request.dealer.entity.slug %}"
class="btn btn-md btn-phoenix-primary"><i class="fa fa-plus me-2"></i>{{ _("Create New PO") }}</a> class="btn btn-md btn-phoenix-primary"><i class="fa fa-plus me-2"></i>{{ _("Create New Purchase") }}</a>
{% endif %} {% endif %}
</div>
</div> </div>
<div class="col-auto"> <div class="col-auto">
<div class="d-flex"> <div class="d-flex">