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"))
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')

View File

@ -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)
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.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", {})

View File

@ -141,6 +141,12 @@
<span id="grand-total">{{vat_amount}}</span>
</td>
</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">
<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">

View File

@ -144,13 +144,13 @@
</tr>
</thead>
<tbody>
{% for item in invoice.get_itemtxs_data.0 %}
{% for item in invoice.ce_model.get_itemtxs_data.0 %}
<tr>
<td class="">{{forloop.counter}}</td>
<td class="">{{item.item_model.name}}</td>
<td class="align-middle">{{item.quantity}}</td>
<td class="align-middle ps-5">{{item.unit_cost}}</td>
<td class="align-middle text-body-tertiary fw-semibold">{{item.total_amount}}</td>
<td class="align-middle">{{item.ce_quantity}}</td>
<td class="align-middle ps-5">{{item.ce_unit_revenue_estimate}}</td>
<td class="align-middle text-body-tertiary fw-semibold">{{item.ce_revenue_estimate}}</td>
</tr>
{% endfor %}
<tr class="bg-body-secondary total-sum">
@ -159,6 +159,12 @@
<span id="grand-total">{{vat_amount}}</span>
</td>
</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">
<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">