From 2684e53f7878ae7813fdc698975c859c3621768b Mon Sep 17 00:00:00 2001 From: ismail Date: Sun, 3 Aug 2025 13:09:24 +0300 Subject: [PATCH] updates on the po and bill --- car_inventory/urls.py | 1 - inventory/forms.py | 62 +++++- inventory/middleware.py | 21 +- inventory/models.py | 63 ++++++ inventory/urls.py | 10 + inventory/views.py | 193 +++++++++++++++++- templates/base.html | 2 +- templates/bill/bill_detail.html | 25 ++- templates/bill/includes/card_bill.html | 129 ++++++------ .../purchase_orders/includes/card_po.html | 102 ++++----- .../recalls/email/recall_notification.txt | 24 +++ .../recalls/partials/recall_cars_table.html | 35 ++++ .../recalls/partials/recall_filter_form.html | 79 +++++++ templates/recalls/recall_create.html | 80 ++++++++ templates/recalls/recall_detail.html | 82 ++++++++ templates/recalls/recall_filter.html | 48 +++++ templates/recalls/recall_list.html | 92 +++++++++ templates/recalls/recall_success.html | 16 ++ 18 files changed, 921 insertions(+), 143 deletions(-) create mode 100644 templates/recalls/email/recall_notification.txt create mode 100644 templates/recalls/partials/recall_cars_table.html create mode 100644 templates/recalls/partials/recall_filter_form.html create mode 100644 templates/recalls/recall_create.html create mode 100644 templates/recalls/recall_detail.html create mode 100644 templates/recalls/recall_filter.html create mode 100644 templates/recalls/recall_list.html create mode 100644 templates/recalls/recall_success.html diff --git a/car_inventory/urls.py b/car_inventory/urls.py index b2a078d7..4ab074af 100644 --- a/car_inventory/urls.py +++ b/car_inventory/urls.py @@ -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)), ) diff --git a/inventory/forms.py b/inventory/forms.py index 99ce9d4e..e80e6e4a 100644 --- a/inventory/forms.py +++ b/inventory/forms.py @@ -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'}) - ) \ No newline at end of file + ) + +# 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'}), + } + diff --git a/inventory/middleware.py b/inventory/middleware.py index c42a6132..8a022f5c 100644 --- a/inventory/middleware.py +++ b/inventory/middleware.py @@ -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: diff --git a/inventory/models.py b/inventory/models.py index e2653559..6dd4c092 100644 --- a/inventory/models.py +++ b/inventory/models.py @@ -3411,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}" \ No newline at end of file diff --git a/inventory/urls.py b/inventory/urls.py index 19a79ef8..86363bf6 100644 --- a/inventory/urls.py +++ b/inventory/urls.py @@ -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( "/purchase_orders//delete//", views.PurchaseOrderModelDeleteView.as_view(), @@ -1271,6 +1276,11 @@ urlpatterns = [ ), path('car-sale-report//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//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" diff --git a/inventory/views.py b/inventory/views.py index 89b95e9b..a1622737 100644 --- a/inventory/views.py +++ b/inventory/views.py @@ -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 @@ -10151,10 +10151,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") @@ -10763,4 +10802,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}) \ No newline at end of file + 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' \ No newline at end of file diff --git a/templates/base.html b/templates/base.html index 53d4dbdf..5c3cf542 100644 --- a/templates/base.html +++ b/templates/base.html @@ -84,7 +84,7 @@ {% include "plans/expiration_messages.html" %} {% block period_navigation %} {% endblock period_navigation %} -
+
diff --git a/templates/bill/bill_detail.html b/templates/bill/bill_detail.html index 25d9cafe..a350d14a 100644 --- a/templates/bill/bill_detail.html +++ b/templates/bill/bill_detail.html @@ -8,17 +8,16 @@ {% block content%}
- diff --git a/templates/bill/includes/card_bill.html b/templates/bill/includes/card_bill.html index 76eb1630..3d829e36 100644 --- a/templates/bill/includes/card_bill.html +++ b/templates/bill/includes/card_bill.html @@ -1,6 +1,6 @@ {% load django_ledger %} {% load i18n %} -
+
{% if not create_bill %} {% if style == 'dashboard' %} @@ -54,7 +54,7 @@ {% trans 'View' %} {% if perms.django_ledger.change_billmodel %} - {% trans 'Update' %} {% if bill.can_pay %} - - {% if bill.can_draft %} - + + {% if "detail" not in request.path %} + + {% if bill.can_draft %} + + {% endif %} + + {% if bill.can_review %} + + {% endif %} + {% endif %} - - {% if bill.can_review %} - - {% endif %} - - {% if bill.can_approve and perms.django_ledger.can_approve_billmodel %} - - {% endif %} - {% if bill.can_approve and not request.is_manager %} - - {% endif %} - - {% if bill.can_pay %} - - {% endif %} - - {% if bill.can_void %} - - {% endif %} - - {% if bill.can_cancel %} - - {% 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 %} + + {% else %} + {% if bill.can_approve and perms.django_ledger.can_approve_billmodel %} + + {% endif %} + {% endif %} + + {% if "detail" not in request.path %} + {% if bill.can_pay %} + + {% endif %} + + {% if bill.can_void %} + + {% endif %} + + {% if bill.can_cancel %} + + {% 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 %}
@@ -289,7 +294,10 @@ } diff --git a/templates/purchase_orders/includes/card_po.html b/templates/purchase_orders/includes/card_po.html index f74f311d..1465c2de 100644 --- a/templates/purchase_orders/includes/card_po.html +++ b/templates/purchase_orders/includes/card_po.html @@ -2,7 +2,10 @@ {% load django_ledger %} {% load widget_tweaks %} {% if not create_po %} {% if style == 'po-detail' %} @@ -106,52 +110,54 @@ class="btn btn-phoenix-primary"> {% trans 'Update' %} -
- {% if po_model.can_draft %} - - {% endif %} - {% if po_model.can_review %} - - {% endif %} - {% if po_model.can_approve %} - - {% endif %} - {% if po_model.can_fulfill %} - - {% endif %} - {% if po_model.can_delete %} - {% if perms.django_ledger.delete_purchaseordermodel %} - {% endif %} - {% endif %} - {% if po_model.can_void %} - - {% endif %} - {% if po_model.can_cancel %} - - {% endif %} -
+ {% if po_model.can_review %} + + {% endif %} + {% if po_model.can_approve %} + + {% endif %} + {% if po_model.can_fulfill %} + + {% endif %} + {% if po_model.can_delete %} + {% if perms.django_ledger.delete_purchaseordermodel %} + + {% endif %} + {% endif %} + {% if po_model.can_void %} + + {% endif %} + {% if po_model.can_cancel %} + + {% endif %} +
+ {% endif %}
{% endif %}
diff --git a/templates/recalls/email/recall_notification.txt b/templates/recalls/email/recall_notification.txt new file mode 100644 index 00000000..644bc91d --- /dev/null +++ b/templates/recalls/email/recall_notification.txt @@ -0,0 +1,24 @@ + +{% 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 %} + +يرجى التواصل مع فريق خدمة العملاء لدينا على أرقام الهاتف التالية [أدخل الأرقام هنا] أو عبر البريد الإلكتروني [أدخل البريد هنا] لترتيب موعد لإجراء الإصلاحات اللازمة مجاناً. + +وتفضلوا بقبول فائق الاحترام والتقدير، +إدارة خدمة العملاء +شركة [تنحل] \ No newline at end of file diff --git a/templates/recalls/partials/recall_cars_table.html b/templates/recalls/partials/recall_cars_table.html new file mode 100644 index 00000000..7276599a --- /dev/null +++ b/templates/recalls/partials/recall_cars_table.html @@ -0,0 +1,35 @@ +{% load i18n %} +{% if cars %} + + + + + + + + + + + + + + + {% for car in cars %} + + + + + + + + + + + {% endfor %} + +
{% trans "VIN" %}{% trans "Make" %}{% trans "Model" %}{% trans "Series" %}{% trans "Trim" %}{% trans "Year" %}{% trans "Dealer" %}{% trans "Status" %}
{{ car.vin }}{{ car.id_car_make|default:"-" }}{{ car.id_car_model|default:"-" }}{{ car.id_car_serie|default:"-" }}{{ car.id_car_trim|default:"-" }}{{ car.year }}{{ car.dealer.name }}{{ car.get_status_display }}
+ + + {% trans "Recall Request" %} + +{% endif %} \ No newline at end of file diff --git a/templates/recalls/partials/recall_filter_form.html b/templates/recalls/partials/recall_filter_form.html new file mode 100644 index 00000000..402d99f4 --- /dev/null +++ b/templates/recalls/partials/recall_filter_form.html @@ -0,0 +1,79 @@ +{% load i18n %} +
+ +
+ + +
+ + +
+ + +
+ + +
+ + +
+ + +
+ + +
+
+ + +
+
+ + +
+
\ No newline at end of file diff --git a/templates/recalls/recall_create.html b/templates/recalls/recall_create.html new file mode 100644 index 00000000..dc9dbf16 --- /dev/null +++ b/templates/recalls/recall_create.html @@ -0,0 +1,80 @@ + +{% extends "base.html" %} +{% load i18n %} + +{% block content %} +
+

