diff --git a/inventory/models.py b/inventory/models.py index f8a5cdb3..6af7da08 100644 --- a/inventory/models.py +++ b/inventory/models.py @@ -369,42 +369,34 @@ class CarFinance(models.Model): selling_price = models.DecimalField(max_digits=14, decimal_places=2, verbose_name=_("Selling Price")) discount_amount = models.DecimalField(max_digits=14, decimal_places=2, verbose_name=_("Discount Amount"), default=Decimal('0.00')) - # profit_margin = models.DecimalField(max_digits=14, - # decimal_places=2, - # verbose_name=_("Profit Margin"), - # editable=False) - # vat_amount = models.DecimalField(max_digits=14, - # decimal_places=2, - # verbose_name=_("Vat Amount"), - # editable=False,default=Decimal('0.00')) - # registration_fee = models.DecimalField(max_digits=14, decimal_places=2, verbose_name=_("Registration Fee"), - # default=Decimal('0.00')) - # administration_fee = models.DecimalField(max_digits=14, decimal_places=2, verbose_name=_("Administration Fee"), - # default=Decimal('0.00')) - # transportation_fee = models.DecimalField(max_digits=14, decimal_places=2, verbose_name=_("Transportation Fee"), - # default=Decimal('0.00')) - # custom_card_fee = models.DecimalField(max_digits=14, decimal_places=2, verbose_name=_("Custom Card Fee"), - # default=Decimal('0.00')) + @property - def total(self): - total = 0 + def total(self): + total = self.selling_price + if self.additional_services.count() != 0: total_additional_services = sum(x.default_amount for x in self.additional_services.all()) - total = self.selling_price + total_additional_services - else: - total = self.selling_price - if self.discount_amount != 0: - total = total - self.discount_amount + total += total_additional_services + return total + @property + def total_discount(self): + if self.discount_amount != 0: + total = self.total - self.discount_amount + return total + return self.total + + @property + def total_vat(self): + return self.total_discount + self.vat_amount + @property def vat_amount(self): vat = VatRate.objects.filter(is_active=True).first() - return (self.total * vat.vat_rate).quantize(Decimal('0.01')) - - @property - def total_vat(self): - return self.total + self.vat_amount + if vat: + return (self.total_discount * Decimal(vat.vat_rate)).quantize(Decimal('0.01')) + return Decimal('0.00') diff --git a/inventory/utils.py b/inventory/utils.py index db755765..1bdeb3c2 100644 --- a/inventory/utils.py +++ b/inventory/utils.py @@ -1,3 +1,4 @@ +from decimal import Decimal from django.shortcuts import redirect from django.contrib import messages from django.utils import timezone @@ -90,4 +91,11 @@ def reserve_car(car,request): except Exception as e: messages.error(request, f"Error reserving car: {e}") - return redirect("car_detail", pk=car.pk) \ No newline at end of file + return redirect("car_detail", pk=car.pk) + + +def calculate_vat_amount(amount): + vat = models.VatRate.objects.filter(is_active=True).first() + if vat: + return ((amount * Decimal(vat.vat_rate)).quantize(Decimal('0.01')),vat.vat_rate) + return amount \ No newline at end of file diff --git a/inventory/views.py b/inventory/views.py index 9313148e..1cee636f 100644 --- a/inventory/views.py +++ b/inventory/views.py @@ -59,7 +59,13 @@ from . import models, forms from django.contrib.auth.mixins import PermissionRequiredMixin from django.contrib.messages.views import SuccessMessageMixin from django.contrib.auth.models import Group -from .utils import get_calculations, reserve_car, send_email, get_user_type +from .utils import ( + calculate_vat_amount, + get_calculations, + reserve_car, + send_email, + get_user_type, +) from django.contrib.auth.models import User from allauth.account import views from django.db.models import Count, F, Value @@ -629,8 +635,8 @@ class CustomCardCreateView(LoginRequiredMixin, CreateView): @login_required() def reserve_car_view(request, car_id): if request.method == "POST": - car = get_object_or_404(models.Car, pk=car_id) - if car.is_reserved(): + car = get_object_or_404(models.Car, pk=car_id) + if car.is_reserved(): messages.error(request, _("This car is already reserved.")) return redirect("car_detail", pk=car.pk) response = reserve_car(car, request) @@ -740,6 +746,14 @@ class CustomerDetailView(LoginRequiredMixin, DetailView): template_name = "customers/view_customer.html" context_object_name = "customer" + def get_context_data(self, **kwargs): + context = super().get_context_data(**kwargs) + name = f"{context['customer'].first_name} {context['customer'].middle_name} {context['customer'].last_name}" + context["estimates"] = self.request.entity.get_estimates().filter( + customer__customer_name=name + ) + return context + class CustomerCreateView( LoginRequiredMixin, @@ -1620,6 +1634,7 @@ def bank_account_delete(request, pk): # Accounts + class AccountListView(LoginRequiredMixin, ListView): model = AccountModel template_name = "ledger/coa_accounts/account_list.html" @@ -1785,7 +1800,7 @@ def create_estimate(request): "quantity": float(item.get("quantity")), "unit_cost": car_instance.finances.cost_price, "unit_revenue": car_instance.finances.selling_price, - "total_amount": car_instance.finances.cost_price + "total_amount": (car_instance.finances.total_vat) * int(item.get("quantity")), } ) @@ -1807,7 +1822,8 @@ def create_estimate(request): "unit_cost": instance.finances.cost_price, "unit_revenue": instance.finances.selling_price, "quantity": float(quantities), - "total_amount": instance.finances.total * int(quantities), + "total_amount": instance.finances.total_vat + * int(quantities), } } @@ -1820,9 +1836,9 @@ def create_estimate(request): if isinstance(items, list): for item in items: item_instance = ItemModel.objects.get(pk=item) - instance = models.Car.objects.get(vin=item_instance.name) - response = reserve_car(instance, request) - + instance = models.Car.objects.get(vin=item_instance.name) + reserve_car(instance, request) + else: item_instance = ItemModel.objects.get(pk=items) instance = models.Car.objects.get(vin=item_instance.name) @@ -1837,15 +1853,6 @@ def create_estimate(request): } ) - # except Exception as e: - # return JsonResponse( - # { - # "status": "error", - # "message": f"An error occurred while processing the request: {str(e)}", - # }, - # status=400, - # ) - form = EstimateModelCreateForm(entity_slug=entity.slug, user_model=entity.admin) car_list = models.Car.objects.filter( dealer=dealer, finances__selling_price__gt=0 @@ -1874,25 +1881,27 @@ class EstimateDetailView(LoginRequiredMixin, DetailView): estimate = kwargs.get("object") if estimate.get_itemtxs_data(): total = sum( - ( - x.ce_revenue_estimate - - models.Car.objects.get( + float( + models.Car.objects.get( vin=x.item_model.name - ).finances.discount_amount + ).finances.total ) + * float(x.ce_quantity) for x in estimate.get_itemtxs_data()[0].all() ) + discount_amount = sum( + models.CarFinance.objects.get( + car__vin=i.item_model.name + ).discount_amount + for i in estimate.get_itemtxs_data()[0].all() + ) vat = models.VatRate.objects.filter(is_active=True).first() - - # Calculate VAT and total with 2 decimal places - vat_amount = round(total * vat.vat_rate, 2) # Round to 2 decimal places - grand_total = round( - (total * vat.vat_rate) + total, 2 - ) # Round to 2 decimal places - - # Add values to the context + grand_total = float(total) - float(discount_amount) + vat_amount = round(float(grand_total) * float(vat.vat_rate), 2) + kwargs["vat_amount"] = vat_amount - kwargs["total"] = grand_total + kwargs["total"] = grand_total + vat_amount + kwargs["discount_amount"] = discount_amount kwargs["vat"] = vat.rate kwargs["invoice"] = ( InvoiceModel.objects.all().filter(ce_model=estimate).first() @@ -1941,52 +1950,25 @@ def estimate_mark_as(request, pk): if mark == "review": if not estimate.can_review(): messages.error(request, "Estimate is not ready for review") - return redirect("estimate_detail", pk=estimate.pk) + return redirect("estimate_detail", pk=estimate.pk) estimate.mark_as_review() elif mark == "approved": if not estimate.can_approve(): messages.error(request, "Estimate is not ready for approval") return redirect("estimate_detail", pk=estimate.pk) - estimate.mark_as_approved() - - messages.success(request, "Estimate approved successfully.") + estimate.mark_as_approved() + messages.success(request, "Estimate approved successfully.") elif mark == "rejected": if not estimate.can_cancel(): messages.error(request, "Estimate is not ready for rejection") return redirect("estimate_detail", pk=estimate.pk) estimate.mark_as_canceled() - messages.success(request, "Estimate canceled successfully.") - + messages.success(request, "Estimate canceled successfully.") elif mark == "completed": if not estimate.can_complete(): messages.error(request, "Estimate is not ready for completion") return redirect("estimate_detail", pk=estimate.pk) - - # invoice = entity.create_invoice(customer_model=estimate.customer, - # terms=estimate.terms, - # cash_account=entity.get_default_coa_accounts().get(name="Cash"), - # prepaid_account=entity.get_default_coa_accounts().get(name="Accounts Receivable"), - # coa_model=entity.get_default_coa() - # ) - - # unit_items = estimate.get_itemtxs_data()[0] - # invoice_itemtxs = { - # i.item_model.item_number: { - # 'unit_cost': i.ce_unit_cost_estimate, - # 'quantity': i.ce_quantity, - # 'total_amount': i.ce_cost_estimate - # } for i in unit_items - # } - - # invoice_itemtxs = invoice.migrate_itemtxs(itemtxs=invoice_itemtxs, - # commit=True, - # operation=InvoiceModel.ITEMIZE_APPEND) - # invoice.bind_estimate(estimate) - # invoice.mark_as_review() - # estimate.mark_as_completed() - # estimate.save() - # invoice.save() estimate.save() messages.success(request, "Estimate marked as " + mark.upper()) @@ -2012,15 +1994,27 @@ class InvoiceDetailView(LoginRequiredMixin, DetailView): def get_context_data(self, **kwargs): invoice = kwargs.get("object") + vat = models.VatRate.objects.filter(is_active=True).first() if invoice.get_itemtxs_data(): total = sum( - x.unit_cost * x.quantity for x in invoice.get_itemtxs_data()[0].all() + float(x.ce_revenue_estimate) * float(x.ce_quantity) + for x in invoice.ce_model.get_itemtxs_data()[0].all() ) - total = int(total) - vat = models.VatRate.objects.filter(is_active=True).first() - kwargs["vat_amount"] = total * vat.vat_rate - kwargs["total"] = (total * vat.vat_rate) + total + discount_amount = sum( + models.CarFinance.objects.get( + car__vin=i.item_model.name + ).discount_amount + for i in invoice.get_itemtxs_data()[0].all() + ) + + grand_total = float(total) - float(discount_amount) + vat_amount = round(float(grand_total) * float(vat.vat_rate), 2) + + + kwargs["vat_amount"] = vat_amount + kwargs["total"] = grand_total + vat_amount + kwargs["discount_amount"] = discount_amount kwargs["vat"] = vat.rate kwargs["payments"] = JournalEntryModel.objects.filter( ledger=invoice.ledger @@ -2080,13 +2074,27 @@ def invoice_create(request, pk): invoice_model.save() unit_items = estimate.get_itemtxs_data()[0] + + itemtxs = [] + for item in unit_items: + car = models.Car.objects.get(vin=item.item_model.name) + itemtxs.append( + { + "item_number": item.item_model.item_number, + "unit_cost": car.finances.total_vat, + "unit_revenue": car.finances.total_vat, + "quantity": item.ce_quantity, + "total_amount": float(car.finances.total_vat) + * float(item.ce_quantity), + } + ) invoice_itemtxs = { - i.item_model.item_number: { - "unit_cost": i.ce_unit_cost_estimate, - "quantity": i.ce_quantity, - "total_amount": i.ce_cost_estimate, + i.get("item_number"): { + "unit_cost": i.get("unit_cost"), + "quantity": i.get("quantity"), + "total_amount": i.get("total_amount"), } - for i in unit_items + for i in itemtxs } invoice_itemtxs = invoice_model.migrate_itemtxs( @@ -2232,59 +2240,6 @@ class UserActivityLogListView(ListView): return queryset -# email -def send_email_view(request, pk): - estimate = get_object_or_404(EstimateModel, pk=pk) - if request.method == "POST": - # if not estimate.can_review(): - # messages.error(request, "Estimate is not ready for review") - # return redirect("estimate_detail", pk=estimate.pk) - if not estimate.get_itemtxs_data()[0]: - messages.error(request, "Estimate has no items") - return redirect("estimate_detail", pk=estimate.pk) - - send_email( - "manager@tenhal.com", - request.POST.get("to"), - request.POST.get("subject"), - request.POST.get("message"), - ) - # estimate.mark_as_review() - messages.success(request, "Email sent successfully!") - return redirect("estimate_detail", pk=estimate.pk) - link = reverse_lazy("estimate_preview", kwargs={"pk": estimate.pk}) - msg = f""" - السلام عليكم - Dear {estimate.customer.customer_name}, - - أود أن أشارككم تقدير المشروع الذي ناقشناه. يرجى العثور على الوثيقة التفصيلية للمقترح المرفقة. - - I hope this email finds you well. I wanted to share with you the estimate for the project we discussed. Please find the detailed estimate document attached. - - يرجى مراجعة المقترح وإعلامي إذا كانت لديك أي أسئلة أو مخاوف. إذا كانت كل شيء يبدو جيدًا، يمكننا المضي قدمًا في المشروع. - - Please review the estimate and let me know if you have any questions or concerns. If everything looks good, we can proceed with the project. - - Estimate Link: - {link} - - شكراً لاهتمامكم بهذا الأمر. - Thank you for your attention to this matter. - - تحياتي, - Best regards, - [Your Name] - [Your Position] - [Your Company] - [Your Contact Information] - """ - return render( - request, - "sales/estimates/estimate_send.html", - {"estimate": estimate, "message": msg}, - ) - - # CRM RELATED VIEWS def create_lead(request, pk): customer = get_object_or_404(models.Customer, pk=pk) @@ -2459,3 +2414,73 @@ class SubscriptionPlans(ListView): model = models.SubscriptionPlan template_name = "subscriptions/subscription_plan.html" context_object_name = "plans" + + +# email +def send_email_view(request, pk): + estimate = get_object_or_404(EstimateModel, pk=pk) + if request.method == "POST": + # if not estimate.can_review(): + # messages.error(request, "Estimate is not ready for review") + # return redirect("estimate_detail", pk=estimate.pk) + if not estimate.get_itemtxs_data()[0]: + messages.error(request, "Estimate has no items") + return redirect("estimate_detail", pk=estimate.pk) + + send_email( + "manager@tenhal.com", + request.POST.get("to"), + request.POST.get("subject"), + request.POST.get("message"), + ) + estimate.mark_as_review() + messages.success(request, "Email sent successfully!") + return redirect("estimate_detail", pk=estimate.pk) + link = reverse_lazy("estimate_preview", kwargs={"pk": estimate.pk}) + msg = f""" + السلام عليكم + Dear {estimate.customer.customer_name}, + + أود أن أشارككم تقدير المشروع الذي ناقشناه. يرجى العثور على الوثيقة التفصيلية للمقترح المرفقة. + + I hope this email finds you well. I wanted to share with you the estimate for the project we discussed. Please find the detailed estimate document attached. + + يرجى مراجعة المقترح وإعلامي إذا كانت لديك أي أسئلة أو مخاوف. إذا كانت كل شيء يبدو جيدًا، يمكننا المضي قدمًا في المشروع. + + Please review the estimate and let me know if you have any questions or concerns. If everything looks good, we can proceed with the project. + + Estimate Link: + {link} + + شكراً لاهتمامكم بهذا الأمر. + Thank you for your attention to this matter. + + تحياتي, + Best regards, + [Your Name] + [Your Position] + [Your Company] + [Your Contact Information] + """ + return render( + request, + "sales/estimates/estimate_send.html", + {"estimate": estimate, "message": msg}, + ) + + +# errors +def custom_page_not_found_view(request, exception): + return render(request, "errors/404.html", {}) + + +def custom_error_view(request, exception=None): + return render(request, "errors/500.html", {}) + + +def custom_permission_denied_view(request, exception=None): + return render(request, "errors/403.html", {}) + + +def custom_bad_request_view(request, exception=None): + return render(request, "errors/400.html", {}) diff --git a/templates/sales/estimates/estimate_detail.html b/templates/sales/estimates/estimate_detail.html index 67ad84c2..79bcdbd6 100644 --- a/templates/sales/estimates/estimate_detail.html +++ b/templates/sales/estimates/estimate_detail.html @@ -141,6 +141,12 @@ {{vat_amount}} + + {% trans "Discount Amount" %} + + {{discount_amount}} + + {% trans "Grand Total" %} diff --git a/templates/sales/invoices/invoice_detail.html b/templates/sales/invoices/invoice_detail.html index 345a19c3..8d588856 100644 --- a/templates/sales/invoices/invoice_detail.html +++ b/templates/sales/invoices/invoice_detail.html @@ -144,13 +144,13 @@ - {% for item in invoice.get_itemtxs_data.0 %} + {% for item in invoice.ce_model.get_itemtxs_data.0 %} {{forloop.counter}} {{item.item_model.name}} - {{item.quantity}} - {{item.unit_cost}} - {{item.total_amount}} + {{item.ce_quantity}} + {{item.ce_unit_revenue_estimate}} + {{item.ce_revenue_estimate}} {% endfor %} @@ -159,6 +159,12 @@ {{vat_amount}} + + {% trans "Discount Amount" %} + + {{discount_amount}} + + {% trans "Grand Total" %}