From fa111b159fb32ef158888ab754b26db3822863f3 Mon Sep 17 00:00:00 2001 From: ismail Date: Wed, 23 Jul 2025 17:29:32 +0300 Subject: [PATCH] rebase with marwan update --- inventory/models.py | 6 +- inventory/urls.py | 2 +- inventory/views.py | 103 +++++++++++------- static/css/custom.css | 42 +++++++ static/spinner.svg | 1 + templates/account/logout.html | 2 +- templates/base.html | 31 +++++- templates/components/email_modal.html | 28 ++--- templates/components/note_modal.html | 8 ++ templates/components/task_modal.html | 6 + templates/crm/leads/lead_detail.html | 48 ++++++-- templates/crm/leads/lead_list.html | 66 ----------- .../crm/leads/partials/update_action.html | 10 +- templates/header.html | 14 +-- templates/inventory/car_form.html | 2 +- templates/modal/delete_modal.html | 4 + 16 files changed, 224 insertions(+), 149 deletions(-) create mode 100644 static/spinner.svg diff --git a/inventory/models.py b/inventory/models.py index 1a60b673..0cddfef4 100644 --- a/inventory/models.py +++ b/inventory/models.py @@ -660,7 +660,7 @@ class Car(Base): remarks = models.TextField(blank=True, null=True, verbose_name=_("Remarks")) mileage = models.IntegerField(blank=True, null=True, verbose_name=_("Mileage")) receiving_date = models.DateTimeField(verbose_name=_("Receiving Date")) - sold_date=models.DateTimeField(verbose_name=_("Sold Date")) + sold_date=models.DateTimeField(verbose_name=_("Sold Date"),null=True,blank=True) hash = models.CharField( max_length=64, blank=True, null=True, verbose_name=_("Hash") ) @@ -3361,9 +3361,9 @@ class ExtraInfo(models.Model): ) # qs = qs.select_related("customer","estimate","invoice") data = SaleOrder.objects.filter(pk__in=[x.content_object.sale_orders.select_related("customer","estimate","invoice").first().pk for x in qs if x.content_object.sale_orders.first()]) - + return data - + # return [ # x.content_object.sale_orders.select_related( # "customer", "estimate", "invoice" diff --git a/inventory/urls.py b/inventory/urls.py index 152dbf61..5e50f177 100644 --- a/inventory/urls.py +++ b/inventory/urls.py @@ -1251,7 +1251,7 @@ urlpatterns = [ name="po-action-mark-as-void", ), - # reports + # reports path( "/purchase-report/", views.purchase_report_view, diff --git a/inventory/views.py b/inventory/views.py index bd48499d..8bebe435 100644 --- a/inventory/views.py +++ b/inventory/views.py @@ -1494,7 +1494,7 @@ class CarFinanceCreateView(LoginRequiredMixin, PermissionRequiredMixin, CreateVi form_class = forms.CarFinanceForm template_name = "inventory/car_finance_form.html" permission_required = ["inventory.add_carfinance"] - + def dispatch(self, request, *args, **kwargs): self.car = get_object_or_404(models.Car, slug=self.kwargs["slug"]) return super().dispatch(request, *args, **kwargs) @@ -4320,8 +4320,8 @@ def sales_list_view(request, dealer_slug): item transactions specific to the user's entity. :rtype: HttpResponse """ - - dealer = get_object_or_404(models.Dealer, slug=dealer_slug) + + dealer = get_object_or_404(models.Dealer, slug=dealer_slug) staff = getattr(request.user.staffmember, "staff", None) qs = [] try: @@ -4459,9 +4459,9 @@ class EstimateListView(LoginRequiredMixin, PermissionRequiredMixin, ListView): if search_query: print("inside") queryset = queryset.filter( - Q(estimate_number__icontains=search_query) + Q(estimate_number__icontains=search_query) - ).distinct() + ).distinct() return queryset @@ -6104,6 +6104,7 @@ def update_lead_actions(request, dealer_slug): current_action = request.POST.get("current_action") next_action = request.POST.get("next_action") next_action_date = request.POST.get("next_action_date", None) + lead = models.Lead.objects.get(id=lead_id) if not all([lead_id, current_action, next_action]): # Log for missing required fields @@ -6111,12 +6112,17 @@ def update_lead_actions(request, dealer_slug): f"User {user_username} submitted incomplete data to update lead actions " f"for dealer '{dealer_slug}'. Missing fields: lead_id='{lead_id}', current_action='{current_action}', next_action='{next_action}'." ) - return JsonResponse( - {"success": False, "message": "All fields are required"}, status=400 + messages.error( + request, + _("All fields are required") ) + return redirect("lead_detail", dealer_slug=dealer_slug, slug=lead.slug) + # return JsonResponse( + # {"success": False, "message": "All fields are required"}, status=400 + # ) # Get the lead - lead = models.Lead.objects.get(id=lead_id) + # Update lead fields @@ -6146,9 +6152,14 @@ def update_lead_actions(request, dealer_slug): f"submitted invalid date format ('{next_action_date}') " f"for Lead ID: {lead.pk}. Error: {ve}" ) - return JsonResponse( - {"success": False, "message": "Invalid date format"}, status=400 + messages.error( + request, + _("Invalid date format") ) + return redirect("lead_detail", dealer_slug=dealer_slug, slug=lead.slug) + # return JsonResponse( + # {"success": False, "message": "Invalid date format"}, status=400 + # ) # Save the lead lead.save() # --- Logging for successful update (main try block success) --- @@ -6156,9 +6167,14 @@ def update_lead_actions(request, dealer_slug): f"User {user_username} successfully updated Lead ID: {lead.pk} ('{lead.slug}'). " f"New Status: '{lead.status}', Next Action: '{lead.next_action}', Next Action Date: '{lead.next_action_date}'." ) - return JsonResponse( - {"success": True, "message": "Actions updated successfully"} + messages.success( + request, + _("Actions updated successfully") ) + return redirect("lead_detail", dealer_slug=dealer_slug, slug=lead.slug) + # return JsonResponse( + # {"success": True, "message": "Actions updated successfully"} + # ) except models.Lead.DoesNotExist: # --- Logging for Lead not found --- @@ -6166,7 +6182,12 @@ def update_lead_actions(request, dealer_slug): f"User {user_username} attempted to update non-existent Lead with ID: '{lead_id}' " f"for dealer '{dealer_slug}'. Returning 404." ) - return JsonResponse({"success": False, "message": "Lead not found"}, status=404) + messages.error( + request, + _("Lead not found") + ) + return redirect("lead_detail", dealer_slug=dealer_slug, slug=lead.slug) + # return JsonResponse({"success": False, "message": "Lead not found"}, status=404) except Exception as e: involved_lead_id = request.POST.get("lead_id", "N/A") logger.error( @@ -6174,7 +6195,12 @@ def update_lead_actions(request, dealer_slug): f"for dealer '{dealer_slug}'. Error: {e}", exc_info=True, # CRUCIAL: Includes the full traceback ) - return JsonResponse({"success": False, "message": str(e)}, status=500) + messages.error( + request, + _("An error occurred while updating lead actions") + ) + return redirect("lead_detail", dealer_slug=dealer_slug, slug=lead.slug) + # return JsonResponse({"success": False, "message": str(e)}, status=500) class LeadUpdateView(LoginRequiredMixin, PermissionRequiredMixin, UpdateView): @@ -6558,7 +6584,7 @@ def lead_transfer(request, dealer_slug, slug): messages.success(request, _("Lead transferred successfully")) else: messages.error(request, f"Invalid form data: {str(form.errors)}") - return redirect("lead_list", dealer_slug=dealer.slug) + return redirect("lead_detail", dealer_slug=dealer.slug ,slug=lead.slug) @login_required @@ -10399,7 +10425,7 @@ def upload_cars(request, dealer_slug, pk=None): form = forms.CSVUploadForm() form.fields["vendor"].queryset = dealer.vendors.all() - print(request) + return render( request, "csv_upload.html", @@ -10451,16 +10477,16 @@ def purchase_report_view(request,dealer_slug): pos = request.entity.get_purchase_orders() data = [] total_po_amount=0 - total_po_cars=0 + total_po_cars=0 for po in pos: items = [{"total":x.total_amount,"q":x.quantity} for x in po.get_itemtxs_data()[0].all()] - + po_amount=0 po_quantity=0 for item in items: po_amount+=item["total"] po_quantity+=item["q"] - + total_po_amount+=po_amount total_po_cars+=po_quantity bills=po.get_po_bill_queryset() @@ -10468,7 +10494,7 @@ def purchase_report_view(request,dealer_slug): vendors_str = ", ".join(sorted(list(vendors))) if vendors else "N/A" data.append({"po_number":po.po_number,"po_created":po.created,"po_status":po.po_status,"po_fulfilled_date":po.date_fulfilled,"po_amount":po_amount, "po_quantity":po_quantity,"vendors_str":vendors_str}) - + current_time = timezone.now().strftime("%Y-%m-%d %H:%M:%S") context={ "dealer":request.entity.name, @@ -10479,23 +10505,23 @@ def purchase_report_view(request,dealer_slug): "current_time":current_time } - - + + return render(request,'ledger/reports/purchase_report.html',context) def purchase_report_csv_export(request,dealer_slug): response = HttpResponse(content_type='text/csv') - - + + current_time = timezone.now().strftime("%Y-%m-%d_%H%M%S") filename = f"purchase_report_{dealer_slug}_{current_time}.csv" response['Content-Disposition'] = f'attachment; filename="{filename}"' writer = csv.writer(response) - + header = [ 'PO Number', 'Created Date', @@ -10507,7 +10533,7 @@ def purchase_report_csv_export(request,dealer_slug): ] writer.writerow(header) pos = request.entity.get_purchase_orders() - + for po in pos: po_amount = 0 po_quantity = 0 @@ -10516,10 +10542,10 @@ def purchase_report_csv_export(request,dealer_slug): for item in items: po_amount += item["total"] po_quantity += item["q"] - + bills = po.get_po_bill_queryset() vendors = set([bill.vendor.vendor_name for bill in bills ]) - vendors_str = ", ".join(sorted(list(vendors))) if vendors else "N/A" + vendors_str = ", ".join(sorted(list(vendors))) if vendors else "N/A" writer.writerow([ @@ -10527,7 +10553,7 @@ def purchase_report_csv_export(request,dealer_slug): po.created.strftime("%Y-%m-%d %H:%M:%S") if po.created else '', po.get_po_status_display(), po.date_fulfilled.strftime("%Y-%m-%d") if po.date_fulfilled else '', - f"{po_amount:.2f}", + f"{po_amount:.2f}", po_quantity, vendors_str ]) @@ -10540,13 +10566,13 @@ def car_sale_report_view(request,dealer_slug): current_time = timezone.now().strftime("%Y-%m-%d %H:%M:%S") context={'cars_sold':cars_sold,'current_time':current_time } return render(request,'ledger/reports/car_sale_report.html',context) - + def car_sale_report_csv_export(request,dealer_slug): - + response = HttpResponse(content_type='text/csv') - - + + current_time = timezone.now().strftime("%Y-%m-%d %H:%M:%S") filename = f"sales_report_{dealer_slug}_{current_time}.csv" response['Content-Disposition'] = f'attachment; filename="{filename}"' @@ -10555,7 +10581,7 @@ def car_sale_report_csv_export(request,dealer_slug): header=[ 'Make', - 'VIN', + 'VIN', 'Model', 'Year', 'Serie', @@ -10572,16 +10598,16 @@ def car_sale_report_csv_export(request,dealer_slug): 'Invoice Number', ] writer.writerow(header) - + dealer=get_object_or_404(models.Dealer,slug=dealer_slug) cars_sold=models.Car.objects.filter(dealer=dealer,status='sold') for car in cars_sold: writer.writerow([ - car.vin, + car.vin, car.id_car_make.name, car.id_car_model.name, car.year, - car.id_car_serie.name, + car.id_car_serie.name, car.id_car_trim.name, car.mileage, car.stock_type, @@ -10595,5 +10621,4 @@ def car_sale_report_csv_export(request,dealer_slug): car.item_model.invoicemodel_set.first().invoice_number ]) - return response - \ No newline at end of file + return response diff --git a/static/css/custom.css b/static/css/custom.css index b61b11af..987242ff 100644 --- a/static/css/custom.css +++ b/static/css/custom.css @@ -132,3 +132,45 @@ html[dir="rtl"] .form-icon-container .form-control { to { transform: rotate(360deg); } } + +#spinner { + position: fixed; + top: 50%; + left: 50%; + transform: translate(-50%, -50%); + z-index: 10; +} + +#spinner-bg { + position: fixed; + top: 0; + left: 0; + width: 100%; + height: 100%; + background-color: rgba(255, 255, 255, 0.7); + opacity: 0; + transition: opacity 500ms ease-in; + z-index: 5; +} + +#spinner-bg.htmx-request { + opacity: .8; +} + + +/* .fade-me-in.htmx-added { + opacity: 0; + } +.fade-me-in { + opacity: .9; + transition: opacity 300ms ease-out; +} */ + +#main_content.fade-me-in:not(.modal):not(.modal *) { + opacity: 1; + transition: opacity 300ms ease-out; +} + +#main_content.fade-me-in.htmx-added:not(.modal):not(.modal *) { + opacity: 0; +} \ No newline at end of file diff --git a/static/spinner.svg b/static/spinner.svg new file mode 100644 index 00000000..2cd994df --- /dev/null +++ b/static/spinner.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/templates/account/logout.html b/templates/account/logout.html index c3e29a7b..133adebe 100644 --- a/templates/account/logout.html +++ b/templates/account/logout.html @@ -13,7 +13,7 @@