{% trans "Create New Recall" %}

+ +
+
+
{% trans "Recall Details" %}
+
+
+
+ {% csrf_token %} + +
+
+ {{ form.title.label_tag }} + {{ form.title }} + {% if form.title.errors %} +
+ {{ form.title.errors }} +
+ {% endif %} +
+
+ +
+
+ {{ form.description.label_tag }} + {{ form.description }} + {% if form.description.errors %} +
+ {{ form.description.errors }} +
+ {% endif %} +
+
+ +
+
+ {{ form.make.label_tag }} + {{ form.make }} +
+
+ {{ form.model.label_tag }} + {{ form.model }} +
+
+ {{ form.serie.label_tag }} + {{ form.serie }} +
+
+ {{ form.trim.label_tag }} + {{ form.trim }} +
+
+ {{ form.year_from.label_tag }} + {{ form.year_from }} +
+
+ {{ form.year_to.label_tag }} + {{ form.year_to }} +
+
+ +
+ + + {% trans "Cancel" %} + +
+
+
+
+
+{% endblock %} \ No newline at end of file diff --git a/templates/recalls/recall_detail.html b/templates/recalls/recall_detail.html new file mode 100644 index 00000000..8b1a5c45 --- /dev/null +++ b/templates/recalls/recall_detail.html @@ -0,0 +1,82 @@ + +{% extends "base.html" %} +{% load i18n humanize %} + +{% block content %} +
+
+
+ +

