This commit is contained in:
Marwan Alwali 2025-01-07 18:22:40 +03:00
commit 7fe829b362
5 changed files with 194 additions and 157 deletions

View File

@ -369,42 +369,34 @@ class CarFinance(models.Model):
selling_price = models.DecimalField(max_digits=14, decimal_places=2, verbose_name=_("Selling Price")) 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"), discount_amount = models.DecimalField(max_digits=14, decimal_places=2, verbose_name=_("Discount Amount"),
default=Decimal('0.00')) 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 @property
def total(self): def total(self):
total = 0 total = self.selling_price
if self.additional_services.count() != 0: if self.additional_services.count() != 0:
total_additional_services = sum(x.default_amount for x in self.additional_services.all()) total_additional_services = sum(x.default_amount for x in self.additional_services.all())
total = self.selling_price + total_additional_services total += total_additional_services
else:
total = self.selling_price
if self.discount_amount != 0:
total = total - self.discount_amount
return total 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 @property
def vat_amount(self): def vat_amount(self):
vat = VatRate.objects.filter(is_active=True).first() vat = VatRate.objects.filter(is_active=True).first()
return (self.total * vat.vat_rate).quantize(Decimal('0.01')) if vat:
return (self.total_discount * Decimal(vat.vat_rate)).quantize(Decimal('0.01'))
@property return Decimal('0.00')
def total_vat(self):
return self.total + self.vat_amount

View File

@ -1,3 +1,4 @@
from decimal import Decimal
from django.shortcuts import redirect from django.shortcuts import redirect
from django.contrib import messages from django.contrib import messages
from django.utils import timezone from django.utils import timezone
@ -91,3 +92,10 @@ def reserve_car(car,request):
messages.error(request, f"Error reserving car: {e}") messages.error(request, f"Error reserving car: {e}")
return redirect("car_detail", pk=car.pk) 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

View File

