some changes in invoice
This commit is contained in:
parent
da1b53c775
commit
a03bb8ccf1
@ -801,6 +801,7 @@ class Customer(models.Model):
|
|||||||
middle = f" {self.middle_name}" if self.middle_name else ""
|
middle = f" {self.middle_name}" if self.middle_name else ""
|
||||||
return f"{self.first_name}{middle} {self.last_name}"
|
return f"{self.first_name}{middle} {self.last_name}"
|
||||||
|
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def get_full_name(self):
|
def get_full_name(self):
|
||||||
return f"{self.first_name} {self.middle_name} {self.last_name}"
|
return f"{self.first_name} {self.middle_name} {self.last_name}"
|
||||||
|
|||||||
@ -218,7 +218,7 @@ def create_ledger_entity(sender, instance, created, **kwargs):
|
|||||||
active=True,
|
active=True,
|
||||||
)
|
)
|
||||||
#Create Deferred Revenue Account
|
#Create Deferred Revenue Account
|
||||||
entity.create_account(
|
deferred_revenue = entity.create_account(
|
||||||
coa_model=coa,
|
coa_model=coa,
|
||||||
code="2060",
|
code="2060",
|
||||||
role=roles.LIABILITY_CL_DEFERRED_REVENUE,
|
role=roles.LIABILITY_CL_DEFERRED_REVENUE,
|
||||||
@ -226,6 +226,9 @@ def create_ledger_entity(sender, instance, created, **kwargs):
|
|||||||
balance_type="credit",
|
balance_type="credit",
|
||||||
active=True,
|
active=True,
|
||||||
)
|
)
|
||||||
|
deferred_revenue.role_default = True
|
||||||
|
deferred_revenue.save()
|
||||||
|
|
||||||
|
|
||||||
# Create Vendor
|
# Create Vendor
|
||||||
@receiver(post_save, sender=models.Vendor)
|
@receiver(post_save, sender=models.Vendor)
|
||||||
|
|||||||
@ -91,7 +91,7 @@ urlpatterns = [
|
|||||||
path('generate_invoice/<int:pk>/', views.generate_invoice, name='generate_invoice'),
|
path('generate_invoice/<int:pk>/', views.generate_invoice, name='generate_invoice'),
|
||||||
path('sales/quotations/<int:pk>/mark_quotation/', views.mark_quotation, name='mark_quotation'),
|
path('sales/quotations/<int:pk>/mark_quotation/', views.mark_quotation, name='mark_quotation'),
|
||||||
path('sales/quotations/<int:pk>/post_quotation/', views.post_quotation, name='post_quotation'),
|
path('sales/quotations/<int:pk>/post_quotation/', views.post_quotation, name='post_quotation'),
|
||||||
path('sales/quotations/<int:pk>/invoice_detail/', views.invoice_detail, name='invoice_detail'),
|
# path('sales/quotations/<int:pk>/invoice_detail/', views.invoice_detail, name='invoice_detail'),
|
||||||
path('subscriptions', views.SubscriptionPlans.as_view(), name='subscriptions'),
|
path('subscriptions', views.SubscriptionPlans.as_view(), name='subscriptions'),
|
||||||
#Payment URLs
|
#Payment URLs
|
||||||
# path('sales/quotations/<int:pk>/payment/', views.PaymentCreateView.as_view(), name='payment_create'),
|
# path('sales/quotations/<int:pk>/payment/', views.PaymentCreateView.as_view(), name='payment_create'),
|
||||||
@ -140,6 +140,7 @@ urlpatterns = [
|
|||||||
path('sales/estimates/<uuid:pk>/send_email', views.send_email_view, name='send_email'),
|
path('sales/estimates/<uuid:pk>/send_email', views.send_email_view, name='send_email'),
|
||||||
# Invoice
|
# Invoice
|
||||||
path('sales/invoices/', views.InvoiceListView.as_view(), name='invoice_list'),
|
path('sales/invoices/', views.InvoiceListView.as_view(), name='invoice_list'),
|
||||||
|
path('sales/invoices/create/', views.invoice_create, name='invoice_create'),
|
||||||
path('sales/invoices/<uuid:pk>/create/', views.invoice_create, name='invoice_create'),
|
path('sales/invoices/<uuid:pk>/create/', views.invoice_create, name='invoice_create'),
|
||||||
path('sales/invoices/<uuid:pk>/', views.InvoiceDetailView.as_view(), name='invoice_detail'),
|
path('sales/invoices/<uuid:pk>/', views.InvoiceDetailView.as_view(), name='invoice_detail'),
|
||||||
path('sales/invoices/<uuid:pk>/preview/', views.InvoicePreviewView.as_view(), name='invoice_preview'),
|
path('sales/invoices/<uuid:pk>/preview/', views.InvoicePreviewView.as_view(), name='invoice_preview'),
|
||||||
|
|||||||
@ -749,7 +749,12 @@ class CustomerDetailView(LoginRequiredMixin, DetailView):
|
|||||||
model = models.Customer
|
model = models.Customer
|
||||||
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,
|
||||||
@ -1274,23 +1279,6 @@ def UserDeleteview(request, pk):
|
|||||||
return redirect("user_list")
|
return redirect("user_list")
|
||||||
|
|
||||||
|
|
||||||
# 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", {})
|
|
||||||
|
|
||||||
|
|
||||||
class OrganizationListView(LoginRequiredMixin, ListView):
|
class OrganizationListView(LoginRequiredMixin, ListView):
|
||||||
model = models.Organization
|
model = models.Organization
|
||||||
template_name = "organizations/organization_list.html"
|
template_name = "organizations/organization_list.html"
|
||||||
@ -1378,31 +1366,6 @@ class RepresentativeDeleteView(LoginRequiredMixin, SuccessMessageMixin, DeleteVi
|
|||||||
success_message = "Representative deleted successfully."
|
success_message = "Representative deleted successfully."
|
||||||
|
|
||||||
|
|
||||||
# def quotation_pdf_view(request, pk):
|
|
||||||
# # Get the quotation object
|
|
||||||
# quotation = models.SaleQuotation.objects.get(pk=pk)
|
|
||||||
#
|
|
||||||
# # Render the HTML template for the quotation page
|
|
||||||
# context = {
|
|
||||||
# "quotation": quotation,
|
|
||||||
# }
|
|
||||||
# context_result = get_calculations(quotation)
|
|
||||||
# context = context.update(context_result)
|
|
||||||
#
|
|
||||||
# html_content = render_to_string("sales/quotation_pdf.html", context)
|
|
||||||
#
|
|
||||||
# # Create a PDF file
|
|
||||||
#
|
|
||||||
# pdf_file = HTML(string=html_content).render()
|
|
||||||
#
|
|
||||||
# # Save the PDF file to a file
|
|
||||||
# with open("quotation.pdf", "wb") as f:
|
|
||||||
# f.write(pdf_file.write_pdf())
|
|
||||||
#
|
|
||||||
# # Return the PDF file as a response
|
|
||||||
# return HttpResponse(pdf_file, content_type="application/pdf")
|
|
||||||
|
|
||||||
|
|
||||||
@login_required
|
@login_required
|
||||||
def download_quotation_pdf(request, quotation_id):
|
def download_quotation_pdf(request, quotation_id):
|
||||||
try:
|
try:
|
||||||
@ -1427,59 +1390,40 @@ def download_quotation_pdf(request, quotation_id):
|
|||||||
return HttpResponse("Quotation not found", status=404)
|
return HttpResponse("Quotation not found", status=404)
|
||||||
|
|
||||||
|
|
||||||
@login_required
|
# @login_required
|
||||||
def invoice_detail(request, pk):
|
# def invoice_detail(request, pk):
|
||||||
quotation = get_object_or_404(models.SaleQuotation, pk=pk)
|
# quotation = get_object_or_404(models.SaleQuotation, pk=pk)
|
||||||
dealer = request.user.dealer
|
# dealer = request.user.dealer
|
||||||
entity = dealer.entity
|
# entity = dealer.entity
|
||||||
customer = (
|
# customer = (
|
||||||
entity.get_customers()
|
# entity.get_customers()
|
||||||
.filter(customer_name=quotation.customer.get_full_name)
|
# .filter(customer_name=quotation.customer.get_full_name)
|
||||||
.first()
|
# .first()
|
||||||
)
|
# )
|
||||||
invoice_model = entity.get_invoices()
|
# invoice_model = entity.get_invoices()
|
||||||
|
|
||||||
invoice = invoice_model.filter(
|
# invoice = invoice_model.filter(
|
||||||
customer=customer, date_draft=quotation.date_draft
|
# customer=customer, date_draft=quotation.date_draft
|
||||||
).first()
|
# ).first()
|
||||||
return redirect("quotation_detail", pk=pk)
|
# return redirect("quotation_detail", pk=pk)
|
||||||
|
|
||||||
|
|
||||||
@login_required
|
# @login_required
|
||||||
def payment_invoice(request, pk):
|
# def payment_invoice(request, pk):
|
||||||
quotation = get_object_or_404(models.SaleQuotation, pk=pk)
|
# quotation = get_object_or_404(models.SaleQuotation, pk=pk)
|
||||||
dealer = request.user.dealer
|
# dealer = request.user.dealer
|
||||||
entity = dealer.entity
|
# entity = dealer.entity
|
||||||
customer = (
|
# customer = (
|
||||||
entity.get_customers()
|
# entity.get_customers()
|
||||||
.filter(customer_name=quotation.customer.get_full_name)
|
# .filter(customer_name=quotation.customer.get_full_name)
|
||||||
.first()
|
# .first()
|
||||||
)
|
# )
|
||||||
invoice_model = entity.get_invoices()
|
# invoice_model = entity.get_invoices()
|
||||||
invoice = invoice_model.filter(
|
# invoice = invoice_model.filter(
|
||||||
customer=customer, date_draft=quotation.date_draft
|
# customer=customer, date_draft=quotation.date_draft
|
||||||
).first()
|
# ).first()
|
||||||
|
|
||||||
return redirect("quotation_detail", pk=pk)
|
|
||||||
|
|
||||||
|
|
||||||
# class PaymentCreateView(LoginRequiredMixin, SuccessMessageMixin, CreateView):
|
|
||||||
# model = models.Payment
|
|
||||||
# form_class = forms.PaymentForm
|
|
||||||
# template_name = "sales/payments/payment_form.html"
|
|
||||||
# success_url = reverse_lazy("quotation_list")
|
|
||||||
# success_message = "Payment created successfully."
|
|
||||||
|
|
||||||
# def form_valid(self, form):
|
|
||||||
# quotation = get_object_or_404(models.SaleQuotation, pk=self.kwargs["pk"])
|
|
||||||
# form.instance.quotation = quotation
|
|
||||||
# form.save()
|
|
||||||
# return super().form_valid(form)
|
|
||||||
# def get_context_data(self, **kwargs):
|
|
||||||
# context = super().get_context_data(**kwargs)
|
|
||||||
# context["quotation"] = get_object_or_404(models.SaleQuotation, pk=self.kwargs["pk"])
|
|
||||||
# return context
|
|
||||||
|
|
||||||
|
# return redirect("quotation_detail", pk=pk)
|
||||||
|
|
||||||
def payment_create(request, pk):
|
def payment_create(request, pk):
|
||||||
quotation = get_object_or_404(models.SaleQuotation, pk=pk)
|
quotation = get_object_or_404(models.SaleQuotation, pk=pk)
|
||||||
@ -1629,7 +1573,6 @@ 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"
|
||||||
@ -1705,36 +1648,6 @@ class EstimateListView(LoginRequiredMixin, ListView):
|
|||||||
return entity.get_estimates()
|
return entity.get_estimates()
|
||||||
|
|
||||||
|
|
||||||
# class EstimateCreateView(LoginRequiredMixin, SuccessMessageMixin, CreateView):
|
|
||||||
# model = EstimateModel
|
|
||||||
# form_class = EstimateModelCreateForm
|
|
||||||
# template_name = "sales/estimates/estimate_form.html"
|
|
||||||
# success_url = reverse_lazy("estimate_list")
|
|
||||||
# success_message = "Estimate created successfully."
|
|
||||||
|
|
||||||
# def get_form_kwargs(self):
|
|
||||||
# """
|
|
||||||
# Override this method to pass additional keyword arguments to the form.
|
|
||||||
# """
|
|
||||||
# entity = self.request.user.dealer.entity
|
|
||||||
# kwargs = super().get_form_kwargs()
|
|
||||||
# kwargs['entity_slug'] = entity.slug
|
|
||||||
# kwargs['user_model'] = entity.admin
|
|
||||||
# return kwargs
|
|
||||||
|
|
||||||
# def get_context_data(self, **kwargs):
|
|
||||||
# entity = self.request.user.dealer.entity
|
|
||||||
# kwargs['items'] = entity.get_items_all()
|
|
||||||
# return super().get_context_data(**kwargs)
|
|
||||||
# def get_customer_queryset(self):
|
|
||||||
# entity = self.request.user.dealer.entity
|
|
||||||
# return entity.get_customer_queryset()
|
|
||||||
|
|
||||||
# def form_valid(self, form):
|
|
||||||
# form.instance.entity = self.request.user.dealer.entity
|
|
||||||
# return super().form_valid(form)
|
|
||||||
|
|
||||||
|
|
||||||
# @csrf_exempt
|
# @csrf_exempt
|
||||||
@login_required
|
@login_required
|
||||||
def create_estimate(request):
|
def create_estimate(request):
|
||||||
@ -1831,12 +1744,12 @@ 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)
|
||||||
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)
|
||||||
|
|
||||||
url = reverse("estimate_detail", kwargs={"pk": estimate.pk})
|
url = reverse("estimate_detail", kwargs={"pk": estimate.pk})
|
||||||
return JsonResponse(
|
return JsonResponse(
|
||||||
@ -1846,16 +1759,7 @@ def create_estimate(request):
|
|||||||
"url": f"{url}",
|
"url": f"{url}",
|
||||||
}
|
}
|
||||||
)
|
)
|
||||||
|
|
||||||
# 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
|
||||||
@ -1894,13 +1798,10 @@ class EstimateDetailView(LoginRequiredMixin, DetailView):
|
|||||||
)
|
)
|
||||||
vat = models.VatRate.objects.filter(is_active=True).first()
|
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)
|
||||||
vat_amount = round(total * vat.vat_rate, 2) # Round to 2 decimal places
|
|
||||||
grand_total = round(
|
grand_total = round(
|
||||||
(total * vat.vat_rate) + total, 2
|
(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
|
||||||
kwargs["vat"] = vat.rate
|
kwargs["vat"] = vat.rate
|
||||||
@ -1945,7 +1846,6 @@ class EstimatePreviewView(LoginRequiredMixin, DetailView):
|
|||||||
@login_required
|
@login_required
|
||||||
def estimate_mark_as(request, pk):
|
def estimate_mark_as(request, pk):
|
||||||
estimate = get_object_or_404(EstimateModel, pk=pk)
|
estimate = get_object_or_404(EstimateModel, pk=pk)
|
||||||
entity = estimate.entity
|
|
||||||
mark = request.GET.get("mark")
|
mark = request.GET.get("mark")
|
||||||
if mark:
|
if mark:
|
||||||
if mark == "review":
|
if mark == "review":
|
||||||
@ -1953,13 +1853,11 @@ def estimate_mark_as(request, pk):
|
|||||||
messages.error(request, "Estimate is not ready for 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()
|
estimate.mark_as_review()
|
||||||
|
|
||||||
elif mark == "approved":
|
elif mark == "approved":
|
||||||
if not estimate.can_approve():
|
if not estimate.can_approve():
|
||||||
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():
|
||||||
@ -1967,36 +1865,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())
|
||||||
|
|
||||||
@ -2032,6 +1904,7 @@ class InvoiceDetailView(LoginRequiredMixin, DetailView):
|
|||||||
kwargs["vat_amount"] = total * vat.vat_rate
|
kwargs["vat_amount"] = total * vat.vat_rate
|
||||||
kwargs["total"] = (total * vat.vat_rate) + total
|
kwargs["total"] = (total * vat.vat_rate) + total
|
||||||
kwargs["vat"] = vat.rate
|
kwargs["vat"] = vat.rate
|
||||||
|
kwargs["amount_left"] = invoice.amount_due - invoice.amount_paid
|
||||||
kwargs["payments"] = JournalEntryModel.objects.filter(
|
kwargs["payments"] = JournalEntryModel.objects.filter(
|
||||||
ledger=invoice.ledger
|
ledger=invoice.ledger
|
||||||
).all()
|
).all()
|
||||||
@ -2067,7 +1940,7 @@ def invoice_mark_as(request, pk):
|
|||||||
|
|
||||||
def invoice_create(request, pk):
|
def invoice_create(request, pk):
|
||||||
estimate = get_object_or_404(EstimateModel, pk=pk)
|
estimate = get_object_or_404(EstimateModel, pk=pk)
|
||||||
entity = request.user.dealer.entity
|
entity = request.entity
|
||||||
|
|
||||||
form = InvoiceModelCreateForm(entity_slug=entity.slug, user_model=entity.admin)
|
form = InvoiceModelCreateForm(entity_slug=entity.slug, user_model=entity.admin)
|
||||||
if request.method == "POST":
|
if request.method == "POST":
|
||||||
@ -2081,6 +1954,7 @@ def invoice_create(request, pk):
|
|||||||
terms=invoice.terms,
|
terms=invoice.terms,
|
||||||
cash_account=invoice.cash_account,
|
cash_account=invoice.cash_account,
|
||||||
prepaid_account=invoice.prepaid_account,
|
prepaid_account=invoice.prepaid_account,
|
||||||
|
payable_account=invoice.unearned_account,
|
||||||
coa_model=entity.get_default_coa(),
|
coa_model=entity.get_default_coa(),
|
||||||
)
|
)
|
||||||
ledger = entity.create_ledger(name=f"Invoice {str(invoice_model.pk)}")
|
ledger = entity.create_ledger(name=f"Invoice {str(invoice_model.pk)}")
|
||||||
@ -2242,58 +2116,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):
|
||||||
@ -2467,3 +2289,72 @@ 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", {})
|
||||||
|
|||||||
@ -27,7 +27,7 @@
|
|||||||
</button>
|
</button>
|
||||||
<a type="button"
|
<a type="button"
|
||||||
class="btn btn-sm btn-danger"
|
class="btn btn-sm btn-danger"
|
||||||
href="{% url 'customer_delete' customer.id %}">
|
href="{% url 'customer_delete' customer.pk %}">
|
||||||
{% trans 'Yes' %}
|
{% trans 'Yes' %}
|
||||||
</a>
|
</a>
|
||||||
</div>
|
</div>
|
||||||
@ -37,8 +37,7 @@
|
|||||||
<!-- Delete Modal -->
|
<!-- Delete Modal -->
|
||||||
|
|
||||||
<div class="container">
|
<div class="container">
|
||||||
|
<div class="mb-9">
|
||||||
<div class="mb-9">
|
|
||||||
<div class="row align-items-center justify-content-between g-3 mb-4">
|
<div class="row align-items-center justify-content-between g-3 mb-4">
|
||||||
<div class="col-auto">
|
<div class="col-auto">
|
||||||
<h3 class="mb-0">{% trans 'Customer details' %}</h3>
|
<h3 class="mb-0">{% trans 'Customer details' %}</h3>
|
||||||
@ -119,7 +118,7 @@
|
|||||||
</div>
|
</div>
|
||||||
<div class="col-12 col-xxl-8">
|
<div class="col-12 col-xxl-8">
|
||||||
<div class="mb-6">
|
<div class="mb-6">
|
||||||
<h3 class="mb-4">{{ _("Cars") }} <span class="text-body-tertiary fw-normal">(4)</span></h3>
|
<h3 class="mb-4">{{ _("Cars") }} <span class="text-body-tertiary fw-normal">({{ estimates.count }})</span></h3>
|
||||||
<div class="border-top border-bottom border-translucent" id="customerOrdersTable" data-list='{"valueNames":["order","total","payment_status","fulfilment_status","delivery_type","date"],"page":6,"pagination":true}'>
|
<div class="border-top border-bottom border-translucent" id="customerOrdersTable" data-list='{"valueNames":["order","total","payment_status","fulfilment_status","delivery_type","date"],"page":6,"pagination":true}'>
|
||||||
<div class="table-responsive scrollbar">
|
<div class="table-responsive scrollbar">
|
||||||
<table class="table table-sm fs-9 mb-0">
|
<table class="table table-sm fs-9 mb-0">
|
||||||
@ -135,10 +134,21 @@
|
|||||||
</tr>
|
</tr>
|
||||||
</thead>
|
</thead>
|
||||||
<tbody class="list" id="customer-order-table-body">
|
<tbody class="list" id="customer-order-table-body">
|
||||||
|
{% for estimate in estimates %}
|
||||||
<tr class="hover-actions-trigger btn-reveal-trigger position-static">
|
<tr class="hover-actions-trigger btn-reveal-trigger position-static">
|
||||||
<td class="order align-middle white-space-nowrap ps-0"><a class="fw-semibold" href="#!">#2453</a></td>
|
<td class="order align-middle white-space-nowrap ps-0"><a class="fw-semibold" href="{% url 'estimate_detail' estimate.pk %}">#{{ estimate.estimate_number }}</a></td>
|
||||||
<td class="total align-middle text-end fw-semibold pe-7 text-body-highlight">$87</td>
|
<td class="total align-middle text-end fw-semibold pe-7 text-body-highlight">$87</td>
|
||||||
<td class="payment_status align-middle white-space-nowrap text-start fw-bold text-body-tertiary"><span class="badge badge-phoenix fs-10 badge-phoenix-success"><span class="badge-label">Paid</span><span class="ms-1" data-feather="check" style="height:12.8px;width:12.8px;"></span></span></td>
|
<td class="payment_status align-middle white-space-nowrap text-start fw-bold text-body-tertiary">
|
||||||
|
{% if estimate.is_draft %}
|
||||||
|
<span class="badge badge-phoenix badge-phoenix-warning text-uppercase">{% trans 'Draft' %}</span>
|
||||||
|
{% elif estimate.is_review %}
|
||||||
|
<span class="badge badge-phoenix badge-phoenix-info text-uppercase">{% trans 'Review' %}</span>
|
||||||
|
{% elif estimate.is_approved %}
|
||||||
|
<span class="badge badge-phoenix badge-phoenix-success text-uppercase">{% trans 'Approved' %}</span>
|
||||||
|
{% else %}
|
||||||
|
<span class="badge badge-phoenix badge-phoenix-success text-uppercase">{% trans 'Paid' %}</span>
|
||||||
|
{% endif %}
|
||||||
|
</td>
|
||||||
<td class="fulfilment_status align-middle white-space-nowrap text-start fw-bold text-body-tertiary"><span class="badge badge-phoenix fs-10 badge-phoenix-success"><span class="badge-label">Order Fulfilled</span><span class="ms-1" data-feather="check" style="height:12.8px;width:12.8px;"></span></span></td>
|
<td class="fulfilment_status align-middle white-space-nowrap text-start fw-bold text-body-tertiary"><span class="badge badge-phoenix fs-10 badge-phoenix-success"><span class="badge-label">Order Fulfilled</span><span class="ms-1" data-feather="check" style="height:12.8px;width:12.8px;"></span></span></td>
|
||||||
<td class="delivery_type align-middle white-space-nowrap text-body fs-9 text-start">Cash on delivery</td>
|
<td class="delivery_type align-middle white-space-nowrap text-body fs-9 text-start">Cash on delivery</td>
|
||||||
<td class="date align-middle white-space-nowrap text-body-tertiary fs-9 ps-4 text-end">Dec 12, 12:56 PM</td>
|
<td class="date align-middle white-space-nowrap text-body-tertiary fs-9 ps-4 text-end">Dec 12, 12:56 PM</td>
|
||||||
@ -151,55 +161,7 @@
|
|||||||
</div>
|
</div>
|
||||||
</td>
|
</td>
|
||||||
</tr>
|
</tr>
|
||||||
<tr class="hover-actions-trigger btn-reveal-trigger position-static">
|
{% endfor %}
|
||||||
<td class="order align-middle white-space-nowrap ps-0"><a class="fw-semibold" href="#!">#2452</a></td>
|
|
||||||
<td class="total align-middle text-end fw-semibold pe-7 text-body-highlight">$7264</td>
|
|
||||||
<td class="payment_status align-middle white-space-nowrap text-start fw-bold text-body-tertiary"><span class="badge badge-phoenix fs-10 badge-phoenix-secondary"><span class="badge-label">Cancelled</span><span class="ms-1" data-feather="x" style="height:12.8px;width:12.8px;"></span></span></td>
|
|
||||||
<td class="fulfilment_status align-middle white-space-nowrap text-start fw-bold text-body-tertiary"><span class="badge badge-phoenix fs-10 badge-phoenix-info"><span class="badge-label">Ready to pickup</span><span class="ms-1" data-feather="info" style="height:12.8px;width:12.8px;"></span></span></td>
|
|
||||||
<td class="delivery_type align-middle white-space-nowrap text-body fs-9 text-start">Free shipping</td>
|
|
||||||
<td class="date align-middle white-space-nowrap text-body-tertiary fs-9 ps-4 text-end">Dec 9, 2:28PM</td>
|
|
||||||
<td class="align-middle white-space-nowrap text-end pe-0 ps-5">
|
|
||||||
<div class="btn-reveal-trigger position-static">
|
|
||||||
<button class="btn btn-sm dropdown-toggle dropdown-caret-none transition-none btn-reveal fs-10" type="button" data-bs-toggle="dropdown" data-boundary="window" aria-haspopup="true" aria-expanded="false" data-bs-reference="parent"><span class="fas fa-ellipsis-h fs-10"></span></button>
|
|
||||||
<div class="dropdown-menu dropdown-menu-end py-2"><a class="dropdown-item" href="#!">View</a><a class="dropdown-item" href="#!">Export</a>
|
|
||||||
<div class="dropdown-divider"></div><a class="dropdown-item text-danger" href="#!">Remove</a>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</td>
|
|
||||||
</tr>
|
|
||||||
<tr class="hover-actions-trigger btn-reveal-trigger position-static">
|
|
||||||
<td class="order align-middle white-space-nowrap ps-0"><a class="fw-semibold" href="#!">#2451</a></td>
|
|
||||||
<td class="total align-middle text-end fw-semibold pe-7 text-body-highlight">$375</td>
|
|
||||||
<td class="payment_status align-middle white-space-nowrap text-start fw-bold text-body-tertiary"><span class="badge badge-phoenix fs-10 badge-phoenix-warning"><span class="badge-label">Pending</span><span class="ms-1" data-feather="alert-octagon" style="height:12.8px;width:12.8px;"></span></span></td>
|
|
||||||
<td class="fulfilment_status align-middle white-space-nowrap text-start fw-bold text-body-tertiary"><span class="badge badge-phoenix fs-10 badge-phoenix-warning"><span class="badge-label">Partial FulfiLled</span><span class="ms-1" data-feather="alert-octagon" style="height:12.8px;width:12.8px;"></span></span></td>
|
|
||||||
<td class="delivery_type align-middle white-space-nowrap text-body fs-9 text-start">Local pickup</td>
|
|
||||||
<td class="date align-middle white-space-nowrap text-body-tertiary fs-9 ps-4 text-end">Dec 4, 12:56 PM</td>
|
|
||||||
<td class="align-middle white-space-nowrap text-end pe-0 ps-5">
|
|
||||||
<div class="btn-reveal-trigger position-static">
|
|
||||||
<button class="btn btn-sm dropdown-toggle dropdown-caret-none transition-none btn-reveal fs-10" type="button" data-bs-toggle="dropdown" data-boundary="window" aria-haspopup="true" aria-expanded="false" data-bs-reference="parent"><span class="fas fa-ellipsis-h fs-10"></span></button>
|
|
||||||
<div class="dropdown-menu dropdown-menu-end py-2"><a class="dropdown-item" href="#!">View</a><a class="dropdown-item" href="#!">Export</a>
|
|
||||||
<div class="dropdown-divider"></div><a class="dropdown-item text-danger" href="#!">Remove</a>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</td>
|
|
||||||
</tr>
|
|
||||||
<tr class="hover-actions-trigger btn-reveal-trigger position-static">
|
|
||||||
<td class="order align-middle white-space-nowrap ps-0"><a class="fw-semibold" href="#!">#2450</a></td>
|
|
||||||
<td class="total align-middle text-end fw-semibold pe-7 text-body-highlight">$657</td>
|
|
||||||
<td class="payment_status align-middle white-space-nowrap text-start fw-bold text-body-tertiary"><span class="badge badge-phoenix fs-10 badge-phoenix-secondary"><span class="badge-label">Cancelled</span><span class="ms-1" data-feather="x" style="height:12.8px;width:12.8px;"></span></span></td>
|
|
||||||
<td class="fulfilment_status align-middle white-space-nowrap text-start fw-bold text-body-tertiary"><span class="badge badge-phoenix fs-10 badge-phoenix-secondary"><span class="badge-label">Order CancelLed</span><span class="ms-1" data-feather="x" style="height:12.8px;width:12.8px;"></span></span></td>
|
|
||||||
<td class="delivery_type align-middle white-space-nowrap text-body fs-9 text-start">Standard shipping</td>
|
|
||||||
<td class="date align-middle white-space-nowrap text-body-tertiary fs-9 ps-4 text-end">Dec 1, 4:07 AM</td>
|
|
||||||
<td class="align-middle white-space-nowrap text-end pe-0 ps-5">
|
|
||||||
<div class="btn-reveal-trigger position-static">
|
|
||||||
<button class="btn btn-sm dropdown-toggle dropdown-caret-none transition-none btn-reveal fs-10" type="button" data-bs-toggle="dropdown" data-boundary="window" aria-haspopup="true" aria-expanded="false" data-bs-reference="parent"><span class="fas fa-ellipsis-h fs-10"></span></button>
|
|
||||||
<div class="dropdown-menu dropdown-menu-end py-2"><a class="dropdown-item" href="#!">View</a><a class="dropdown-item" href="#!">Export</a>
|
|
||||||
<div class="dropdown-divider"></div><a class="dropdown-item text-danger" href="#!">Remove</a>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</td>
|
|
||||||
</tr>
|
|
||||||
|
|
||||||
</tbody>
|
</tbody>
|
||||||
</table>
|
</table>
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@ -55,6 +55,7 @@
|
|||||||
<div>
|
<div>
|
||||||
<p class="fw-bold mb-1">{% trans 'Paid Amount' %}</p>
|
<p class="fw-bold mb-1">{% trans 'Paid Amount' %}</p>
|
||||||
<h4 class="fw-bolder text-nowrap">${{invoice.amount_paid}}</h4>
|
<h4 class="fw-bolder text-nowrap">${{invoice.amount_paid}}</h4>
|
||||||
|
<h6 class="fw-normal text-nowrap text-success">${{amount_left}} Left</h6>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user