Compare commits

...

6 Commits

46 changed files with 1063 additions and 254 deletions

View File

@ -30,7 +30,6 @@ urlpatterns += i18n_patterns(
path("plans/", include("plans.urls")),
path("schema/", Schema.as_view()),
path("tours/", include("tours.urls")),
# path('', include(tf_urls)),
)

View File

@ -55,6 +55,7 @@ from .models import (
Organization,
DealerSettings,
Tasks,
Recall,
)
from django_ledger import models as ledger_models
from django.forms import (
@ -2113,4 +2114,63 @@ class CustomSetPasswordForm(SetPasswordForm):
new_password2 = forms.CharField(
label="Confirm New Password",
widget=forms.PasswordInput(attrs={'class': 'form-control', 'placeholder': 'Confirm New Password'})
)
)
# forms.py
class RecallFilterForm(forms.Form):
make = forms.ModelChoiceField(
queryset=CarMake.objects.all(),
required=False,
label=_("Make"),
widget=forms.Select(attrs={'class': 'form-control'})
)
model = forms.ModelChoiceField(
queryset=CarModel.objects.none(),
required=False,
label=_("Model"),
widget=forms.Select(attrs={'class': 'form-control'})
)
serie = forms.ModelChoiceField(
queryset=CarSerie.objects.none(),
required=False,
label=_("Series"),
widget=forms.Select(attrs={'class': 'form-control'})
)
trim = forms.ModelChoiceField(
queryset=CarTrim.objects.none(),
required=False,
label=_("Trim"),
widget=forms.Select(attrs={'class': 'form-control'})
)
year_from = forms.IntegerField(required=False, label=_("From Year"),
widget=forms.NumberInput(attrs={'class': 'form-control'}))
year_to = forms.IntegerField(required=False, label=_("To Year"),
widget=forms.NumberInput(attrs={'class': 'form-control'}))
def __init__(self, *args, **kwargs):
make_id = kwargs.pop('make_id', None)
model_id = kwargs.pop('model_id', None)
serie_id = kwargs.pop('serie_id', None)
super().__init__(*args, **kwargs)
if make_id:
self.fields['model'].queryset = CarModel.objects.filter(id_car_make_id=make_id)
if model_id:
self.fields['serie'].queryset = CarSerie.objects.filter(id_car_model_id=model_id)
if serie_id:
self.fields['trim'].queryset = CarTrim.objects.filter(id_car_serie_id=serie_id)
class RecallCreateForm(forms.ModelForm):
class Meta:
model = Recall
fields = ['title', 'description', 'make', 'model', 'serie', 'trim', 'year_from', 'year_to']
widgets = {
'make': forms.Select(attrs={'class': 'form-control'}),
'model': forms.Select(attrs={'class': 'form-control'}),
'serie': forms.Select(attrs={'class': 'form-control'}),
'trim': forms.Select(attrs={'class': 'form-control'}),
'title': forms.TextInput(attrs={'class': 'form-control'}),
'description': forms.Textarea(attrs={'class': 'form-control'}),
'year_from': forms.NumberInput(attrs={'class': 'form-control'}),
'year_to': forms.NumberInput(attrs={'class': 'form-control'}),
}

View File

@ -151,20 +151,13 @@ class DealerSlugMiddleware:
return response
def process_view(self, request, view_func, view_args, view_kwargs):
if (
request.path_info.startswith("/ar/signup/")
or request.path_info.startswith("/en/signup/")
or request.path_info.startswith("/ar/login/")
or request.path_info.startswith("/en/login/")
or request.path_info.startswith("/ar/logout/")
or request.path_info.startswith("/en/logout/")
or request.path_info.startswith("/en/ledger/")
or request.path_info.startswith("/ar/ledger/")
or request.path_info.startswith("/en/notifications/")
or request.path_info.startswith("/ar/notifications/")
or request.path_info.startswith("/en/appointment/")
or request.path_info.startswith("/ar/appointment/")
):
paths = [
"/ar/signup/", "/en/signup/", "/ar/login/", "/en/login/",
"/ar/logout/", "/en/logout/", "/en/ledger/", "/ar/ledger/",
"/en/notifications/", "/ar/notifications/", "/en/appointment/",
"/ar/appointment/", "/en/feature/recall/","ar/feature/recall/"
]
if any(request.path_info.startswith(path) for path in paths):
return None
if not request.user.is_authenticated:

View File

@ -48,7 +48,7 @@ from imagekit.models import ImageSpecField
from imagekit.processors import ResizeToFill
# from plans.models import AbstractPlan
# from simple_history.models import HistoricalRecords
from plans.models import Invoice
class Base(models.Model):
id = models.UUIDField(
@ -1262,6 +1262,9 @@ class Dealer(models.Model, LocalizedNameMixin):
def __str__(self):
return self.name
@property
def invoices(self):
return Invoice.objects.filter(order__user=self.user)
class StaffTypes(models.TextChoices):
# MANAGER = "manager", _("Manager")
@ -3408,3 +3411,66 @@ class ExtraInfo(models.Model):
for x in qs
if x.content_object.invoicemodel_set.first()
]
class Recall(models.Model):
title = models.CharField(max_length=200, verbose_name=_("Recall Title"))
description = models.TextField(verbose_name=_("Description"))
make = models.ForeignKey(
CarMake,
models.DO_NOTHING,
verbose_name=_("Make"),
null=True,
blank=True
)
model = models.ForeignKey(
CarModel,
models.DO_NOTHING,
verbose_name=_("Model"),
null=True,
blank=True
)
serie = models.ForeignKey(
CarSerie,
models.DO_NOTHING,
verbose_name=_("Series"),
null=True,
blank=True
)
trim = models.ForeignKey(
CarTrim,
models.DO_NOTHING,
verbose_name=_("Trim"),
null=True,
blank=True
)
year_from = models.IntegerField(verbose_name=_("From Year"), null=True, blank=True)
year_to = models.IntegerField(verbose_name=_("To Year"), null=True, blank=True)
created_at = models.DateTimeField(auto_now_add=True, verbose_name=_("Created At"))
created_by = models.ForeignKey(
settings.AUTH_USER_MODEL,
on_delete=models.SET_NULL,
null=True,
blank=True,
verbose_name=_("Created By")
)
class Meta:
verbose_name = _("Recall")
verbose_name_plural = _("Recalls")
def __str__(self):
return self.title
class RecallNotification(models.Model):
recall = models.ForeignKey(Recall, on_delete=models.CASCADE, related_name='notifications')
dealer = models.ForeignKey("Dealer", on_delete=models.CASCADE, related_name='recall_notifications')
sent_at = models.DateTimeField(auto_now_add=True)
cars_affected = models.ManyToManyField(Car, related_name='recall_notifications')
class Meta:
verbose_name = _("Recall Notification")
verbose_name_plural = _("Recall Notifications")
def __str__(self):
return f"Notification for {self.dealer} about {self.recall}"

View File