@ -59,7 +59,13 @@ from . import models, forms
from django.contrib.auth.mixins import PermissionRequiredMixin from django.contrib.auth.mixins import PermissionRequiredMixin
from django.contrib.messages.views import SuccessMessageMixin from django.contrib.messages.views import SuccessMessageMixin
from django.contrib.auth.models import Group 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 django.contrib.auth.models import User
from allauth.account import views from allauth.account import views
from django.db.models import Count, F, Value from django.db.models import Count, F, Value
@ -740,6 +746,14 @@ class CustomerDetailView(LoginRequiredMixin, DetailView):
template_name = "customers/view_customer.html" template_name = "customers/view_customer.html"
context_object_name = "customer" 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( class CustomerCreateView(
LoginRequiredMixin, LoginRequiredMixin,
@ -1620,6 +1634,7 @@ def bank_account_delete(request, pk):
# Accounts # Accounts
class AccountListView(LoginRequiredMixin, ListView): class AccountListView(LoginRequiredMixin, ListView):
model = AccountModel model = AccountModel
template_name = "ledger/coa_accounts/account_list.html" template_name = "ledger/coa_accounts/account_list.html"
@ -1785,7 +1800,7 @@ def create_estimate(request):
"quantity": float(item.get("quantity")), "quantity": float(item.get("quantity")),
"unit_cost": car_instance.finances.cost_price, "unit_cost": car_instance.finances.cost_price,
"unit_revenue": car_instance.finances.selling_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")), * int(item.get("quantity")),
} }
) )
@ -1807,7 +1822,8 @@ def create_estimate(request):
"unit_cost": instance.finances.cost_price, "unit_cost": instance.finances.cost_price,
"unit_revenue": instance.finances.selling_price, "unit_revenue": instance.finances.selling_price,
"quantity": float(quantities), "quantity": float(quantities),
"total_amount": instance.finances.total * int(quantities), "total_amount": instance.finances.total_vat
* int(quantities),
} }
} }
@ -1821,7 +1837,7 @@ def create_estimate(request):
for item in items: for item in items:
item_instance = ItemModel.objects.get(pk=item) item_instance = ItemModel.objects.get(pk=item)
instance = models.Car.objects.get(vin=item_instance.name) instance = models.Car.objects.get(vin=item_instance.name)
response = reserve_car(instance, request) reserve_car(instance, request)
else: else:
item_instance = ItemModel.objects.get(pk=items) item_instance = ItemModel.objects.get(pk=items)
@ -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) form = EstimateModelCreateForm(entity_slug=entity.slug, user_model=entity.admin)
car_list = models.Car.objects.filter( car_list = models.Car.objects.filter(
dealer=dealer, finances__selling_price__gt=0 dealer=dealer, finances__selling_price__gt=0
@ -1874,25 +1881,27 @@ class EstimateDetailView(LoginRequiredMixin, DetailView):
estimate = kwargs.get("object") estimate = kwargs.get("object")
if estimate.get_itemtxs_data(): if estimate.get_itemtxs_data():
total = sum( total = sum(
( float(
x.ce_revenue_estimate models.Car.objects.get(
- models.Car.objects.get(
vin=x.item_model.name vin=x.item_model.name
).finances.discount_amount ).finances.total
) )
* float(x.ce_quantity)
for x in estimate.get_itemtxs_data()[0].all() 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() vat = models.VatRate.objects.filter(is_active=True).first()
grand_total = float(total) - float(discount_amount)
vat_amount = round(float(grand_total) * float(vat.vat_rate), 2)
# 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
kwargs["vat_amount"] = vat_amount 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["vat"] = vat.rate
kwargs["invoice"] = ( kwargs["invoice"] = (
InvoiceModel.objects.all().filter(ce_model=estimate).first() InvoiceModel.objects.all().filter(ce_model=estimate).first()
@ -1949,7 +1958,6 @@ def estimate_mark_as(request, pk):
messages.error(request, "Estimate is not ready for approval") messages.error(request, "Estimate is not ready for approval")
return redirect("estimate_detail", pk=estimate.pk) return redirect("estimate_detail", pk=estimate.pk)
estimate.mark_as_approved() estimate.mark_as_approved()
messages.success(request, "Estimate approved successfully.") messages.success(request, "Estimate approved successfully.")
elif mark == "rejected": elif mark == "rejected":
if not estimate.can_cancel(): if not estimate.can_cancel():
@ -1957,36 +1965,10 @@ def estimate_mark_as(request, pk):
return redirect("estimate_detail", pk=estimate.pk) return redirect("estimate_detail", pk=estimate.pk)
estimate.mark_as_canceled() estimate.mark_as_canceled()
messages.success(request, "Estimate canceled successfully.") messages.success(request, "Estimate canceled successfully.")
elif mark == "completed": elif mark == "completed":
if not estimate.can_complete(): if not estimate.can_complete():
messages.error(request, "Estimate is not ready for completion") messages.error(request, "Estimate is not ready for completion")
return redirect("estimate_detail", pk=estimate.pk) 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() estimate.save()
messages.success(request, "Estimate marked as " + mark.upper()) messages.success(request, "Estimate marked as " + mark.upper())
@ -2012,15 +1994,27 @@ class InvoiceDetailView(LoginRequiredMixin, DetailView):
def get_context_data(self, **kwargs): def get_context_data(self, **kwargs):
invoice = kwargs.get("object") invoice = kwargs.get("object")
vat = models.VatRate.objects.filter(is_active=True).first()
if invoice.get_itemtxs_data(): if invoice.get_itemtxs_data():
total = sum( 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) discount_amount = sum(
vat = models.VatRate.objects.filter(is_active=True).first() models.CarFinance.objects.get(
kwargs["vat_amount"] = total * vat.vat_rate car__vin=i.item_model.name
kwargs["total"] = (total * vat.vat_rate) + total ).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["vat"] = vat.rate
kwargs["payments"] = JournalEntryModel.objects.filter( kwargs["payments"] = JournalEntryModel.objects.filter(
ledger=invoice.ledger ledger=invoice.ledger
@ -2080,13 +2074,27 @@ def invoice_create(request, pk):
invoice_model.save() invoice_model.save()
unit_items = estimate.get_itemtxs_data()[0] 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 = { invoice_itemtxs = {
i.item_model.item_number: { i.get("item_number"): {
"unit_cost": i.ce_unit_cost_estimate, "unit_cost": i.get("unit_cost"),
"quantity": i.ce_quantity, "quantity": i.get("quantity"),
"total_amount": i.ce_cost_estimate, "total_amount": i.get("total_amount"),
} }
for i in unit_items for i in itemtxs
} }
invoice_itemtxs = invoice_model.migrate_itemtxs( invoice_itemtxs = invoice_model.migrate_itemtxs(
@ -2232,59 +2240,6 @@ class UserActivityLogListView(ListView):
return queryset 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 # CRM RELATED VIEWS
def create_lead(request, pk): def create_lead(request, pk):
customer = get_object_or_404(models.Customer, pk=pk) customer = get_object_or_404(models.Customer, pk=pk)
@ -2459,3 +2414,73 @@ class SubscriptionPlans(ListView):
model = models.SubscriptionPlan model = models.SubscriptionPlan
template_name = "subscriptions/subscription_plan.html" template_name = "subscriptions/subscription_plan.html"
context_object_name = "plans" 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", {})

View File

@ -141,6 +141,12 @@
<span id="grand-total">{{vat_amount}}</span> <span id="grand-total">{{vat_amount}}</span>
</td> </td>
</tr> </tr>
<tr class="bg-body-secondary total-sum">
<td class="align-middle ps-4 fw-bold text-body-highlight" colspan="4">{% trans "Discount Amount" %}</td>
<td class="align-middle text-start fw-bold">
<span id="grand-total">{{discount_amount}}</span>
</td>
</tr>
<tr class="bg-body-secondary total-sum"> <tr class="bg-body-secondary total-sum">
<td class="align-middle ps-4 fw-bold text-body-highlight" colspan="4">{% trans "Grand Total" %}</td> <td class="align-middle ps-4 fw-bold text-body-highlight" colspan="4">{% trans "Grand Total" %}</td>
<td class="align-middle text-start fw-bold"> <td class="align-middle text-start fw-bold">

View File

@ -144,13 +144,13 @@
</tr> </tr>
</thead> </thead>
<tbody> <tbody>
{% for item in invoice.get_itemtxs_data.0 %} {% for item in invoice.ce_model.get_itemtxs_data.0 %}
<tr> <tr>
<td class="">{{forloop.counter}}</td> <td class="">{{forloop.counter}}</td>
<td class="">{{item.item_model.name}}</td> <td class="">{{item.item_model.name}}</td>
<td class="align-middle">{{item.quantity}}</td> <td class="align-middle">{{item.ce_quantity}}</td>
<td class="align-middle ps-5">{{item.unit_cost}}</td> <td class="align-middle ps-5">{{item.ce_unit_revenue_estimate}}</td>
<td class="align-middle text-body-tertiary fw-semibold">{{item.total_amount}}</td> <td class="align-middle text-body-tertiary fw-semibold">{{item.ce_revenue_estimate}}</td>
</tr> </tr>
{% endfor %} {% endfor %}
<tr class="bg-body-secondary total-sum"> <tr class="bg-body-secondary total-sum">
@ -159,6 +159,12 @@
<span id="grand-total">{{vat_amount}}</span> <span id="grand-total">{{vat_amount}}</span>
</td> </td>
</tr> </tr>
<tr class="bg-body-secondary total-sum">
<td class="align-middle ps-4 fw-bold text-body-highlight" colspan="4">{% trans "Discount Amount" %}</td>
<td class="align-middle text-start fw-bold">
<span id="grand-total">{{discount_amount}}</span>
</td>
</tr>
<tr class="bg-body-secondary total-sum"> <tr class="bg-body-secondary total-sum">
<td class="align-middle ps-4 fw-bold text-body-highlight" colspan="4">{% trans "Grand Total" %}</td> <td class="align-middle ps-4 fw-bold text-body-highlight" colspan="4">{% trans "Grand Total" %}</td>
<td class="align-middle text-start fw-bold"> <td class="align-middle text-start fw-bold">