{{ _("Are you sure you want to sign out?") }}

-
+ {% csrf_token %} {{ redirect_field }}
diff --git a/templates/base.html b/templates/base.html index b9dcc9f9..93dc619f 100644 --- a/templates/base.html +++ b/templates/base.html @@ -69,7 +69,7 @@ {% comment %} {% endcomment %} - {% block customCSS %}{% endblock %} + {% comment %} {% block customCSS %}{% endblock %} {% endcomment %} {% include "toast-alert.html" %} @@ -81,10 +81,20 @@ {% include "plans/expiration_messages.html" %} {% block period_navigation %} {% endblock period_navigation %} -
- {% block content %} - {% endblock content %} - +
+
+ +
+ {% block customCSS %}{% endblock %} + {% block content %}{% endblock content %} + {% block customJS %}{% endblock %} + + {% comment %} + + + + {% endcomment %} +
{% block body %} {% endblock body %} @@ -136,7 +146,16 @@ let datePickers = document.querySelectorAll("[id^='djl-datepicker']") datePickers.forEach(dp => djLedger.getCalendar(dp.attributes.id.value, dateNavigationUrl)) {% endif %} + + /*document.body.addEventListener('htmx:configRequest', function(evt) { + evt.detail.indicators = [ + ...(evt.detail.indicators || []), + document.getElementById('global-indicator') + ]; + });*/ + + - {% block customJS %}{% endblock %} + {% comment %} {% block customJS %}{% endblock %} {% endcomment %} diff --git a/templates/components/email_modal.html b/templates/components/email_modal.html index a18766c0..47a1aa41 100644 --- a/templates/components/email_modal.html +++ b/templates/components/email_modal.html @@ -1,16 +1,16 @@ {% load i18n crispy_forms_tags %} \ No newline at end of file + +
\ No newline at end of file diff --git a/templates/components/note_modal.html b/templates/components/note_modal.html index 5079960c..e3f5f452 100644 --- a/templates/components/note_modal.html +++ b/templates/components/note_modal.html @@ -16,6 +16,13 @@
-
+
{{ _("Status") }} {% if lead.status == "new" %} {{ _("New") }} @@ -103,7 +104,7 @@
-
+
{{ _("Assigned To") }}
@@ -219,7 +220,7 @@
-
+
{{ lead.status|capfirst }}
@@ -303,6 +304,11 @@