@ -502,7 +502,7 @@ def create_item_service(sender, instance, created, **kwargs):
"""
if created:
entity = instance.dealer.entity
uom = entity.get_uom_all().get(name=str(instance.uom).lower())
uom = entity.get_uom_all().filter(unit_abbr=instance.uom).first()
cogs = (
entity.get_all_accounts()
.filter(role=roles.COGS, active=True, role_default=True)
@ -1032,7 +1032,7 @@ def po_fullfilled_notification(sender, instance, created, **kwargs):
).format(
url=reverse(
"purchase_order_detail",
kwargs={"dealer_slug": dealer.slug, "pk": instance.pk},
kwargs={"dealer_slug": dealer.slug,"entity_slug":instance.entity.slug, "pk": instance.pk},
),
),
)

View File

@ -1214,6 +1214,11 @@ urlpatterns = [
views.inventory_items_filter,
name="inventory_items_filter",
),
path(
"inventory_items_filter/",
views.inventory_items_filter,
name="inventory_items_filter",
),
path(
"<slug:dealer_slug>/purchase_orders/<slug:entity_slug>/delete/<uuid:po_pk>/",
views.PurchaseOrderModelDeleteView.as_view(),
@ -1271,6 +1276,11 @@ urlpatterns = [
),
path('car-sale-report/<slug:dealer_slug>/csv/', views.car_sale_report_csv_export, name='car-sale-report-csv-export'),
path('feature/recalls/', views.RecallListView.as_view(), name='recall_list'),
path('feature/recall/', views.RecallFilterView, name='recall_filter'),
path('feature/recall/<int:pk>/view/', views.RecallDetailView.as_view(), name='recall_detail'),
path('feature/recall/create/', views.RecallCreateView.as_view(), name='recall_create'),
path('feature/recall/success/', views.RecallSuccessView.as_view(), name='recall_success'),
]
handler404 = "inventory.views.custom_page_not_found_view"

View File

@ -1094,16 +1094,13 @@ class CarFinanceCalculator:
total_vat_amount = total_price_discounted * self.vat_rate
return {
"total_price_before_discount": round(
total_price, 2
),
"total_price": round(total_price_discounted, 2),
"total_vat_amount": round(total_vat_amount, 2),
"total_discount": round(Decimal(total_discount)),
"total_additionals": round(total_additionals, 2),
"grand_total": round(
total_price_discounted + total_vat_amount + total_additionals, 2
),
"total_price_discounted":total_price_discounted,
"total_price_before_discount":total_price,
"total_price": total_price_discounted,
"total_vat_amount": total_vat_amount,
"total_discount": Decimal(total_discount),
"total_additionals": total_additionals,
"grand_total":total_price_discounted + total_vat_amount + total_additionals,
}
def get_finance_data(self):
@ -1114,6 +1111,7 @@ class CarFinanceCalculator:
self._get_quantity(item) for item in self.item_transactions
),
"total_price": totals["total_price"],
"total_price_discounted": totals["total_price_discounted"],
"total_price_before_discount": totals["total_price_before_discount"],
"total_vat": totals["total_vat_amount"] + totals["total_price"],
"total_vat_amount": totals["total_vat_amount"],

View File

@ -34,7 +34,7 @@ from django.core.exceptions import ImproperlyConfigured, ValidationError
from django.core.exceptions import PermissionDenied
from django.contrib.contenttypes.models import ContentType
from django.views.decorators.http import require_POST
from django.template.loader import render_to_string
# Django
from django.db.models import Q
from django.conf import settings
@ -4798,6 +4798,7 @@ class EstimateDetailView(LoginRequiredMixin, PermissionRequiredMixin, DetailView
finance_data = calculator.get_finance_data()
invoice_obj = InvoiceModel.objects.all().filter(ce_model=estimate).first()
kwargs["data"] = finance_data
print(kwargs["data"])
kwargs["invoice"] = invoice_obj
try:
car_finances = estimate.get_itemtxs_data()[0].first().item_model.car.finances
@ -7203,6 +7204,7 @@ class ItemServiceCreateView(
def form_valid(self, form):
dealer = get_user_type(self.request)
vat = models.VatRate.objects.get(dealer=dealer, is_active=True)
form.instance.dealer = dealer
# if form.instance.taxable:
# form.instance.price = (form.instance.price * vat.rate) + form.instance.price
@ -7252,7 +7254,11 @@ class ItemServiceUpdateView(
def form_valid(self, form):
dealer = get_user_type(self.request)
vat = models.VatRate.objects.get(dealer=dealer, is_active=True)
uom = dealer.entity.get_uom_all().filter(unit_abbr=form.instance.uom).first()
form.instance.dealer = dealer
form.instance.uom = uom.name
form.instance.item.uom = uom
# if form.instance.taxable:
# form.instance.price = (form.instance.price * vat.rate) + form.instance.price
return super().form_valid(form)
@ -10146,10 +10152,49 @@ def InventoryItemCreateView(request, dealer_slug):
)
# def inventory_items_filter(request):
# year = request.GET.get("year")
# make = request.GET.get("make")
# model = request.GET.get("model")
# serie = request.GET.get("serie")
# # Get all makes for initial dropdown
# makes = models.CarMake.objects.all()
# print(make)
# model_data = models.CarModel.objects.none()
# serie_data = models.CarSerie.objects.none()
# trim_data = models.CarTrim.objects.none()
# if make:
# make_obj = models.CarMake.objects.get(pk=int(make))
# model_data = make_obj.carmodel_set.all()
# if model:
# model_obj = models.CarModel.objects.get(pk=model)
# serie_data = model_obj.carserie_set.all()
# if year:
# serie_data = serie_data.filter(year_begin__lte=year, year_end__gte=year)
# if serie:
# serie_obj = models.CarSerie.objects.get(pk=serie)
# trim_data = serie_obj.cartrim_set.all()
# # Generate year choices (adjust range as needed)
# current_year = datetime.now().year
# year_choices = range(current_year, current_year - 10, -1)
# context = {
# "makes": makes,
# "model_data": model_data,
# "serie_data": serie_data,
# "trim_data": trim_data,
# "year_choices": year_choices,
# "selected_make": make,
# "selected_model": model,
# "selected_serie": serie,
# "selected_year": year,
# }
# return render(request, "cars/partials/recall_filter_form.html", context)
@login_required
@permission_required("django_ledger.view_purchaseordermodel", raise_exception=True)
def inventory_items_filter(request, dealer_slug):
dealer = get_object_or_404(models.Dealer, slug=dealer_slug)
def inventory_items_filter(request,dealer_slug=None):
year = request.GET.get("year", None)
make = request.GET.get("make")
model = request.GET.get("model")
@ -10606,7 +10651,7 @@ def purchase_report_view(request,dealer_slug):
po_quantity=0
for item in items:
po_amount+=item["total"]
po_quantity+=item["q"]
po_quantity+=item["q"]
total_po_amount+=po_amount
total_po_cars+=po_quantity
@ -10760,4 +10805,148 @@ def staff_password_reset_view(request, dealer_slug, user_pk):
else:
messages.error(request, _('Invalid password. Please try again.'))
form = forms.CustomSetPasswordForm(staff.user)
return render(request, 'users/user_password_reset.html', {'form': form})
return render(request, 'users/user_password_reset.html', {'form': form})
class RecallListView(ListView):
model = models.Recall
template_name = 'recalls/recall_list.html'
context_object_name = 'recalls'
paginate_by = 20
def get_queryset(self):
queryset = super().get_queryset().annotate(
dealer_count=Count('notifications', distinct=True),
car_count=Count('notifications__cars_affected', distinct=True)
)
return queryset.select_related('make', 'model', 'serie', 'trim')
class RecallDetailView(DetailView):
model = models.Recall
template_name = 'recalls/recall_detail.html'
context_object_name = 'recall'
def get_context_data(self, **kwargs):
context = super().get_context_data(**kwargs)
context['notifications'] = self.object.notifications.select_related('dealer')
return context
def RecallFilterView(request):
context = {'make_data': models.CarMake.objects.all()}
if request.method == "POST":
print(request.POST)
make = request.POST.get('make')
model = request.POST.get('model')
serie = request.POST.get('serie')
trim = request.POST.get('trim')
year = request.POST.get('year')
url = reverse('recall_create')
url += f"?make={make}&model={model}&serie={serie}&trim={trim}&year={year}"
cars = models.Car.objects.filter(id_car_make=make,id_car_model=model,id_car_serie=serie,id_car_trim=trim,year=year)
context['url'] = url
context['cars'] = cars
return render(request,'recalls/recall_filter.html',context)
class RecallCreateView(FormView):
template_name = 'recalls/recall_create.html'
form_class = forms.RecallCreateForm
success_url = reverse_lazy('recall_success')
def get_form(self, form_class=None):
form = super().get_form(form_class)
make = self.request.GET.get('make')
model = self.request.GET.get('model')
serie = self.request.GET.get('serie')
trim = self.request.GET.get('trim')
year = self.request.GET.get('year')
if make:
qs = models.CarMake.objects.filter(pk=make)
form.fields['make'].queryset = qs
form.initial['make'] = qs.first()
if model:
qs = models.CarModel.objects.filter(pk=model)
form.fields['model'].queryset = qs
form.initial['model'] = qs.first()
if serie:
qs = models.CarSerie.objects.filter(pk=serie)
form.fields['serie'].queryset = qs
form.initial['serie'] = qs.first()
if trim:
qs = models.CarTrim.objects.filter(pk=trim)
form.fields['trim'].queryset = qs
form.initial['trim'] = qs.first()
if year:
form.fields['year_from'].initial = year
form.fields['year_to'].initial = year
return form
def get_initial(self):
initial = super().get_initial()
if self.request.method == 'GET':
initial.update(self.request.GET.dict())
return initial
def form_valid(self, form):
recall = form.save(commit=False)
recall.created_by = self.request.user
recall.save()
# Get affected cars based on recall criteria
cars = models.Car.objects.all()
if recall.make:
cars = cars.filter(id_car_make=recall.make)
if recall.model:
cars = cars.filter(id_car_model=recall.model)
if recall.serie:
cars = cars.filter(id_car_serie=recall.serie)
if recall.trim:
cars = cars.filter(id_car_trim=recall.trim)
if recall.year_from:
cars = cars.filter(year__gte=recall.year_from)
if recall.year_to:
cars = cars.filter(year__lte=recall.year_to)
# Group cars by dealer and send notifications
dealers = models.Dealer.objects.filter(cars__in=cars).distinct()
for dealer in dealers:
dealer_cars = cars.filter(dealer=dealer)
notification = models.RecallNotification.objects.create(
recall=recall,
dealer=dealer
)
notification.cars_affected.set(dealer_cars)
# Send email
self.send_notification_email(dealer, recall, dealer_cars)
messages.success(self.request, _("Recall created and notifications sent successfully"))
return super().form_valid(form)
def send_notification_email(self, dealer, recall, cars):
subject = f"Recall Notification: {recall.title}"
message = render_to_string('recalls/email/recall_notification.txt', {
'dealer': dealer,
'recall': recall,
'cars': cars,
})
send_email(
subject,
message,
'noreply@yourdomain.com',
[dealer.user.email],
)
class RecallSuccessView(TemplateView):
template_name = 'recalls/recall_success.html'

Binary file not shown.

After

Width:  |  Height:  |  Size: 7.8 MiB

View File

@ -282,9 +282,9 @@
</div>
</div>
</section>
<section class="pt-lg-0 pt-xl-8">
{% include 'footer.html' %}
</section>
<script src="{% static 'js/phoenix.js' %}"></script>
{% endblock content %}
{% block customJS %}

View File

@ -84,7 +84,7 @@
{% include "plans/expiration_messages.html" %}
{% block period_navigation %}
{% endblock period_navigation %}
<div id="main_content" class="fade-me-in" hx-boost="true" hx-target="#main_content" hx-select="#main_content" hx-swap="innerHTML transition:true" hx-select-oob="#toast-container" hx-history-elt>
<div id="main_content" class="fade-me-in" hx-boost="true" hx-target="#main_content" hx-select="#main_content" hx-swap="outerHTML transition:true" hx-select-oob="#toast-container" hx-history-elt>
<div id="spinner" class="htmx-indicator spinner-bg">
<img src="{% static 'spinner.svg' %}" width="100" height="100" alt="">
</div>

View File

@ -8,17 +8,16 @@
{% block content%}
<div class="row mt-4">
<div class="col-12 mb-3">
<div class="card shadow-sm">
<div class="card-body">
{% include 'bill/includes/card_bill.html' with dealer_slug=request.dealer.slug bill=bill entity_slug=view.kwargs.entity_slug style='bill-detail' %}
</div>
{% if bill.is_configured %}
<div class="row text-center g-3 mb-3">
<div class="col-12 col-md-3">
<h6 class="text-uppercase text-xs text-muted mb-2">
{% trans 'Cash Account' %}:
<a href="{% url 'account_detail' request.dealer.slug bill.cash_account.uuid %}"
@ -27,11 +26,11 @@
<h4 class="mb-0" id="djl-bill-detail-amount-paid">
{% currency_symbol %}{{ bill.get_amount_cash | absolute | currency_format }}
</h4>
</div>
{% if bill.accrue %}
<div class="col-12 col-md-3">
<h6 class="text-uppercase text-xs text-muted mb-2">
{% trans 'Prepaid Account' %}:
<a href="{% url 'account_detail' request.dealer.slug bill.prepaid_account.uuid %}"
@ -42,10 +41,10 @@
<h4 class="text-success mb-0" id="djl-bill-detail-amount-prepaid">
{% currency_symbol %}{{ bill.get_amount_prepaid | currency_format }}
</h4>
</div>
<div class="col-12 col-md-3">
<h6 class="text-uppercase text-xs text-muted mb-2">
{% trans 'Accounts Payable' %}:
<a href="{% url 'account_detail' request.dealer.slug bill.unearned_account.uuid %}"
@ -56,26 +55,26 @@
<h4 class="text-danger mb-0" id="djl-bill-detail-amount-unearned">
{% currency_symbol %}{{ bill.get_amount_unearned | currency_format }}
</h4>
</div>
<div class="col-12 col-md-3">
<h6 class="text-uppercase text-xs text-muted mb-2">{% trans 'Accrued' %} {{ bill.get_progress | percentage }}</h6>
<h4 class="mb-0">{% currency_symbol %}{{ bill.get_amount_earned | currency_format }}</h4>
</div>
{% else %}
<div class="col-12 col-md-3 offset-md-6">
<h6 class="text-uppercase text-xs text-muted mb-2">{% trans 'You Still Owe' %}</h6>
<h4 class="text-danger mb-0" id="djl-bill-detail-amount-owed">
{% currency_symbol %}{{ bill.get_amount_open | currency_format }}
</h4>
</div>
{% endif %}
</div>
{% endif %}
</div>
</div>

View File

@ -1,6 +1,6 @@
{% load django_ledger %}
{% load i18n %}
<div id="djl-bill-card-widget" class="">
<div id="djl-bill-card-widget" class="" hx-boost="false">
{% if not create_bill %}
{% if style == 'dashboard' %}
<!-- Dashboard Style Card -->
@ -54,7 +54,7 @@
<a href="{% url 'django_ledger:bill-detail' entity_slug=entity_slug bill_pk=bill.uuid %}"
class="btn btn-sm btn-phoenix-primary me-md-2">{% trans 'View' %}</a>
{% if perms.django_ledger.change_billmodel %}
<a href="{% url 'django_ledger:bill-update' entity_slug=entity_slug bill_pk=bill.uuid %}"
<a hx-boost="true" href="{% url 'django_ledger:bill-update' entity_slug=entity_slug bill_pk=bill.uuid %}"
class="btn btn-sm btn-phoenix-warning me-md-2">{% trans 'Update' %}</a>
{% if bill.can_pay %}
<button onclick="djLedger.toggleModal('{{ bill.get_html_id }}')"
@ -198,66 +198,71 @@
{% endif %}
</div>
<div class="card-footer p-0">
<div class="d-flex flex-wrap gap-2 mt-2" hx-boost="false">
<div class="d-flex flex-wrap gap-2 mt-2">
<!-- Update Button -->
{% if perms.django_ledger.change_billmodel %}
<button class="btn btn-phoenix-primary"
<a hx-boost="true" href="{% url 'bill-update' dealer_slug=dealer_slug entity_slug=entity_slug bill_pk=bill.uuid %}">
<button class="btn btn-phoenix-primary"
{% if not request.is_accountant %}disabled{% endif %}>
<a href="{% url 'bill-update' dealer_slug=dealer_slug entity_slug=entity_slug bill_pk=bill.uuid %}">
<i class="fas fa-edit me-2"></i>{% trans 'Update' %}
</a>
</button>
<!-- Mark as Draft -->
{% if bill.can_draft %}
<button class="btn btn-phoenix-success"
{% if not request.is_accountant %}disabled{% endif %}
onclick="showPOModal('Mark as Draft', '{% url 'bill-action-mark-as-draft' dealer_slug=request.dealer.slug entity_slug=entity_slug bill_pk=bill.pk %}', 'Mark as Draft')">
<i class="fas fa-check-circle me-2"></i>{% trans 'Mark as Draft' %}
</button>
</a>
{% if "detail" not in request.path %}
<!-- Mark as Draft -->
{% if bill.can_draft %}
<button class="btn btn-phoenix-success"
{% if not request.is_accountant %}disabled{% endif %}
onclick="showPOModal('Mark as Draft', '{% url 'bill-action-mark-as-draft' dealer_slug=request.dealer.slug entity_slug=entity_slug bill_pk=bill.pk %}', 'Mark as Draft')">
<i class="fas fa-check-circle me-2"></i>{% trans 'Mark as Draft' %}
</button>
{% endif %}
<!-- Mark as Review -->
{% if bill.can_review %}
<button class="btn btn-phoenix-warning"
{% if not request.is_accountant %}disabled{% endif %}
onclick="showPOModal('Mark as Review', '{% url 'bill-action-mark-as-review' dealer_slug=request.dealer.slug entity_slug=entity_slug bill_pk=bill.pk %}', 'Mark as Review')">
<i class="fas fa-check-circle me-2"></i>{% trans 'Mark as Review' %}
</button>
{% endif %}
<!-- Mark as Approved -->
{% endif %}
<!-- Mark as Review -->
{% if bill.can_review %}
<button class="btn btn-phoenix-warning"
{% if not request.is_accountant %}disabled{% endif %}
onclick="showPOModal('Mark as Review', '{% url 'bill-action-mark-as-review' dealer_slug=request.dealer.slug entity_slug=entity_slug bill_pk=bill.pk %}', 'Mark as Review')">
<i class="fas fa-check-circle me-2"></i>{% trans 'Mark as Review' %}
</button>
{% endif %}
<!-- Mark as Approved -->
{% if bill.can_approve and perms.django_ledger.can_approve_billmodel %}
<button class="btn btn-phoenix-success"
onclick="showPOModal('Mark as Approved', '{% url 'bill-action-mark-as-approved' dealer_slug=request.dealer.slug entity_slug=entity_slug bill_pk=bill.pk %}', 'Mark as Approved')">
<i class="fas fa-check-circle me-2"></i>{% trans 'Mark as Approved' %}
</button>
{% endif %}
{% if bill.can_approve and not request.is_manager %}
<button class="btn btn-phoenix-warning" disabled>
<i class="fas fa-hourglass-start me-2"></i><span class="text-warning">{% trans 'Waiting for Manager Approval' %}</span>
</button>
{% endif %}
<!-- Mark as Paid -->
{% if bill.can_pay %}
<button class="btn btn-phoenix-success"
onclick="showPOModal('Mark as Paid', '{% url 'bill-action-mark-as-paid' dealer_slug=request.dealer.slug entity_slug=entity_slug bill_pk=bill.pk %}', 'Mark as Paid')">
<i class="fas fa-check-circle me-2"></i>{% trans 'Mark as Paid' %}
</button>
{% endif %}
<!-- Void Button -->
{% if bill.can_void %}
<button class="btn btn-phoenix-danger"
onclick="showPOModal('Mark as Void', '{% url 'bill-action-mark-as-void' dealer_slug=request.dealer.slug entity_slug=entity_slug bill_pk=bill.pk %}', 'Mark as Void')">
<i class="fas fa-check-circle me-2"></i>{% trans 'Mark as Void' %}
</button>
{% endif %}
<!-- Cancel Button -->
{% if bill.can_cancel %}
<button class="btn btn-phoenix-danger"
{% if not request.is_accountant %}disabled{% endif %}
onclick="showPOModal('Mark as Canceled', '{% url 'bill-action-mark-as-canceled' dealer_slug=request.dealer.slug entity_slug=entity_slug bill_pk=bill.pk %}', 'Mark as Canceled')">
<i class="fas fa-check-circle me-2"></i>{% trans 'Mark as Canceled' %}
</button>
{% modal_action_v2 bill bill.get_mark_as_canceled_url bill.get_mark_as_canceled_message bill.get_mark_as_canceled_html_id %}
{% endif %}
{% if bill.can_approve and not request.is_manager or not request.is_dealer %}
<button class="btn btn-phoenix-warning" disabled>
<i class="fas fa-hourglass-start me-2"></i><span class="text-warning">{% trans 'Waiting for Manager Approval' %}</span>
</button>
{% else %}
{% if bill.can_approve and perms.django_ledger.can_approve_billmodel %}
<button class="btn btn-phoenix-success"
onclick="showPOModal('Mark as Approved', '{% url 'bill-action-mark-as-approved' dealer_slug=request.dealer.slug entity_slug=entity_slug bill_pk=bill.pk %}', 'Mark as Approved')">
<i class="fas fa-check-circle me-2"></i>{% trans 'Mark as Approved' %}
</button>
{% endif %}
{% endif %}
<!-- Mark as Paid -->
{% if "detail" not in request.path %}
{% if bill.can_pay %}
<button class="btn btn-phoenix-success"
onclick="showPOModal('Mark as Paid', '{% url 'bill-action-mark-as-paid' dealer_slug=request.dealer.slug entity_slug=entity_slug bill_pk=bill.pk %}', 'Mark as Paid')">
<i class="fas fa-check-circle me-2"></i>{% trans 'Mark as Paid' %}
</button>
{% endif %}
<!-- Void Button -->
{% if bill.can_void %}
<button class="btn btn-phoenix-danger"
onclick="showPOModal('Mark as Void', '{% url 'bill-action-mark-as-void' dealer_slug=request.dealer.slug entity_slug=entity_slug bill_pk=bill.pk %}', 'Mark as Void')">
<i class="fas fa-check-circle me-2"></i>{% trans 'Mark as Void' %}
</button>
{% endif %}
<!-- Cancel Button -->
{% if bill.can_cancel %}
<button class="btn btn-phoenix-danger"
{% if not request.is_accountant %}disabled{% endif %}
onclick="showPOModal('Mark as Canceled', '{% url 'bill-action-mark-as-canceled' dealer_slug=request.dealer.slug entity_slug=entity_slug bill_pk=bill.pk %}', 'Mark as Canceled')">
<i class="fas fa-check-circle me-2"></i>{% trans 'Mark as Canceled' %}
</button>
{% modal_action_v2 bill bill.get_mark_as_canceled_url bill.get_mark_as_canceled_message bill.get_mark_as_canceled_html_id %}
{% endif %}
{% endif %}
{% endif %}
</div>
</div>
@ -289,7 +294,10 @@
}
</style>
<script>
document.addEventListener('DOMContentLoaded', function() {
document.addEventListener('DOMContentLoaded',processElements);
//document.addEventListener('htmx:afterSwap',processElements);
function processElements() {
window.showPOModal = function(title, actionUrl, buttonText) {
const modalEl = document.getElementById('POModal');
if (!modalEl) {
@ -300,7 +308,8 @@
const modal = bootstrap.Modal.getOrCreateInstance(modalEl);
document.getElementById('POModalTitle').textContent = title;
document.getElementById('POModalBody').innerHTML = `
const modalBody = document.getElementById('POModalBody')
modalBody.innerHTML = `
<div class="d-flex justify-content-center gap-3 py-3">
<a class="btn btn-phoenix-primary px-4" href="${actionUrl}">
<i class="fas fa-check-circle me-2"></i>${buttonText}
@ -310,8 +319,8 @@
</button>
</div>
`;
//htmx.process(modalBody);
modal.show();
};
});
};
</script>

View File

@ -5,7 +5,8 @@
{{ _("Leads") |capfirst }}
{% endblock title %}
{% block content %}
{% if page_obj.object_list %}
{% if page_obj.object_list or request.GET.q%}
<div class="row g-3 mt-4 mb-4">
<h2 class="mb-2">
{{ _("Leads") |capfirst }}

View File

@ -178,9 +178,9 @@
<div class="d-flex flex-wrap justify-content-between mb-2">
<h5 class="mb-0 text-body-highlight me-2">{{ _("Invoice") }}</h5>
</div>
{% if opportunity.estimate.invoice %}
{% if opportunity.estimate.invoicemodel_set.all %}
<a class="dropdown-item"
href="{% url 'invoice_detail' request.dealer.slug request.entity.slug opportunity.estimate.invoice.pk %}">{{ _("View Invoice") }}</a>
href="{% url 'invoice_detail' request.dealer.slug request.entity.slug opportunity.estimate.invoicemodel_set.first.pk %}">{{ _("View Invoice") }}</a>
{% else %}
<p>{{ _("No Invoice") }}</p>
{% endif %}
@ -1020,7 +1020,7 @@
</div>
<div class="tab-pane fade"
id="tab-activity"
hx-get="{% url 'lead_detail' request.dealer.slug lead.slug %}"
hx-get="{% url 'opportunity_detail' request.dealer.slug opportunity.slug %}"
hx-trigger="htmx:afterRequest from:"
hx-select="#tab-activity"
hx-target="this"

View File

@ -5,7 +5,7 @@
{{ _("Opportunities") }}
{% endblock title %}
{% block content %}
{% if opportunities %}
{% if opportunities or request.GET.q%}
<div class="row g-3 mt-4">
<div class="col-12">
<h2 class="mb-3">

View File

@ -6,7 +6,7 @@
{% endblock title %}
{% block vendors %}<a class="nav-link active">{{ _("Customers") |capfirst }}</a>{% endblock %}
{% block content %}
{% if customers %}
{% if customers or request.GET.q%}
<div class="row g-3 mt-4">
<h2 class="mb-2">
{{ _("Customers") |capfirst }}

View File

@ -11,7 +11,7 @@
<!--heading -->
<div class="row align-items-center justify-content-between g-3 mb-4">
<div class="col-auto">
<h3 class="mb-0">{% trans 'Customer details' %}</h3>
<h3 class="mb-0">{% trans 'Customer details' %}<li class="fas fa-user ms-2 text-primary"></li></h3>
</div>
<div class="col-auto">
<div class="row g-3">
@ -127,20 +127,16 @@
</div>
<div class="col-12 mt-3">
<div class="mb-6">
<div class="border-top border-bottom border-translucent"
id="customerOrdersTable"
data-list='{"valueNames":["order","total","payment_status","fulfilment_status","delivery_type","date"],"page":6,"pagination":true}'>
<div>
<div class="table-responsive scrollbar">
<table class="table table-sm fs-9 mb-0">
<thead>
<thead class="bg-body-highlight">
<tr>
<th class="sort white-space-nowrap align-middle" scope="col" data-sort="leads">{% trans 'Leads'|upper %}</th>
<th class="sort align-middle " scope="col" data-sort="opportunities">{% trans 'Opportunities'|upper %}</th>
<th class="sort align-middle " scope="col" data-sort="estimates">{% trans 'Estimates'|upper %}</th>
<th class="sort align-middle " scope="col" data-sort="sale_orders">{% trans 'Sale orders'|upper %}</th>
<th class="sort align-middle " scope="col" data-sort="invoices">{% trans 'Invoices'|upper %}</th>
<th class="sort align-middle " scope="col" data-sort="car">{% trans 'Car'|upper %}</th>
<th class="sort align-middle" scope="col" >{% trans 'Leads'|upper %}</th>
<th class="sort align-middle " scope="col" >{% trans 'Opportunities'|upper %}</th>
<th class="sort align-middle " scope="col">{% trans 'Estimates'|upper %}</th>
</tr>
</thead>
@ -148,44 +144,69 @@
{% for lead in leads %}
<tr>
<td><a href="{% url 'lead_detail' request.dealer.slug lead.slug%}">{{lead}} ({{ forloop.counter }})<a></td>
<td><a href="{$ url 'opportunity_detail' lead.opportunity.slug request.dealer.slug}">{{lead.opportunity}} ({{ forloop.counter }})</a></td>
<td><a href="{% url 'opportunity_detail' request.dealer.slug lead.opportunity.slug%}">{{lead.opportunity}} ({{ forloop.counter }})</a></td>
<td>
{% for estimate in lead.customer.customer_model.estimatemodel_set.all %}
<div class="me-2"><a href="{% url 'estimate_detail' request.dealer.slug estimate.pk %}">{{estimate}}</a></div>
<hr>
{% endfor %}
</td>
{% for estimate in lead.customer.customer_model.estimatemodel_set.all %}
<h4 class="me-2 my-1"><a href="{% url 'estimate_detail' request.dealer.slug estimate.pk %}"><span class="me-2">#{{forloop.counter }}</span>{{estimate}}</a></h4>
<table class="table table-sm">
<thead class="bg-body-highlight">
<tr>
<th class="sort align-middle " scope="col">{% trans 'Sale orders'|upper %}</th>
<th class="sort align-middle " scope="col">{% trans 'Invoices'|upper %}</th>
<th class="sort align-middle " scope="col">{% trans 'Car VIN'|upper %}</th>
</tr>
</thead>
<tbody>
<tr>
<td>
{% for sale_order in estimate.sale_orders.all %}
<div><a href={% url 'order_detail' request.dealer.slug sale_order.pk%}>{{estimate.sale_orders.first}}</a></div>
{% endfor %}
</td>
<td>
{% for estimate in lead.customer.customer_model.estimatemodel_set.all %}
<div><a href={% url 'order_detail' request.dealer.slug estimate.sale_orders.first.pk%}>{{estimate.sale_orders.first}}</a></div>
<hr>
{% endfor %}
</td>
<td>
{% for invoice in lead.customer.customer_model.invoicemodel_set.all %}
<td>
{% for invoice in estimate.invoicemodel_set.all %}
{% if invoice.is_paid %}
<span class="badge badge-phoenix fs-10 badge-phoenix-success">
<div><a href="{% url 'invoice_detail' request.dealer.slug request.entity.slug invoice.pk %}">{{invoice}}</a></div>
</span>
{%else%}
<span class="badge badge-phoenix fs-10 badge-phoenix-info">
<div><a href="{% url 'invoice_detail' request.dealer.slug request.entity.slug invoice.pk %}">{{invoice}}</a></div>
</span>
{% endif %}
{% if invoice.is_paid %}
<span class="badge badge-phoenix fs-10 badge-phoenix-success">
<div><a href="{% url 'invoice_detail' request.dealer.slug request.entity.slug invoice.pk %}">{{invoice}}</a></div>
</span>
{%else%}
<span class="badge badge-phoenix fs-10 badge-phoenix-info">
<div><a href="{% url 'invoice_detail' request.dealer.slug request.entity.slug invoice.pk %}">{{invoice}}</a></div>
</span>
{% endif %}
<hr>
{% endfor %}
{% endfor %}
</td>
<td>
<div><a href="#">{{estimate.itemtransactionmodel_set.first.item_model.name}}</a></div>
<td>
</tr>
</tbody>
</table>
<br>
{% endfor %}
</td>
<td>
{% for estimate in lead.customer.customer_model.invoicemodel_set.all %}
<div><a href="#">{{estimate.itemtransactionmodel_set.first.item_model.name}}</a></div>
<hr>
{% endfor %}
<td>
<tr>

View File

@ -7,6 +7,7 @@
<div class="row align-items-center justify-content-between g-3 mb-4">
<div class="col-auto">
<h2 class="mb-0">{% trans 'Profile' %}</h2>
</div>
<div class="col-auto">
<div class="row g-2 g-sm-3">

View File

@ -37,31 +37,33 @@
/* No specific styles for .btn-add-new or .message-box are needed here as per previous updates */
</style>
<div class="empty-state-container">
<div class="empty-state-container">
<!-- Empty State Illustration -->
{% if image %}
{% if image %}
{% static image as final_image_path %}
{% else %}
{% static 'images/no_content/no_item.jpg' as final_image_path %}
{% endif %}
{% else %}
{% static 'images/no_content/no_item.jpg' as final_image_path %}
{% endif %}
<img src="{{ final_image_path }}" alt="No-empty-state-image" class="empty-state-image">
<!-- Title -->
<h3 class="empty-state-title">
<h3 class="empty-state-title">
{% blocktrans %}No {{ value}} Yet{% endblocktrans %}
</h3>
</h3>
<!-- Description -->
<p class="empty-state-text">
<p class="empty-state-text">
{% blocktrans %}It looks like you haven't added any {{ value }} to your account.
Click the button below to get started and add your first {{ value }}!{% endblocktrans %}
</p>
</p>
<!-- Call to Action Button -->
<a class="btn btn-lg btn-primary" href="{{ url }}">
<a class="btn btn-lg btn-primary" href="{{ url }}">
<i class="fa fa-plus me-2"></i>
{% blocktrans %}Create New {{value}}{% endblocktrans %}
</a>
</div>
</a>
</div>

View File

@ -7,7 +7,7 @@
{% endblock title %}
{% block content %}
{% if groups %}
{% if groups or request.GET.q%}
<section class="">
<div class="row mt-4">
<div class="col-auto">

View File

@ -25,7 +25,7 @@
{% endblock customCSS %}
{% block content %}
{% if cars%}
{% if cars or request.GET.q%}
<div class="container-fluid" id="projectSummary">
<div class="row g-3 justify-content-between align-items-end mb-4">
<div class="col-12 col-sm-auto">

View File

@ -6,7 +6,7 @@
{% block content %}
{% if expenses %}
{% if expenses or request.GET.q %}
<div class="row mt-4">
<div class="d-flex justify-content-between mb-2">
<h3 class="">{% trans "Expenses" %} <span class="fas fa-money-bill-wave ms-2 text-primary"></span></h3>

View File

@ -5,7 +5,7 @@
{% endblock title %}
{% block content %}
{% if services %}
{% if services or request.GET.q %}
<div class="row mt-4">
<div class="d-flex justify-content-between mb-2">
<h3 class="">{% trans "Services" %}<span class="fas fa-tools text-primary ms-2"></span></h3>

View File

@ -11,7 +11,7 @@
{% endblock %}
{% block content %}
{% if bills %}
{% if bills or request.GET.q%}
<div class="row mt-4">
<div class="d-flex justify-content-between mb-2">
<h3 class="">{% trans "Bills" %}<span class="fas fa-money-bills ms-2 text-primary"></span></h3>

View File

@ -12,7 +12,7 @@
{% block content %}
{% if accounts%}
{% if accounts or request.GET.q%}
<div class="row mt-4">
<div class="d-flex justify-content-between mb-2">
<h3 class=""> {% trans "Accounts" %}<i class="fa-solid fa-book ms-2 text-primary"></i></h3>

View File

@ -5,7 +5,7 @@
{% endblock title %}
{% block content %}
{% if journal_entries %}
{% if journal_entries or request.GET.q%}
<div class="modal fade"
id="confirmModal"
tabindex="-1"

View File

@ -5,16 +5,16 @@
{% block title %}
{% trans 'Cash Flow Statement' %} {% endblock %}
{% block period_navigation %}
{% if unit_model and entity %}
<div class="col-12">{% period_navigation 'unit-cf' %}</div>
{% if unit_model and entity %}
<div class="col-12">{% period_navigation 'unit-cf' %}</div>
{% elif entity %}
<div class="col-12">{% period_navigation 'entity-cf' %}</div>
<div class="col-12">{% period_navigation 'entity-cf' %}</div>
{% elif ledger %}
<div class="col-12">{% period_navigation 'ledger-cf' %}</div>
<div class="col-12">{% period_navigation 'ledger-cf' %}</div>
{% elif unit_model %}
<div class="col-12">{% period_navigation 'unit-cf' %}</div>
{% endif %}
{% endblock %}
<div class="col-12">{% period_navigation 'unit-cf' %}</div>
{% endif %}
{% endblock period_navigation %}
{% block content %}
<div class="card">
<div class="card-body text-center">

View File

@ -70,10 +70,10 @@
{% for po in data %}
<tr>
<td class="ps-1">{{po.po_number}}</td>
<td>{{po.po_created}}</td>
<td>{{po.po_created|date}}</td>
<td>{{po.po_status}}</td>
<td>{{po.po_amount}}</td>
<td>{{po.date_fulfilled}}</td>
<td>{{po.po_fulfilled_date}}</td>
<td>staff</td>
<td>{{po.po_quantity}}</td>
<td>

View File

@ -71,7 +71,7 @@
<div class="empty-state-container alert mb-2" role="alert">
<img src="{% static message_image %}" alt="message-image" class="empty-state-image mb-2">
<img src="{% static 'images/no_content/no_item.jpg' %}" alt="message-image" class="empty-state-image mb-2">
<!-- Title -->
<h3 class="empty-state-title mb-2">
{% blocktrans %}{{ value1}}{% endblocktrans %}

View File

@ -8,7 +8,7 @@
<a class="nav-link active">{% trans 'Organizations' %}</a>
{% endblock %}
{% block content %}
{% if organizations%}
{% if organizations or request.GET.q%}
<section class="pt-5 pb-9 ">
<div class="row overflow-x-auto whitespace-nowrap -mx-2 sm:mx-0">
<h2 class="mb-4">

View File

@ -2,7 +2,10 @@
{% load django_ledger %}
{% load widget_tweaks %}
<script>
document.addEventListener('DOMContentLoaded', function() {
document.addEventListener('DOMContentLoaded',processElements);
//document.addEventListener('htmx:afterSwap',processElements);
function processElements() {
window.showPOModal = function(title, actionUrl, buttonText) {
const modalEl = document.getElementById('POModal');
if (!modalEl) {
@ -13,7 +16,8 @@
const modal = bootstrap.Modal.getOrCreateInstance(modalEl);
document.getElementById('POModalTitle').textContent = title;
document.getElementById('POModalBody').innerHTML = `
const modalBody = document.getElementById('POModalBody')
modalBody.innerHTML = `
<div class="d-flex justify-content-center gap-3 py-3">
<a hx-boost="true" class="btn btn-phoenix-primary px-4" href="${actionUrl}">
<i class="fas fa-check-circle me-2"></i>${buttonText}
@ -23,10 +27,10 @@
</button>
</div>
`;
//htmx.process(modalBody)
modal.show();
};
});
};
</script>
{% if not create_po %}
{% if style == 'po-detail' %}
@ -106,52 +110,54 @@
class="btn btn-phoenix-primary">
<i class="fas fa-edit me-2"></i>{% trans 'Update' %}
</a>
<div class="d-flex flex-wrap gap-2">
{% if po_model.can_draft %}
<button class="btn btn-phoenix-secondary"
onclick="showPOModal('Draft PO', '{% url 'po-action-mark-as-draft' request.dealer.slug entity_slug po_model.pk %}', 'Mark As Draft')">
<i class="fas fa-file me-2"></i>{% trans 'Mark as Draft' %}
</button>
{% endif %}
{% if po_model.can_review %}
<button class="btn btn-phoenix-warning"
onclick="showPOModal('Review PO', '{% url 'po-action-mark-as-review' request.dealer.slug entity_slug po_model.pk %}', 'Mark As Review')">
<i class="fas fa-search me-2"></i>{% trans 'Mark as Review' %}
</button>
{% endif %}
{% if po_model.can_approve %}
<button class="btn btn-phoenix-success"
onclick="showPOModal('Approve PO', '{% url 'po-action-mark-as-approved' request.dealer.slug entity_slug po_model.pk %}', 'Mark As Approved')">
<i class="fas fa-check-circle me-2"></i>{% trans 'Mark as Approved' %}
</button>
{% endif %}
{% if po_model.can_fulfill %}
<button class="btn btn-phoenix-primary"
onclick="showPOModal('Fulfill PO', '{% url 'po-action-mark-as-fulfilled' request.dealer.slug entity_slug po_model.pk %}', 'Mark As Fulfilled')">
<i class="fas fa-truck me-2"></i>{% trans 'Mark as Fulfilled' %}
</button>
{% endif %}
{% if po_model.can_delete %}
{% if perms.django_ledger.delete_purchaseordermodel %}
<button class="btn btn-phoenix-danger"
onclick="showPOModal('Delete PO', '{% url 'po-delete' request.dealer.slug entity_slug po_model.pk %}', 'Delete')">
<i class="fas fa-trash me-2"></i>{% trans 'Delete' %}
{% if "detail" not in request.path %}
<div class="d-flex flex-wrap gap-2">
{% if po_model.can_draft %}
<button class="btn btn-phoenix-secondary"
onclick="showPOModal('Draft PO', '{% url 'po-action-mark-as-draft' request.dealer.slug entity_slug po_model.pk %}', 'Mark As Draft')">
<i class="fas fa-file me-2"></i>{% trans 'Mark as Draft' %}
</button>
{% endif %}
{% endif %}
{% if po_model.can_void %}
<button class="btn btn-phoenix-danger"
onclick="showPOModal('Void PO', '{% url 'po-action-mark-as-void' request.dealer.slug entity_slug po_model.pk %}', 'Mark As Void')">
<i class="fas fa-times-circle me-2"></i>{% trans 'Void' %}
</button>
{% endif %}
{% if po_model.can_cancel %}
<button class="btn btn-phoenix-secondary"
onclick="showPOModal('Cancel PO', '{% url 'po-action-mark-as-canceled' request.dealer.slug entity_slug po_model.pk %}', 'Mark As Cancelled')">
<i class="fas fa-ban me-2"></i>{% trans 'Cancel' %}
</button>
{% endif %}
</div>
{% if po_model.can_review %}
<button class="btn btn-phoenix-warning"
onclick="showPOModal('Review PO', '{% url 'po-action-mark-as-review' request.dealer.slug entity_slug po_model.pk %}', 'Mark As Review')">
<i class="fas fa-search me-2"></i>{% trans 'Mark as Review' %}
</button>
{% endif %}
{% if po_model.can_approve %}
<button class="btn btn-phoenix-success"
onclick="showPOModal('Approve PO', '{% url 'po-action-mark-as-approved' request.dealer.slug entity_slug po_model.pk %}', 'Mark As Approved')">
<i class="fas fa-check-circle me-2"></i>{% trans 'Mark as Approved' %}
</button>
{% endif %}
{% if po_model.can_fulfill %}
<button class="btn btn-phoenix-primary"
onclick="showPOModal('Fulfill PO', '{% url 'po-action-mark-as-fulfilled' request.dealer.slug entity_slug po_model.pk %}', 'Mark As Fulfilled')">
<i class="fas fa-truck me-2"></i>{% trans 'Mark as Fulfilled' %}
</button>
{% endif %}
{% if po_model.can_delete %}
{% if perms.django_ledger.delete_purchaseordermodel %}
<button class="btn btn-phoenix-danger"
onclick="showPOModal('Delete PO', '{% url 'po-delete' request.dealer.slug entity_slug po_model.pk %}', 'Delete')">
<i class="fas fa-trash me-2"></i>{% trans 'Delete' %}
</button>
{% endif %}
{% endif %}
{% if po_model.can_void %}
<button class="btn btn-phoenix-danger"
onclick="showPOModal('Void PO', '{% url 'po-action-mark-as-void' request.dealer.slug entity_slug po_model.pk %}', 'Mark As Void')">
<i class="fas fa-times-circle me-2"></i>{% trans 'Void' %}
</button>
{% endif %}
{% if po_model.can_cancel %}
<button class="btn btn-phoenix-secondary"
onclick="showPOModal('Cancel PO', '{% url 'po-action-mark-as-canceled' request.dealer.slug entity_slug po_model.pk %}', 'Mark As Cancelled')">
<i class="fas fa-ban me-2"></i>{% trans 'Cancel' %}
</button>
{% endif %}
</div>
{% endif %}
</div>
{% endif %}
</div>

View File

@ -4,9 +4,9 @@
{% block title %}
{# Check if an 'object' exists in the context #}
{% if object %}
{% trans 'Update Vendor' %}
{% trans 'Update Purchase Order' %}
{% else %}
{% trans 'Add New Vendor' %}
{% trans 'Create Purchase Order' %}
{% endif %}
{% endblock %}
{% block content %}

View File

@ -5,7 +5,7 @@
{% block content %}
{% if purchase_orders %}
{% if purchase_orders or request.GET.q %}
<div class="row mt-4">
<!-- Success Message -->
{% if messages %}

View File

@ -0,0 +1,24 @@
<!-- templates/recalls/email/recall_notification_formal.txt -->
{% load i18n %}
السادة/ شركة {{ dealer.name }} المحترمين،
السلام عليكم ورحمة الله وبركاته،
نفيدكم علماً بأنه تم إصدار استدعاء سلامة لبعض المركبات من الموديلات الموجودة لديكم في المخزون.
بيانات الاستدعاء:
- عنوان الاستدعاء: {{ recall.title }}
- وصف المشكلة: {{ recall.description }}
المركبات المتأثرة في مخزونكم:
{% for car in cars %}
- رقم الشاصي: {{ car.vin }}
- الماركة: {{ car.id_car_make }}
- الموديل: {{ car.id_car_model }} - {{ car.year }}
- الفئة: {{ car.id_car_trim }}
{% endfor %}
يرجى التواصل مع فريق خدمة العملاء لدينا على أرقام الهاتف التالية [أدخل الأرقام هنا] أو عبر البريد الإلكتروني [أدخل البريد هنا] لترتيب موعد لإجراء الإصلاحات اللازمة مجاناً.
وتفضلوا بقبول فائق الاحترام والتقدير،
إدارة خدمة العملاء
شركة [تنحل]

View File

@ -0,0 +1,35 @@
{% load i18n %}
{% if cars %}
<table class="table table-striped table-hover">
<thead>
<tr>
<th>{% trans "VIN" %}</th>
<th>{% trans "Make" %}</th>
<th>{% trans "Model" %}</th>
<th>{% trans "Series" %}</th>
<th>{% trans "Trim" %}</th>
<th>{% trans "Year" %}</th>
<th>{% trans "Dealer" %}</th>
<th>{% trans "Status" %}</th>
</tr>
</thead>
<tbody>
{% for car in cars %}
<tr>
<td>{{ car.vin }}</td>
<td>{{ car.id_car_make|default:"-" }}</td>
<td>{{ car.id_car_model|default:"-" }}</td>
<td>{{ car.id_car_serie|default:"-" }}</td>
<td>{{ car.id_car_trim|default:"-" }}</td>
<td>{{ car.year }}</td>
<td>{{ car.dealer.name }}</td>
<td>{{ car.get_status_display }}</td>
</tr>
{% endfor %}
</tbody>
</table>
<a href="{{ url }}" class="btn btn-primary">
<i class="fas fa-plus mr-2"></i>
{% trans "Recall Request" %}
</a>
{% endif %}

View File

@ -0,0 +1,79 @@
{% load i18n %}
<div class="row">
<!-- Make Dropdown -->
<div class="col-md-3">
<label for="id_make">{% trans "Make" %}</label>
<select name="make" id="id_make" class="form-select"
hx-get="{% url 'inventory_items_filter' %}"
hx-select="#id_model"
hx-target="#id_model"
hx-swap="outerHTML"
hx-include="[name='year']">
<option value="">{% trans "Select Make" %}</option>
{% for make in makes %}
<option value="{{ make.pk }}" {% if selected_make == make.pk %}selected{% endif %}>{{ make.name }}</option>
{% endfor %}
</select>
</div>
<!-- Model Dropdown Container -->
<div class="col-md-3" id="model-container">
<label for="id_model">{% trans "Model" %}</label>
<select name="model" id="id_model" class="form-select"
{% if not model_data %}disabled{% endif %}
hx-get="{% url 'inventory_items_filter' %}"
hx-select="#serie-container"
hx-target="#serie-container"
hx-trigger="change"
hx-include="[name='year']">
<option value="">{% trans "Select Model" %}</option>
{% for model in model_data %}
<option value="{{ model.pk }}" {% if selected_model == model.pk %}selected{% endif %}>{{ model.name }}</option>
{% endfor %}
</select>
</div>
<!-- Series Dropdown Container -->
<div class="col-md-3" id="serie-container">
<label for="id_serie">{% trans "Series" %}</label>
<select name="serie" id="id_serie" class="form-select"
{% if not serie_data %}disabled{% endif %}
hx-get="{% url 'inventory_items_filter' %}"
hx-select="#trim-container"
hx-target="#trim-container"
hx-trigger="change"
hx-include="[name='year']">
<option value="">{% trans "Select Series" %}</option>
{% for serie in serie_data %}
<option value="{{ serie.pk }}" {% if selected_serie == serie.pk %}selected{% endif %}>{{ serie.name }}</option>
{% endfor %}
</select>
</div>
<!-- Trim Dropdown Container -->
<div class="col-md-3" id="trim-container">
<label for="id_trim">{% trans "Trim" %}</label>
<select name="trim" id="id_trim" class="form-select" {% if not trim_data %}disabled{% endif %}>
<option value="">{% trans "Select Trim" %}</option>
{% for trim in trim_data %}
<option value="{{ trim.pk }}" {% if selected_trim == trim.pk %}selected{% endif %}>{{ trim.name }}</option>
{% endfor %}
</select>
</div>
</div>
<!-- Year Filter -->
<div class="row mt-3">
<div class="col-md-3">
<label for="id_year">{% trans "Year" %}</label>
<select name="year" id="id_year" class="form-select"
hx-get="{% url 'inventory_items_filter' %}"
hx-trigger="change"
hx-target="#serie-container">
<option value="">{% trans "Any Year" %}</option>
{% for year in year_choices %}
<option value="{{ year }}" {% if selected_year == year %}selected{% endif %}>{{ year }}</option>
{% endfor %}
</select>
</div>
</div>

View File

@ -0,0 +1,80 @@
<!-- templates/recalls/recall_create.html -->
{% extends "base.html" %}
{% load i18n %}
{% block content %}
<div class="container mt-4">
<h2>{% trans "Create New Recall" %}</h2>
<div class="card">
<div class="card-header">
<h5>{% trans "Recall Details" %}</h5>
</div>
<div class="card-body">
<form method="post">
{% csrf_token %}
<div class="row mb-3">
<div class="col-md-6">
{{ form.title.label_tag }}
{{ form.title }}
{% if form.title.errors %}
<div class="invalid-feedback d-block">
{{ form.title.errors }}
</div>
{% endif %}
</div>
</div>
<div class="row mb-3">
<div class="col-md-12">
{{ form.description.label_tag }}
{{ form.description }}
{% if form.description.errors %}
<div class="invalid-feedback d-block">
{{ form.description.errors }}
</div>
{% endif %}
</div>
</div>
<div class="row mb-3">
<div class="col-md-2">
{{ form.make.label_tag }}
{{ form.make }}
</div>
<div class="col-md-2">
{{ form.model.label_tag }}
{{ form.model }}
</div>
<div class="col-md-2">
{{ form.serie.label_tag }}
{{ form.serie }}
</div>
<div class="col-md-2">
{{ form.trim.label_tag }}
{{ form.trim }}
</div>
<div class="col-md-2">
{{ form.year_from.label_tag }}
{{ form.year_from }}
</div>
<div class="col-md-2">
{{ form.year_to.label_tag }}
{{ form.year_to }}
</div>
</div>
<div class="mt-4">
<button type="submit" class="btn btn-danger">
{% trans "Confirm and Send Recall Notifications" %}
</button>
<a href="{% url 'recall_filter' %}" class="btn btn-secondary">
{% trans "Cancel" %}
</a>
</div>
</form>
</div>
</div>
</div>
{% endblock %}

View File

@ -0,0 +1,82 @@
<!-- templates/recalls/recall_detail.html -->
{% extends "base.html" %}
{% load i18n humanize %}
{% block content %}
<div class="container mt-4">
<div class="d-flex justify-content-between align-items-center mb-4">
<div class="d-flex align-items-center gap-2">
<img src="{{ recall.make.logo.url }}" width="80" height="80">
<h2>{{ recall.title }}</h2>
</div>
<a href="{% url 'recall_list' %}" class="btn btn-secondary">
{% trans "Back to Recall List" %}
</a>
</div>
<div class="card mb-4">
<div class="card-header">
<h5>{% trans "Recall Details" %}</h5>
</div>
<div class="card-body">
<div class="row mb-3">
<div class="col-md-6">
<strong>{% trans "Description:" %}</strong>
<p>{{ recall.description }}</p>
</div>
<div class="col-md-6">
<strong>{% trans "Sent:" %}</strong>
<p>{{ recall.created_at|date:"DATETIME_FORMAT" }}</p>
</div>
</div>
<div class="row">
<div class="col-md-3">
<strong>{% trans "Make:" %}</strong>
<p>{{ recall.make|default:"-" }}</p>
</div>
<div class="col-md-3">
<strong>{% trans "Model:" %}</strong>
<p>{{ recall.model|default:"-" }}</p>
</div>
<div class="col-md-3">
<strong>{% trans "Series:" %}</strong>
<p>{{ recall.serie|default:"-" }}</p>
</div>
</div>
</div>
</div>
<div class="card">
<div class="card-header">
<h5>{% trans "Notifications Sent" %}</h5>
</div>
<div class="card-body">
<div class="table-responsive">
<table class="table table-striped">
<thead>
<tr>
<th>{% trans "Dealer" %}</th>
<th>{% trans "Cars Affected" %}</th>
<th>{% trans "Notification Date" %}</th>
</tr>
</thead>
<tbody>
{% for notification in notifications %}
<tr>
<td>{{ notification.dealer.name }}</td>
<td>{{ notification.cars_affected.count }}</td>
<td>{{ notification.sent_at|date:"SHORT_DATETIME_FORMAT" }}</td>
</tr>
{% empty %}
<tr>
<td colspan="3" class="text-center">{% trans "No notifications sent" %}</td>
</tr>
{% endfor %}
</tbody>
</table>
</div>
</div>
</div>
</div>
{% endblock %}

View File

@ -0,0 +1,48 @@
{% extends "base.html" %}
{% load i18n %}
{% block content %}
{% block title %}
{% trans 'Recall' %}
{% endblock %}
<form hx-boost="true" action="{% url 'recall_filter' %}" method="post" hx-target=".table-responsive" hx-select=".table-responsive" hx-swap="outerHTML" id="recall-filter-form"
hx-on::after-request="resetSubmitButton(document.querySelector('#recall-filter-form button[type=submit]'))"
>
{% csrf_token %}
<div class="row">
<div class="col-md-3"">
{% include "purchase_orders/partials/po-select.html" with name="make" target="model" data=make_data pk=po_model.pk %}
</div>
<div class="col-md-3"">
{% include "purchase_orders/partials/po-select.html" with name="model" target="serie" data=model_data pk=po_model.pk %}
</div>
<div class="col-md-3">
<label for="id_year">{% trans "Year" %}</label>
<input type="number" name="year" id="id_year" class="form-control" hx-get="{% url 'inventory_items_filter' request.dealer.slug %}"
hx-target="#serie"
hx-select="#serie"
hx-include="#model"
hx-trigger="input delay:500ms"
hx-swap="outerHTML" required>
</div>
<div class="col-md-3"">
{% include "purchase_orders/partials/po-select.html" with name="serie" target="trim" data=serie_data pk=po_model.pk %}
</div>
<div class="col-md-3"">
{% include "purchase_orders/partials/po-select.html" with name="trim" target="none" data=trim_data pk=po_model.pk %}
</div>
</div>
<div class="row">
<div class="col-md-12 mt-3">
<button type="submit" class="btn btn-lg btn-phoenix-primary md-me-2"><i class="fa fa-filter" aria-hidden="true"></i> {% trans "Filter" %}</button>
</div>
</div>
</div>
</form>
<!-- Results table -->
<div class="table-responsive mt-3">
{% include "recalls/partials/recall_cars_table.html" %}
</div>
{% endblock content %}

View File

@ -0,0 +1,92 @@
<!-- templates/recalls/recall_list.html -->
{% extends "base.html" %}
{% load i18n humanize %}
{% block content %}
<div class="container mt-4">
<h2>{% trans "Recall History" %}</h2>
<div class="card mb-4">
<div class="card-body">
<div class="table-responsive">
<table class="table table-striped table-hover">
<thead class="thead-dark">
<tr>
<th>{% trans "Title" %}</th>
<th>{% trans "Sent" %}</th>
<th>{% trans "Make" %}</th>
<th>{% trans "Model" %}</th>
<th>{% trans "Series" %}</th>
<th>{% trans "Affected Dealers" %}</th>
<th>{% trans "Affected Cars" %}</th>
<th>{% trans "Actions" %}</th>
</tr>
</thead>
<tbody>
{% for recall in recalls %}
<tr>
<td>{{ recall.title }}</td>
<td>
<span title="{{ recall.created_at }}">
{{ recall.created_at|naturaltime }}
</span>
</td>
<td><img src="{{ recall.make.logo.url }}" width="50" height="50"> &nbsp;{{ recall.make|default:"-" }}</td>
<td>{{ recall.model|default:"-" }}</td>
<td>{{ recall.serie|default:"-" }}</td>
<td>{{ recall.dealer_count }}</td>
<td>{{ recall.car_count }}</td>
<td>
<a href="{% url 'recall_detail' recall.id %}"
class="btn btn-sm btn-info"
title="{% trans 'View details' %}">
<i class="fas fa-eye"></i>
</a>
</td>
</tr>
{% empty %}
<tr>
<td colspan="8" class="text-center">{% trans "No recalls found" %}</td>
</tr>
{% endfor %}
</tbody>
</table>
</div>
{% if is_paginated %}
<nav aria-label="Page navigation">
<ul class="pagination justify-content-center mt-4">
{% if page_obj.has_previous %}
<li class="page-item">
<a class="page-link" href="?page={{ page_obj.previous_page_number }}">
{% trans "Previous" %}
</a>
</li>
{% endif %}
{% for num in page_obj.paginator.page_range %}
{% if page_obj.number == num %}
<li class="page-item active">
<span class="page-link">{{ num }}</span>
</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 }}">
{% trans "Next" %}
</a>
</li>
{% endif %}
</ul>
</nav>
{% endif %}
</div>
</div>
</div>
{% endblock %}

View File

@ -0,0 +1,16 @@
<!-- templates/recalls/recall_success.html -->
{% extends "base.html" %}
{% load i18n %}
{% block content %}
<div class="container mt-4">
<div class="alert alert-success">
<h4 class="alert-heading">{% trans "Recall Initiated Successfully!" %}</h4>
<p>{% trans "The recall has been created and notifications have been sent to all affected dealers." %}</p>
<hr>
<a href="{% url 'recall_filter' %}" class="btn btn-primary">
{% trans "Back to Recall Management" %}
</a>
</div>
</div>
{% endblock %}

View File

@ -240,7 +240,9 @@
<tbody>
{% for item in data.cars %}
<tr>
<td class="align-middle"></td>
<td class="align-middle">
<img src="{{item.logo}}" width="40" height="40" class="ps-2"></img>
</td>
<td class="align-middle">{{ item.make }}</td>
<td class="align-middle">{{ item.model }}</td>
<td class="align-middle">{{ item.year }}</td>
@ -273,7 +275,7 @@
<tr class="bg-body-secondary total-sum">
<td class="align-middle ps-4 fw-semibold text-body-highlight" colspan="7">{% trans "Vat" %} ({{ data.vat }})</td>
<td class="align-middle text-start fw-semibold">
<span id="grand-total">+ {{ data.total_vat_amount|floatformat }}<span class="icon-saudi_riyal"></span></span>
<span id="">+ {{ data.total_vat_amount }}<span class="icon-saudi_riyal"></span></span>
</td>
</tr>
<tr class="bg-body-secondary total-sum">
@ -336,19 +338,14 @@
{% endblock %}
{% block customJS %}
<script>
// Initialize when page loads and after HTMX swaps
// Initialize when page loads and after HTMX swaps
document.addEventListener('DOMContentLoaded', initEstimateFunctions);
document.addEventListener('htmx:afterSwap', initEstimateFunctions);
function initEstimateFunctions() {
// Initialize calculateTotals if estimate table exists
const estimateTable = document.getElementById('estimate-table');
if (estimateTable) {
calculateTotals();
// Optional: If you need to recalculate when table content changes
estimateTable.addEventListener('change', calculateTotals);
}
// Initialize form action setter if form exists
const confirmForm = document.getElementById('confirmForm');
@ -359,6 +356,7 @@ function initEstimateFunctions() {
button.addEventListener('click', setFormActionHandler);
});
}
htmx.process(confirmForm)
}
function calculateTotals() {
@ -412,6 +410,7 @@ function setFormActionHandler(event) {
function setFormAction(action) {
const form = document.getElementById('confirmForm');
htmx.process(form)
if (!form) return;
const baseUrl = "{% url 'estimate_mark_as' request.dealer.slug estimate.pk %}";

View File

@ -10,7 +10,7 @@
<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">
<h3 class="mb-0 fs-4 text-center">
{{ _("Add Invoice") }}<i class="fa-solid fa-receipt ms-2 text-primary"></i>
</h3>
</div>

View File

@ -5,7 +5,7 @@
{% endblock title %}
{% block content %}
{% if invoices %}
{% if invoices or request.GET.q%}
<div class="row mt-4">
<div class="row g-3 justify-content-between mb-4">
<div class="col-auto">

View File

@ -6,7 +6,7 @@
{% endblock title %}
{% block content %}
{%if users %}
{%if users or request.GET.q%}
<section class="">
<div class="row mt-4">
<div class="col-auto">