{{ recall.title }}

+
+ + {% trans "Back to Recall List" %} + +
+ +
+
+
{% trans "Recall Details" %}
+
+
+
+
+ {% trans "Description:" %} +

{{ recall.description }}

+
+
+ {% trans "Sent:" %} +

{{ recall.created_at|date:"DATETIME_FORMAT" }}

+
+
+ +
+
+ {% trans "Make:" %} +

{{ recall.make|default:"-" }}

+
+
+ {% trans "Model:" %} +

{{ recall.model|default:"-" }}

+
+
+ {% trans "Series:" %} +

{{ recall.serie|default:"-" }}

+
+
+
+
+ +
+
+
{% trans "Notifications Sent" %}
+
+
+
+ + + + + + + + + + {% for notification in notifications %} + + + + + + {% empty %} + + + + {% endfor %} + +
{% trans "Dealer" %}{% trans "Cars Affected" %}{% trans "Notification Date" %}
{{ notification.dealer.name }}{{ notification.cars_affected.count }}{{ notification.sent_at|date:"SHORT_DATETIME_FORMAT" }}
{% trans "No notifications sent" %}
+
+
+
+
+{% endblock %} \ No newline at end of file diff --git a/templates/recalls/recall_filter.html b/templates/recalls/recall_filter.html new file mode 100644 index 00000000..4b4afc4e --- /dev/null +++ b/templates/recalls/recall_filter.html @@ -0,0 +1,48 @@ +{% extends "base.html" %} +{% load i18n %} +{% block content %} +{% block title %} + {% trans 'Recall' %} +{% endblock %} + +
+ {% csrf_token %} +
+
+ {% include "purchase_orders/partials/po-select.html" with name="make" target="model" data=make_data pk=po_model.pk %} +
+
+ {% include "purchase_orders/partials/po-select.html" with name="model" target="serie" data=model_data pk=po_model.pk %} +
+
+ + +
+
+ {% include "purchase_orders/partials/po-select.html" with name="serie" target="trim" data=serie_data pk=po_model.pk %} +
+
+ {% include "purchase_orders/partials/po-select.html" with name="trim" target="none" data=trim_data pk=po_model.pk %} +
+
+
+
+ +
+
+ +
+ + +
+ {% include "recalls/partials/recall_cars_table.html" %} +
+ +{% endblock content %} diff --git a/templates/recalls/recall_list.html b/templates/recalls/recall_list.html new file mode 100644 index 00000000..ad1307cb --- /dev/null +++ b/templates/recalls/recall_list.html @@ -0,0 +1,92 @@ + +{% extends "base.html" %} +{% load i18n humanize %} + +{% block content %} +
+

{% trans "Recall History" %}

+ +
+
+
+ + + + + + + + + + + + + + + {% for recall in recalls %} + + + + + + + + + + + {% empty %} + + + + {% endfor %} + +
{% trans "Title" %}{% trans "Sent" %}{% trans "Make" %}{% trans "Model" %}{% trans "Series" %}{% trans "Affected Dealers" %}{% trans "Affected Cars" %}{% trans "Actions" %}
{{ recall.title }} + + {{ recall.created_at|naturaltime }} + +  {{ recall.make|default:"-" }}{{ recall.model|default:"-" }}{{ recall.serie|default:"-" }}{{ recall.dealer_count }}{{ recall.car_count }} + + + +
{% trans "No recalls found" %}
+
+ + {% if is_paginated %} + + {% endif %} +
+
+
+{% endblock %} \ No newline at end of file diff --git a/templates/recalls/recall_success.html b/templates/recalls/recall_success.html new file mode 100644 index 00000000..da8cd331 --- /dev/null +++ b/templates/recalls/recall_success.html @@ -0,0 +1,16 @@ + +{% extends "base.html" %} +{% load i18n %} + +{% block content %} +
+
+

{% trans "Recall Initiated Successfully!" %}

+

{% trans "The recall has been created and notifications have been sent to all affected dealers." %}

+
+ + {% trans "Back to Recall Management" %} + +
+
+{% endblock %} \ No newline at end of file