update
This commit is contained in:
parent
791e74b3d3
commit
cb89dd450b
@ -26,7 +26,7 @@ SECRET_KEY = 'django-insecure-gc9bh4*3=b6hihdnaom0edjsbxh$5t)aap@e8p&340r7)*)qb8
|
||||
# SECURITY WARNING: don't run with debug turned on in production!
|
||||
DEBUG = True
|
||||
|
||||
ALLOWED_HOSTS = ['10.10.1.109', 'localhost', '127.0.0.1', '192.168.1.135', '172.20.10.4']
|
||||
ALLOWED_HOSTS = ['10.10.1.120', 'localhost', '127.0.0.1', '192.168.1.135', '172.20.10.4']
|
||||
|
||||
# Application definition
|
||||
|
||||
@ -111,9 +111,9 @@ WSGI_APPLICATION = 'car_inventory.wsgi.application'
|
||||
DATABASES = {
|
||||
"default": {
|
||||
"ENGINE": "django_prometheus.db.backends.postgresql",
|
||||
"NAME": "haikal_app",
|
||||
"USER": "f95166",
|
||||
"PASSWORD": "Kfsh&rc9788",
|
||||
"NAME": "haikal",
|
||||
"USER": "haikal",
|
||||
"PASSWORD": "haikal",
|
||||
"HOST": "localhost",
|
||||
"PORT": 5432,
|
||||
}
|
||||
|
||||
19
inventory/migrations/0041_salequotation_quotation_number.py
Normal file
19
inventory/migrations/0041_salequotation_quotation_number.py
Normal file
@ -0,0 +1,19 @@
|
||||
# Generated by Django 4.2.17 on 2024-12-22 08:39
|
||||
|
||||
from django.db import migrations, models
|
||||
|
||||
|
||||
class Migration(migrations.Migration):
|
||||
|
||||
dependencies = [
|
||||
('inventory', '0040_additionalservices_display_name'),
|
||||
]
|
||||
|
||||
operations = [
|
||||
migrations.AddField(
|
||||
model_name='salequotation',
|
||||
name='quotation_number',
|
||||
field=models.CharField(default=1, max_length=10, unique=True),
|
||||
preserve_default=False,
|
||||
),
|
||||
]
|
||||
@ -0,0 +1,26 @@
|
||||
# Generated by Django 4.2.17 on 2024-12-22 10:36
|
||||
|
||||
from django.db import migrations, models
|
||||
import django.db.models.deletion
|
||||
import uuid
|
||||
|
||||
class Migration(migrations.Migration):
|
||||
|
||||
dependencies = [
|
||||
('django_ledger', '0017_alter_accountmodel_unique_together_and_more'),
|
||||
('inventory', '0041_salequotation_quotation_number'),
|
||||
]
|
||||
|
||||
operations = [
|
||||
migrations.AddField(
|
||||
model_name='salequotation',
|
||||
name='entity',
|
||||
field=models.ForeignKey(default="cb12725d-3b89-4742-8668-05d825b0b1f0", on_delete=django.db.models.deletion.CASCADE, to='django_ledger.entitymodel'),
|
||||
preserve_default=False,
|
||||
),
|
||||
migrations.AddField(
|
||||
model_name='salequotation',
|
||||
name='is_approved',
|
||||
field=models.BooleanField(default=False),
|
||||
),
|
||||
]
|
||||
17
inventory/migrations/0043_remove_salequotation_status.py
Normal file
17
inventory/migrations/0043_remove_salequotation_status.py
Normal file
@ -0,0 +1,17 @@
|
||||
# Generated by Django 4.2.17 on 2024-12-22 11:48
|
||||
|
||||
from django.db import migrations
|
||||
|
||||
|
||||
class Migration(migrations.Migration):
|
||||
|
||||
dependencies = [
|
||||
('inventory', '0042_salequotation_entity_salequotation_is_approved'),
|
||||
]
|
||||
|
||||
operations = [
|
||||
migrations.RemoveField(
|
||||
model_name='salequotation',
|
||||
name='status',
|
||||
),
|
||||
]
|
||||
@ -36,7 +36,7 @@ class LocalizedNameMixin:
|
||||
class AddDealerInstanceMixin:
|
||||
def form_valid(self, form):
|
||||
if form.is_valid():
|
||||
form.instance.dealer = self.request.user.dealer.get_parent_or_self
|
||||
form.instance.dealer = self.request.user.dealer.get_root_dealer
|
||||
form.save()
|
||||
return super().form_valid(form)
|
||||
else:
|
||||
|
||||
@ -1,3 +1,4 @@
|
||||
import itertools
|
||||
from uuid import uuid4
|
||||
from django.conf import settings
|
||||
from django.db import models, transaction
|
||||
@ -24,9 +25,9 @@ from phonenumber_field.modelfields import PhoneNumberField
|
||||
from django.contrib.contenttypes.models import ContentType
|
||||
from django.utils.timezone import now
|
||||
from .utilities.financials import get_financial_value, get_total, get_total_financials
|
||||
|
||||
from django.db.models import FloatField
|
||||
from .mixins import LocalizedNameMixin
|
||||
|
||||
from django_ledger.models import EntityModel
|
||||
|
||||
class CarMake(models.Model, LocalizedNameMixin):
|
||||
id_car_make = models.AutoField(primary_key=True)
|
||||
@ -538,7 +539,7 @@ class Dealer(models.Model, LocalizedNameMixin):
|
||||
def is_parent(self):
|
||||
return self.dealer_type == "Owner"
|
||||
@property
|
||||
def get_parent_or_self(self):
|
||||
def get_root_dealer(self):
|
||||
return self.parent_dealer if self.parent_dealer else self
|
||||
|
||||
# Vendor Model
|
||||
@ -602,6 +603,8 @@ class Customer(models.Model):
|
||||
@property
|
||||
def get_full_name(self):
|
||||
return f"{self.first_name} {self.middle_name} {self.last_name}"
|
||||
|
||||
|
||||
|
||||
|
||||
class Organization(models.Model, LocalizedNameMixin):
|
||||
@ -639,6 +642,8 @@ class Representative(models.Model, LocalizedNameMixin):
|
||||
|
||||
|
||||
class SaleQuotation(models.Model):
|
||||
quotation_number = models.CharField(max_length=10, unique=True)
|
||||
|
||||
STATUS_CHOICES = [
|
||||
("DRAFT", _("Draft")),
|
||||
("CONFIRMED", _("Confirmed")),
|
||||
@ -647,6 +652,7 @@ class SaleQuotation(models.Model):
|
||||
dealer = models.ForeignKey(
|
||||
Dealer, on_delete=models.CASCADE, related_name="sales", null=True
|
||||
)
|
||||
entity = models.ForeignKey(EntityModel, on_delete=models.CASCADE)
|
||||
customer = models.ForeignKey(
|
||||
Customer,
|
||||
on_delete=models.CASCADE,
|
||||
@ -660,9 +666,10 @@ class SaleQuotation(models.Model):
|
||||
verbose_name=_("Amount"),
|
||||
)
|
||||
remarks = models.TextField(blank=True, null=True, verbose_name=_("Remarks"))
|
||||
status = models.CharField(
|
||||
max_length=10, choices=STATUS_CHOICES, default="DRAFT", verbose_name=_("Status")
|
||||
)
|
||||
is_approved = models.BooleanField(default=False)
|
||||
# status = models.CharField(
|
||||
# max_length=10, choices=STATUS_CHOICES, default="DRAFT", verbose_name=_("Status")
|
||||
# )
|
||||
created_at = models.DateTimeField(auto_now_add=True, verbose_name=_("Created At"))
|
||||
updated_at = models.DateTimeField(auto_now=True, verbose_name=_("Updated At"))
|
||||
|
||||
@ -671,30 +678,53 @@ class SaleQuotation(models.Model):
|
||||
total_quantity = self.quotation_cars.aggregate(total=Sum('quantity'))['total']
|
||||
return total_quantity or 0
|
||||
|
||||
# @property
|
||||
# def total(self):
|
||||
# total = self.quotation_cars.aggregate(total_price=Sum(F('car__finances__selling_price') * F('quantity')))
|
||||
# if total:
|
||||
# return float(total["total_price"]) * 0.15 + float(total["total_price"])
|
||||
# return 0
|
||||
@property
|
||||
def total(self):
|
||||
total = self.quotation_cars.aggregate(total_price=Sum(F('car__finances__selling_price') * F('quantity')))
|
||||
if not total:
|
||||
return 0
|
||||
return total["total_price"]
|
||||
|
||||
@property
|
||||
def total_vat(self):
|
||||
if self.total:
|
||||
return float(self.total) * 0.15 + float(self.total)
|
||||
return 0
|
||||
|
||||
def confirm(self):
|
||||
"""Confirm the quotation and lock financial details."""
|
||||
if self.status != "DRAFT":
|
||||
raise ValueError(_("Only draft quotations can be confirmed."))
|
||||
self.status = "CONFIRMED"
|
||||
self.save()
|
||||
# def confirm(self):
|
||||
# """Confirm the quotation and lock financial details."""
|
||||
# if self.status != "DRAFT":
|
||||
# raise ValueError(_("Only draft quotations can be confirmed."))
|
||||
# self.status = "CONFIRMED"
|
||||
# self.save()
|
||||
|
||||
def cancel(self):
|
||||
"""Cancel the quotation."""
|
||||
if self.status == "CONFIRMED":
|
||||
raise ValueError(_("Cannot cancel a confirmed quotation."))
|
||||
self.status = "CANCELED"
|
||||
self.save()
|
||||
# def cancel(self):
|
||||
# """Cancel the quotation."""
|
||||
# if self.status == "CONFIRMED":
|
||||
# raise ValueError(_("Cannot cancel a confirmed quotation."))
|
||||
# self.status = "CANCELED"
|
||||
# self.save()
|
||||
|
||||
def __str__(self):
|
||||
return f"Quotation #{self.id} for {self.customer}"
|
||||
return f"Quotation #{self.quotation_number} for {self.customer}"
|
||||
|
||||
@property
|
||||
def display_quotation_number(self):
|
||||
return f"QN-{self.quotation_number}"
|
||||
|
||||
def save(self, *args, **kwargs):
|
||||
if not self.quotation_number:
|
||||
self.quotation_number = str(next(self._get_quotation_number())).zfill(6)
|
||||
super().save(*args, **kwargs)
|
||||
|
||||
@classmethod
|
||||
def _get_quotation_number(cls):
|
||||
last_quotation = cls.objects.all().order_by('id').last()
|
||||
if last_quotation:
|
||||
last_quotation_number = int(last_quotation.quotation_number)
|
||||
else:
|
||||
last_quotation_number = 0
|
||||
return itertools.count(last_quotation_number + 1)
|
||||
|
||||
class SaleQuotationCar(models.Model):
|
||||
quotation = models.ForeignKey(
|
||||
@ -733,14 +763,23 @@ class SaleQuotationCar(models.Model):
|
||||
# "total_amount": car_finance.total,
|
||||
}
|
||||
|
||||
# @property
|
||||
# def total(self):
|
||||
# """
|
||||
# Calculate total price dynamically based on quantity and selling price.
|
||||
# """
|
||||
# if not self.car.finances:
|
||||
# return Decimal("0.00")
|
||||
# return self.car.finances.selling_price * self.quantity
|
||||
@property
|
||||
def total(self):
|
||||
"""
|
||||
Calculate total price dynamically based on quantity and selling price.
|
||||
"""
|
||||
if not self.car.finances:
|
||||
return Decimal("0.00")
|
||||
return self.car.finances.selling_price * self.quantity
|
||||
@property
|
||||
def total_vat(self):
|
||||
"""
|
||||
Calculate total price dynamically based on quantity and selling price.
|
||||
"""
|
||||
if not self.car.finances:
|
||||
return Decimal("0.00")
|
||||
price = float(self.car.finances.selling_price * self.quantity)
|
||||
return (price * 0.15) + price
|
||||
|
||||
def __str__(self):
|
||||
return f"{self.car} - Quotation #{self.quotation.id}"
|
||||
|
||||
@ -15,11 +15,19 @@ from django.utils.translation import gettext_lazy as _
|
||||
from . import models
|
||||
|
||||
|
||||
@receiver(pre_delete, sender=models.Dealer)
|
||||
def remove_user_account(sender, instance, **kwargs):
|
||||
user = instance.user
|
||||
if user:
|
||||
user.delete()
|
||||
|
||||
# @receiver(post_save, sender=models.SaleQuotation)
|
||||
# def link_quotation_to_entity(sender, instance, created, **kwargs):
|
||||
# if created:
|
||||
# # Get the corresponding Django Ledger entity for the dealer
|
||||
# entity = EntityModel.objects.get(name=instance.dealer.get_root_dealer.name)
|
||||
# instance.entity = entity
|
||||
# instance.save()
|
||||
# @receiver(pre_delete, sender=models.Dealer)
|
||||
# def remove_user_account(sender, instance, **kwargs):
|
||||
# user = instance.user
|
||||
# if user:
|
||||
# user.delete()
|
||||
@receiver(post_save, sender=models.Car)
|
||||
def create_car_location(sender, instance, created, **kwargs):
|
||||
"""
|
||||
@ -61,18 +69,23 @@ def update_car_status_on_reservation_delete(sender, instance, **kwargs):
|
||||
@receiver(post_save, sender=models.Dealer)
|
||||
def create_ledger_entity(sender, instance, created, **kwargs):
|
||||
if created:
|
||||
entity = EntityModel.objects.create(
|
||||
name=instance.name,
|
||||
admin=instance.user,
|
||||
entity, created = EntityModel.objects.get_or_create(
|
||||
name=instance.get_root_dealer.name,
|
||||
admin=instance.get_root_dealer.user,
|
||||
# address_1=instance.address,
|
||||
accrual_method=False,
|
||||
fy_start_month=1,
|
||||
# depth=0,
|
||||
)
|
||||
|
||||
default_coa = entity.create_chart_of_accounts(assign_as_default=True,
|
||||
print(entity)
|
||||
if created:
|
||||
default_coa = entity.create_chart_of_accounts(assign_as_default=True,
|
||||
commit=True,
|
||||
coa_name=_("Chart of Accounts"))
|
||||
if default_coa:
|
||||
entity.populate_default_coa(activate_accounts=True, coa_model=default_coa)
|
||||
print(f"Ledger entity created for Dealer: {instance.name}")
|
||||
|
||||
# entity.create_account(
|
||||
# coa_model=coa,
|
||||
# code=1010,
|
||||
@ -120,15 +133,12 @@ def create_ledger_entity(sender, instance, created, **kwargs):
|
||||
# active=True)
|
||||
|
||||
|
||||
if default_coa:
|
||||
entity.populate_default_coa(activate_accounts=True, coa_model=default_coa)
|
||||
|
||||
|
||||
# uom_name = _("Unit")
|
||||
# unit_abbr = _("U")
|
||||
#
|
||||
# entity.create_uom(uom_name, unit_abbr)
|
||||
|
||||
print(f"Ledger entity created for Dealer: {instance.name}")
|
||||
|
||||
|
||||
# Create Vendor
|
||||
@ -157,21 +167,21 @@ def create_ledger_vendor(sender, instance, created, **kwargs):
|
||||
|
||||
@receiver(post_save, sender=models.Customer)
|
||||
def create_customer(sender, instance, created, **kwargs):
|
||||
|
||||
if created:
|
||||
entity = EntityModel.objects.filter(name=instance.dealer.name).first()
|
||||
entity = EntityModel.objects.filter(name=instance.dealer.get_root_dealer.name).first()
|
||||
name = f"{instance.first_name} {instance.middle_name} {instance.last_name}"
|
||||
|
||||
entity.create_customer(
|
||||
customer_name=name,
|
||||
customer_number=instance.national_id,
|
||||
address_1=instance.address,
|
||||
phone=instance.phone_number,
|
||||
email=instance.email,
|
||||
sales_tax_rate=0.15,
|
||||
active=True,
|
||||
hidden=False,
|
||||
additional_info={}
|
||||
entity.create_customer(
|
||||
customer_model_kwargs={
|
||||
"customer_name": name,
|
||||
"address_1": instance.address,
|
||||
"phone": instance.phone_number,
|
||||
"email": instance.email,
|
||||
"sales_tax_rate": 0.15,
|
||||
"active": True,
|
||||
"hidden": False,
|
||||
"additional_info": {}
|
||||
}
|
||||
)
|
||||
|
||||
print(f"Customer created: {name}")
|
||||
|
||||
@ -73,6 +73,9 @@ urlpatterns = [
|
||||
path('sales/quotations/', views.QuotationListView.as_view(), name='quotation_list'),
|
||||
path('sales/quotations/<int:pk>/confirm/', views.confirm_quotation, name='confirm_quotation'),
|
||||
path('sales/orders/detail/<int:order_id>/', views.SalesOrderDetailView.as_view(), name='order_detail'),
|
||||
path('quotation/<pk>/pdf/', views.quotation_pdf_view, name='quotation_pdf'),
|
||||
path('generate_invoice/<int:pk>/', views.generate_invoice, name='generate_invoice'),
|
||||
|
||||
|
||||
# Users URLs
|
||||
path('user/create/', views.UserCreateView.as_view(), name='user_create'),
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
0
quotation.pdf
Normal file
0
quotation.pdf
Normal file
@ -24,6 +24,10 @@
|
||||
<script src="https://cdn.jsdelivr.net/npm/bootstrap@5.3.3/dist/js/bootstrap.min.js"></script>
|
||||
<script src="https://cdn.jsdelivr.net/npm/sweetalert2@11.10.5/dist/sweetalert2.all.min.js"></script>
|
||||
|
||||
<script src="https://cdnjs.cloudflare.com/ajax/libs/jspdf/1.5.3/jspdf.min.js"></script>
|
||||
<script src="https://cdnjs.cloudflare.com/ajax/libs/jspdf-autotable/3.8.4/jspdf.plugin.autotable.min.js" integrity="sha512-PRJxIx+FR3gPzyBBl9cPt62DD7owFXVcfYv0CRNFAcLZeEYfht/PpPNTKHicPs+hQlULFhH2tTWdoxnd1UGu1g==" crossorigin="anonymous" referrerpolicy="no-referrer"></script>
|
||||
|
||||
|
||||
<script src="https://cdn.jsdelivr.net/npm/select2@4.1.0-rc.0/dist/js/select2.min.js"></script>
|
||||
<style>
|
||||
body {
|
||||
@ -38,8 +42,9 @@ small, .small {
|
||||
text-transform: uppercase;
|
||||
border-radius: 5px;
|
||||
}
|
||||
|
||||
|
||||
</style>
|
||||
{% block extra_head %}{% endblock extra_head %}
|
||||
</head>
|
||||
<body>
|
||||
{% include 'header.html' %}
|
||||
@ -102,7 +107,14 @@ small, .small {
|
||||
});
|
||||
});
|
||||
|
||||
|
||||
function save_as_pdf(){
|
||||
const quotationHtml = document.getElementById('quotation-html').outerHTML;
|
||||
const printWindow = window.open('', '', 'height=500,width=800');
|
||||
printWindow.document.write(quotationHtml);
|
||||
printWindow.document.close();
|
||||
printWindow.print();
|
||||
printWindow.close();
|
||||
}
|
||||
</script>
|
||||
</body>
|
||||
</html>
|
||||
|
||||
@ -31,9 +31,9 @@
|
||||
</div>
|
||||
|
||||
<div class="container mt-4">
|
||||
<div class="card">
|
||||
<div class="card" id="quotation-html">
|
||||
<div class="card-header">
|
||||
<h4>{% trans "Quotation Details" %} - {{ quotation.id }}</h4>
|
||||
<h4>{% trans "Quotation Details" %} - {{ quotation.display_quotation_number }}</h4>
|
||||
</div>
|
||||
<div class="card-body">
|
||||
<div class="row">
|
||||
@ -47,7 +47,7 @@
|
||||
</div>
|
||||
<div class="col-md-6">
|
||||
<h5>{% trans "Quotation Information" %}</h5>
|
||||
<p><strong>{% trans "Quotation No" %}:</strong> {{ quotation.id }}</p>
|
||||
<p><strong>{% trans "Quotation No" %}:</strong> {{ quotation.display_quotation_number }}</p>
|
||||
<p><strong>{% trans "Date" %}:</strong> {{ quotation.created_at|date }}</p>
|
||||
<p><strong>{% trans "Remarks" %}:</strong> {{ quotation.remarks }}</p>
|
||||
</div>
|
||||
@ -75,7 +75,7 @@
|
||||
<td>{{ item.quantity }}</td>
|
||||
<td>{{ item.car.finances.selling_price }}</td>
|
||||
<td>{{ 0.15 }}</td>
|
||||
<td>{{ item.total }}</td>
|
||||
<td>{{ item.total_vat}}</td>
|
||||
</tr>
|
||||
{% endfor %}
|
||||
</tbody>
|
||||
@ -83,9 +83,9 @@
|
||||
<tr>
|
||||
<th colspan="3">{% trans "Totals" %}</th>
|
||||
<th>{{ quotation.total_quantity }}</th>
|
||||
<th>{{ total_sales_before_vat }}</th>
|
||||
<th>{{ vat_amount }}</th>
|
||||
<th>{{ total_sales_after_vat }}</th>
|
||||
<th>{{ quotation.total }}</th>
|
||||
<th>{{ vat_amount }}</th>
|
||||
<th>{{ quotation.total_vat }}</th>
|
||||
</tr>
|
||||
</tfoot>
|
||||
</table>
|
||||
@ -114,8 +114,7 @@
|
||||
<td>{{ total_cost }}</td>
|
||||
<td>{{ total_vat }}</td>
|
||||
<td>{{ total_cost_vat }}</td>
|
||||
</tr>
|
||||
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
@ -123,7 +122,15 @@
|
||||
<a href="{% url 'quotation_list' %}" class="btn btn-secondary">{% trans "Back to Quotations" %}</a>
|
||||
{% if perms.inventory.change_carfinance and quotation.status == 'DRAFT' %}
|
||||
<button class="btn btn-success" data-bs-toggle="modal" data-bs-target="#customCardModal">{% trans "Approve Quotation" %}</button>
|
||||
{% endif %}
|
||||
{% endif %}
|
||||
<a href="{% url 'quotation_pdf' quotation.pk %}" class="btn btn-primary">Download as PDF</a>
|
||||
{% if not quotation.is_approved %}
|
||||
<a href="{% url 'confirm_quotation' quotation.pk %}" class="btn btn-success">Approve</a>
|
||||
{% endif %}
|
||||
{% if quotation.is_approved %}
|
||||
<a href="{% url 'generate_invoice' quotation.pk %}" class="btn btn-success">Generate Invoice</a>
|
||||
{% endif %}
|
||||
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@ -11,6 +11,7 @@
|
||||
<thead>
|
||||
<tr>
|
||||
<th>#</th>
|
||||
<th>{% trans "Quotation Number" %}</th>
|
||||
<th>{% trans "Customer" %}</th>
|
||||
<th>{% trans "Total Cars" %}</th>
|
||||
<th>{% trans "Total Amount" %}</th>
|
||||
@ -22,18 +23,15 @@
|
||||
{% for quotation in quotations %}
|
||||
<tr>
|
||||
<td>{{ forloop.counter }}</td>
|
||||
<td>{{ quotation.quotation_number }}</td>
|
||||
<td>{{ quotation.customer.get_full_name }}</td>
|
||||
<td>{{ quotation.quotation_cars.count }}</td>
|
||||
<td>{{ quotation.quotation_cars.get_financial_details.total_amount }}</td>
|
||||
<td>{{ quotation.total_vat }}</td>
|
||||
<td>
|
||||
{% if quotation.status == 'DRAFT' %}
|
||||
<span class="badge rounded-pill bg-light">{{ quotation.status }}</span>
|
||||
{% elif quotation.status == 'PENDING' %}
|
||||
<span class="badge rounded-pill bg-warning">{{ quotation.status }}</span>
|
||||
{% elif quotation.status == 'CONFIRMED' %}
|
||||
<span class="badge rounded-pill bg-success">{{ quotation.status }}</span>
|
||||
{% elif quotation.status == 'CANCELED' %}
|
||||
<span class="badge rounded-pill bg-danger">{{ quotation.status }}</span>
|
||||
{% if quotation.is_approved %}
|
||||
<span class="badge rounded-pill bg-success">Approved</span>
|
||||
{% else %}
|
||||
<span class="badge rounded-pill bg-warning">Pending For Approval</span>
|
||||
{% endif %}
|
||||
</td>
|
||||
<td>{{ quotation.created_at|date:"d/m/Y H:i" }}</td>
|
||||
|
||||
112
templates/sales/quotation_pdf.html
Normal file
112
templates/sales/quotation_pdf.html
Normal file
@ -0,0 +1,112 @@
|
||||
{% load static %} {% load i18n %}
|
||||
<!DOCTYPE html>
|
||||
{% get_current_language as LANGUAGE_CODE %}
|
||||
<html lang="{{ LANGUAGE_CODE }}" dir="{% if LANGUAGE_CODE == 'ar' %}rtl{% else %}ltr{% endif %}" data-bs-theme="dark">
|
||||
<head>
|
||||
<meta charset="UTF-8" />
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
|
||||
<meta name="mobile-web-app-capable" content="yes" />
|
||||
<title>{% block title %}{% trans 'HAIKAL' %}{% endblock %}</title>
|
||||
<link href="{% static 'css/themes/cosmo/_variables.scss' %}" rel="stylesheet" />
|
||||
<link href="{% static 'css/custom.css' %}" rel="stylesheet" />
|
||||
{% if LANGUAGE_CODE == 'ar' %}
|
||||
<link href="{% static 'css/themes/cosmo/bootstrap.rtl.css' %}" rel="stylesheet" />
|
||||
{% else %}
|
||||
<link href="{% static 'css/themes/cosmo/bootstrap.css' %}" rel="stylesheet" />
|
||||
{% endif %}
|
||||
<link href="{% static 'css/themes/cosmo/_bootswatch.scss' %}" rel="stylesheet" />
|
||||
<link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/bootstrap-icons@1.11.3/font/bootstrap-icons.min.css" />
|
||||
</head>
|
||||
<body>
|
||||
<div class="container mt-4">
|
||||
<div class="card" id="quotation-html">
|
||||
<div class="card-header">
|
||||
<h4>{% trans "Quotation Details" %} - {{ quotation.quotation_number }}</h4>
|
||||
</div>
|
||||
<div class="card-body">
|
||||
<div class="row">
|
||||
<div class="col-md-6">
|
||||
<h5>{% trans "Customer Details" %}</h5>
|
||||
<p>
|
||||
<strong>{% trans "Name" %}:</strong>
|
||||
{{ quotation.customer.get_full_name }}</p>
|
||||
<p><strong>{% trans "Address" %}:</strong> {{ quotation.customer.address }}</p>
|
||||
<p><strong>{% trans "VAT No" %}:</strong> {{ quotation.customer.vat_number }}</p>
|
||||
</div>
|
||||
<div class="col-md-6">
|
||||
<h5>{% trans "Quotation Information" %}</h5>
|
||||
<p><strong>{% trans "Quotation No" %}:</strong> {{ quotation.quotation_number }}</p>
|
||||
<p><strong>{% trans "Date" %}:</strong> {{ quotation.created_at|date }}</p>
|
||||
<p><strong>{% trans "Remarks" %}:</strong> {{ quotation.remarks }}</p>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<h5 class="mt-4">{% trans "Car Details" %}</h5>
|
||||
<table class="table table-bordered">
|
||||
<thead>
|
||||
<tr>
|
||||
<th>{% trans "VIN" %}</th>
|
||||
<th>{% trans "Model" %}</th>
|
||||
<th>{% trans "Year" %}</th>
|
||||
<th>{% trans "Quantity" %}</th>
|
||||
<th>{% trans "Price" %}</th>
|
||||
<th>{% trans "VAT" %}</th>
|
||||
<th>{% trans "Total" %}</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
{% for item in quotation.quotation_cars.all %}
|
||||
<tr>
|
||||
<td>{{ item.car.vin }}</td>
|
||||
<td>{{ item.car.id_car_model.get_local_name }}</td>
|
||||
<td>{{ item.car.year }}</td>
|
||||
<td>{{ item.quantity }}</td>
|
||||
<td>{{ item.car.finances.selling_price }}</td>
|
||||
<td>{{ 0.15 }}</td>
|
||||
<td>{{ item.total_vat}}</td>
|
||||
</tr>
|
||||
{% endfor %}
|
||||
</tbody>
|
||||
<tfoot>
|
||||
<tr>
|
||||
<th colspan="3">{% trans "Totals" %}</th>
|
||||
<th>{{ quotation.total_quantity }}</th>
|
||||
<th>{{ quotation.total }}</th>
|
||||
<th>{{ vat_amount }}</th>
|
||||
<th>{{ quotation.total_vat }}</th>
|
||||
</tr>
|
||||
</tfoot>
|
||||
</table>
|
||||
|
||||
<h5 class="mt-4">{% trans "Additional Costs" %}</h5>
|
||||
<table class="table table-bordered">
|
||||
<thead>
|
||||
<tr>
|
||||
<th>{% trans "Additions" %}</th>
|
||||
<th>{% trans "Cost" %}</th>
|
||||
<th>{% trans "VAT %" %}</th>
|
||||
<th>{% trans "Total Cost with VAT" %}</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
{% for service in services %}
|
||||
<tr>
|
||||
<td>{{service.name}}</td>
|
||||
<td>{{ service.price }}</td>
|
||||
<td>{{ service.vated }}</td>
|
||||
<td>{{ service.total_price_vat }}</td>
|
||||
</tr>
|
||||
{% endfor %}
|
||||
<tr>
|
||||
<td></td>
|
||||
<td>{{ total_cost }}</td>
|
||||
<td>{{ total_vat }}</td>
|
||||
<td>{{ total_cost_vat }}</td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</body>
|
||||
</html>
|
||||
Loading…
x
Reference in New Issue
Block a user