Compare commits

...

7 Commits

27 changed files with 2098 additions and 864 deletions

5
.gitignore vendored
View File

@ -163,8 +163,11 @@ GitHub.sublime-settings
.history .history
static-copy
static static
static/*
staticfiles staticfiles
media media
tmp tmp
logs logs
static/testdir

View File

@ -17,31 +17,17 @@ import django
django.setup() django.setup()
from django.urls import path from django.urls import path
from channels.routing import ProtocolTypeRouter, URLRouter from channels.routing import ProtocolTypeRouter, URLRouter
from whitenoise import WhiteNoise
from channels.auth import AuthMiddlewareStack from channels.auth import AuthMiddlewareStack
from api import routing
from inventory.notifications.sse import NotificationSSEApp from inventory.notifications.sse import NotificationSSEApp
from django.urls import re_path from django.urls import re_path
from django.core.asgi import get_asgi_application from django.core.asgi import get_asgi_application
from django.contrib.staticfiles.handlers import ASGIStaticFilesHandler
from pathlib import Path
# application = ProtocolTypeRouter(
# {
# "http": get_asgi_application(),
# # "websocket": AuthMiddlewareStack(URLRouter(routing.websocket_urlpatterns)),
# }
# )
os.environ.setdefault("DJANGO_SETTINGS_MODULE", "car_inventory.settings")
django.setup()
BASE_DIR = Path(__file__).resolve().parent.parent
# BASE_DIR = Path(__file__).resolve().parent.parent
app = get_asgi_application() app = get_asgi_application()
# app = WhiteNoise(app, root=str(BASE_DIR / 'staticfiles'))
application = ProtocolTypeRouter( application = ProtocolTypeRouter(
{ {
"http": AuthMiddlewareStack( "http": AuthMiddlewareStack(
@ -50,7 +36,7 @@ application = ProtocolTypeRouter(
path("sse/notifications/", NotificationSSEApp()), path("sse/notifications/", NotificationSSEApp()),
re_path( re_path(
r"", app r"", app
), # All other routes go to Django ),
] ]
) )
), ),
@ -58,5 +44,5 @@ application = ProtocolTypeRouter(
) )
if django.conf.settings.DEBUG: # if django.conf.settings.DEBUG:
application = ASGIStaticFilesHandler(app) # application = ASGIStaticFilesHandler(app)

View File

@ -5,7 +5,7 @@ from django.urls import path, include
from schema_graph.views import Schema from schema_graph.views import Schema
from django.conf.urls.static import static from django.conf.urls.static import static
from django.conf.urls.i18n import i18n_patterns from django.conf.urls.i18n import i18n_patterns
from inventory.notifications.sse import NotificationSSEApp # from inventory.notifications.sse import NotificationSSEApp
# import debug_toolbar # import debug_toolbar
# from two_factor.urls import urlpatterns as tf_urls # from two_factor.urls import urlpatterns as tf_urls
@ -33,5 +33,7 @@ urlpatterns += i18n_patterns(
# path('', include(tf_urls)), # path('', include(tf_urls)),
) )
# if not settings.DEBUG: if settings.DEBUG:
urlpatterns += static(settings.MEDIA_URL, document_root=settings.MEDIA_ROOT) urlpatterns += static(settings.MEDIA_URL, document_root=settings.MEDIA_ROOT)
urlpatterns += static(settings.STATIC_URL, document_root = settings.STATIC_ROOT)

View File

@ -1,82 +1,3 @@
# import json
# from django.contrib.auth.models import AnonymousUser
# from django.contrib.auth import get_user_model
# from django.db import close_old_connections
# from urllib.parse import parse_qs
# from channels.db import database_sync_to_async
# from inventory.models import Notification
# import asyncio
# @database_sync_to_async
# def get_notifications(user, last_id):
# return Notification.objects.filter(
# user=user, id__gt=last_id, is_read=False
# ).order_by("created")
# class NotificationSSEApp:
# async def __call__(self, scope, receive, send):
# if scope["type"] != "http":
# return
# query_string = parse_qs(scope["query_string"].decode())
# last_id = int(query_string.get("last_id", [0])[0])
# # Get user from scope if using AuthMiddlewareStack
# user = scope.get("user", AnonymousUser())
# if not user.is_authenticated:
# await send({
# "type": "http.response.start",
# "status": 403,
# "headers": [(b"content-type", b"text/plain")],
# })
# await send({
# "type": "http.response.body",
# "body": b"Unauthorized",
# })
# return
# await send({
# "type": "http.response.start",
# "status": 200,
# "headers": [
# (b"content-type", b"text/event-stream"),
# (b"cache-control", b"no-cache"),
# (b"x-accel-buffering", b"no"),
# ]
# })
# try:
# while True:
# close_old_connections()
# notifications = await get_notifications(user, last_id)
# for notification in notifications:
# data = {
# "id": notification.id,
# "message": notification.message,
# "created": notification.created.isoformat(),
# "is_read": notification.is_read,
# }
# event_str = (
# f"id: {notification.id}\n"
# f"event: notification\n"
# f"data: {json.dumps(data)}\n\n"
# )
# await send({
# "type": "http.response.body",
# "body": event_str.encode("utf-8"),
# "more_body": True
# })
# last_id = notification.id
# await asyncio.sleep(2)
# except asyncio.CancelledError:
# pass
import json import json
import time import time
from django.contrib.auth.models import AnonymousUser from django.contrib.auth.models import AnonymousUser

View File

@ -1003,10 +1003,10 @@ def create_po_item_upload(sender, instance, created, **kwargs):
if instance.po_status == "fulfilled" or instance.po_status == 'approved': if instance.po_status == "fulfilled" or instance.po_status == 'approved':
for item in instance.get_itemtxs_data()[0]: for item in instance.get_itemtxs_data()[0]:
dealer = models.Dealer.objects.get(entity=instance.entity) dealer = models.Dealer.objects.get(entity=instance.entity)
if item.bill_model.is_paid(): if item.bill_model and item.bill_model.is_paid():
models.PoItemsUploaded.objects.get_or_create( models.PoItemsUploaded.objects.get_or_create(
dealer=dealer, po=instance, item=item, status=instance.po_status dealer=dealer, po=instance, item=item, status=instance.po_status
) )
# @receiver(post_save, sender=models.Staff) # @receiver(post_save, sender=models.Staff)

View File

@ -342,6 +342,11 @@ urlpatterns = [
views.CarDetailView.as_view(), views.CarDetailView.as_view(),
name="car_detail", name="car_detail",
), ),
path(
"<slug:dealer_slug>/cars/<slug:slug>/estimate/",
views.create_estimate_for_car,
name="create_estimate_for_car",
),
path("cars/<slug:slug>/history/", views.car_history, name="car_history"), path("cars/<slug:slug>/history/", views.car_history, name="car_history"),
path( path(
"<slug:dealer_slug>/cars/<slug:slug>/update/", "<slug:dealer_slug>/cars/<slug:slug>/update/",
@ -938,7 +943,7 @@ urlpatterns = [
views.ItemServiceUpdateView.as_view(), views.ItemServiceUpdateView.as_view(),
name="item_service_update", name="item_service_update",
), ),
path( path(
"<slug:dealer_slug>/items/services/<int:pk>/detail/", "<slug:dealer_slug>/items/services/<int:pk>/detail/",
views.ItemServiceDetailView.as_view(), views.ItemServiceDetailView.as_view(),
@ -962,7 +967,7 @@ urlpatterns = [
), ),
path( path(
"<slug:dealer_slug>/items/expeneses/<uuid:pk>/detail/", "<slug:dealer_slug>/items/expeneses/<uuid:pk>/detail/",
views.ItemExpenseDetailView.as_view(), views.ItemExpenseDetailView.as_view(),
name="item_expense_detail", name="item_expense_detail",
), ),
# Bills # Bills

View File

@ -1329,7 +1329,9 @@ def get_finance_data(estimate, dealer):
additional_services = car.get_additional_services() additional_services = car.get_additional_services()
discounted_price = Decimal(car.marked_price) - discount discounted_price = Decimal(car.marked_price) - discount
vat_amount = discounted_price * vat.rate vat_amount = discounted_price * vat.rate
total_services_amount=additional_services.get("total")
total_services_vat = sum([x[1] for x in additional_services.get("services")]) total_services_vat = sum([x[1] for x in additional_services.get("services")])
total_services_amount_=additional_services.get("total_")
total_vat = vat_amount + total_services_vat total_vat = vat_amount + total_services_vat
return { return {
"car": car, "car": car,
@ -1340,9 +1342,16 @@ def get_finance_data(estimate, dealer):
"discount_amount": discount, "discount_amount": discount,
"additional_services": additional_services, "additional_services": additional_services,
"final_price": discounted_price + vat_amount, "final_price": discounted_price + vat_amount,
"total_services_vat": total_services_vat, "total_services_vat": total_services_vat,
"total_services_amount":total_services_amount,
"total_services_amount_":total_services_amount_,
"total_vat": total_vat, "total_vat": total_vat,
"grand_total": discounted_price + total_vat + additional_services.get("total"), "grand_total": discounted_price + total_vat + additional_services.get("total"),
} }
# totals = self.calculate_totals() # totals = self.calculate_totals()

View File

@ -4656,7 +4656,7 @@ def sales_list_view(request, dealer_slug):
search_query = request.GET.get('q', None) search_query = request.GET.get('q', None)
if search_query: if search_query:
qs = qs.filter( qs = qs.filter(
Q(order_number__icontains=search_query)| Q(customer__phone_number__icontains=search_query)|
Q(customer__customer_name__icontains=search_query) Q(customer__customer_name__icontains=search_query)
).distinct() ).distinct()
@ -5092,6 +5092,7 @@ class EstimateDetailView(LoginRequiredMixin, PermissionRequiredMixin, DetailView
def get_context_data(self, **kwargs): def get_context_data(self, **kwargs):
dealer = get_object_or_404(models.Dealer, slug=self.kwargs["dealer_slug"]) dealer = get_object_or_404(models.Dealer, slug=self.kwargs["dealer_slug"])
estimate = kwargs.get("object") estimate = kwargs.get("object")
if estimate.get_itemtxs_data(): if estimate.get_itemtxs_data():
# calculator = CarFinanceCalculator(estimate) # calculator = CarFinanceCalculator(estimate)
# finance_data = calculator.get_finance_data() # finance_data = calculator.get_finance_data()
@ -5099,6 +5100,8 @@ class EstimateDetailView(LoginRequiredMixin, PermissionRequiredMixin, DetailView
invoice_obj = InvoiceModel.objects.all().filter(ce_model=estimate).first() invoice_obj = InvoiceModel.objects.all().filter(ce_model=estimate).first()
kwargs["data"] = finance_data kwargs["data"] = finance_data
kwargs["customer_obj"]=estimate.customer.customer_set.first()
kwargs['dealer_info']=dealer
kwargs["invoice"] = invoice_obj kwargs["invoice"] = invoice_obj
try: try:
@ -5119,29 +5122,33 @@ class EstimatePrintView(EstimateDetailView):
It reuses the data-fetching logic from EstimateDetailView but It reuses the data-fetching logic from EstimateDetailView but
uses a dedicated, stripped-down print template. uses a dedicated, stripped-down print template.
""" """
template_name = "sales/estimates/estimate_preview.html"
def get(self, request, *args, **kwargs): def get(self, request, *args, **kwargs):
self.object = self.get_object() self.object = self.get_object()
context = self.get_context_data(object=self.object) context = self.get_context_data(object=self.object)
# lang = request.GET.get('lang', 'ar') # lang = request.GET.get('lang', 'ar')
template_path = "sales/estimates/estimate_preview.html" if request.GET.get('lang')=='en':
template_path = "sales/estimates/estimate_preview_en.html"
else:
template_path = "sales/estimates/estimate_preview_ar.html"
html_string = render_to_string(template_path, context) html_string = render_to_string(template_path, context)
base_url = request.build_absolute_uri('/')
pdf_file = HTML(string=html_string).write_pdf() pdf_file = HTML(string=html_string, base_url=base_url).write_pdf()
response = HttpResponse(pdf_file, content_type='application/pdf') response = HttpResponse(pdf_file, content_type='application/pdf')
response['Content-Disposition'] = f'attachment; filename="estimate_{self.object.estimate_number}.pdf"' response['Content-Disposition'] = f'attachment; filename="estimate_{self.object.estimate_number}.pdf"'
return response return response
@ -5522,7 +5529,7 @@ class InvoiceListView(LoginRequiredMixin, PermissionRequiredMixin, ListView):
dealer = get_object_or_404(models.Dealer, slug=self.kwargs["dealer_slug"]) dealer = get_object_or_404(models.Dealer, slug=self.kwargs["dealer_slug"])
entity = dealer.entity entity = dealer.entity
staff = getattr(self.request.user, "staff", None) staff = getattr(self.request.user, "staff", None)
qs = [] qs=None
try: try:
if any( if any(
[ [
@ -5871,7 +5878,6 @@ class InvoicePreviewView(LoginRequiredMixin, PermissionRequiredMixin, DetailView
model = InvoiceModel model = InvoiceModel
context_object_name = "invoice" context_object_name = "invoice"
template_name = "sales/invoices/invoice_preview.html"
permission_required = ["django_ledger.view_invoicemodel"] permission_required = ["django_ledger.view_invoicemodel"]
def get_context_data(self, **kwargs): def get_context_data(self, **kwargs):
@ -5881,8 +5887,37 @@ class InvoicePreviewView(LoginRequiredMixin, PermissionRequiredMixin, DetailView
# calculator = CarFinanceCalculator(invoice) # calculator = CarFinanceCalculator(invoice)
finance_data = get_finance_data(invoice,dealer) finance_data = get_finance_data(invoice,dealer)
kwargs["data"] = finance_data kwargs["data"] = finance_data
kwargs["dealer"] = dealer kwargs["dealer_info"] = dealer
kwargs["customer_obj"]=invoice.customer.customer_set.first()
return super().get_context_data(**kwargs) return super().get_context_data(**kwargs)
def get(self, request, *args, **kwargs):
self.object = self.get_object()
context = self.get_context_data(object=self.object)
# lang = request.GET.get('lang', 'ar')
if request.GET.get('lang')=='en':
template_path = "sales/invoices/invoice_preview_en.html"
elif request.GET.get('lang')=='ar':
template_path = "sales/invoices/invoice_preview_ar.html"
else:
# just for preview not for download
return render(request,'sales/invoices/invoice_preview.html',context)
html_string = render_to_string(template_path, context)
base_url = request.build_absolute_uri('/')
pdf_file = HTML(string=html_string, base_url=base_url).write_pdf()
response = HttpResponse(pdf_file, content_type='application/pdf')
response['Content-Disposition'] = f'attachment; filename="invoice_{self.object.invoice_number}.pdf"'
return response
# payments # payments
@ -6221,10 +6256,10 @@ class LeadListView(LoginRequiredMixin, PermissionRequiredMixin, ListView):
| Q(last_name__icontains=query) | Q(last_name__icontains=query)
| Q(id_car_make__name__icontains=query) | Q(id_car_make__name__icontains=query)
| Q(id_car_model__name__icontains=query) | Q(id_car_model__name__icontains=query)
| Q(email__icontains=query)
| Q(phone_number__icontains=query) | Q(phone_number__icontains=query)
| Q(next_action__icontains=query) | Q(next_action__icontains=query)
| Q(staff__name__icontains=query) | Q(staff__first_name__icontains=query)
| Q(staff__last_name__icontains=query)
) )
if self.request.is_dealer: # or self.request.is_manager: if self.request.is_dealer: # or self.request.is_manager:
@ -10238,7 +10273,8 @@ def payment_callback(request, dealer_slug):
# return render(request, "payment_failed.html", {"message": message}) # return render(request, "payment_failed.html", {"message": message})
@login_required @login_required
async def sse_stream(request): # 👈 Mark as async! async def sse_stream(request):
import asyncio
def event_generator(): def event_generator():
last_id = int(request.GET.get("last_id", 0)) last_id = int(request.GET.get("last_id", 0))
@ -10878,19 +10914,23 @@ def InventoryItemCreateView(request, dealer_slug):
serie = request.POST.get("serie") serie = request.POST.get("serie")
trim = request.POST.get("trim") trim = request.POST.get("trim")
year = request.POST.get("year") year = request.POST.get("year")
exterior = models.ExteriorColors.objects.get( exterior = request.POST.get("exterior")
pk=request.POST.get("exterior") interior = request.POST.get("interior")
)
interior = models.InteriorColors.objects.get(
pk=request.POST.get("interior")
)
make_name = models.CarMake.objects.get(pk=make) make_name = models.CarMake.objects.get(pk=make)
model_name = models.CarModel.objects.get(pk=model) model_name = models.CarModel.objects.get(pk=model)
serie_name = models.CarSerie.objects.get(pk=serie) serie_name = models.CarSerie.objects.get(pk=serie)
trim_name = models.CarTrim.objects.get(pk=trim) trim_name = models.CarTrim.objects.get(pk=trim)
exterior_name = models.ExteriorColors.objects.get(
pk=request.POST.get("exterior")
)
interior_name = models.InteriorColors.objects.get(
pk=request.POST.get("interior")
)
inventory_name = f"{make_name.name} || {model_name.name} || {serie_name.name} || {trim_name.name} || {year} || {exterior_name.name} || {interior_name.name}"
display_name = f"{make_name.name} {model_name.name} {serie_name.name} {trim_name.name} {year} {exterior_name.name}"
inventory_name = f"{make_name.name} || {model_name.name} || {serie_name.name} || {trim_name.name} || {year} || {exterior.name} || {interior.name}"
if ( if (
inventory := entity.get_items_inventory() inventory := entity.get_items_inventory()
.filter(name=inventory_name) .filter(name=inventory_name)
@ -10898,17 +10938,27 @@ def InventoryItemCreateView(request, dealer_slug):
): ):
messages.error(request, _("Inventory item already exists")) messages.error(request, _("Inventory item already exists"))
return response return response
uom = entity.get_uom_all().filter(name="Unit").first() uom = entity.get_uom_all().filter(name="Unit").first()
if not uom: if not uom:
uom = entity.create_uom(name="Unit", unit_abbr="unit") uom = entity.create_uom(name="Unit", unit_abbr="unit")
entity.create_item_inventory( item = entity.create_item_inventory(
name=inventory_name, name=display_name,
uom_model=uom, uom_model=uom,
item_type=ItemModel.ITEM_TYPE_MATERIAL, item_type=ItemModel.ITEM_TYPE_MATERIAL,
inventory_account=account, inventory_account=account,
coa_model=coa, coa_model=coa,
) )
item.additional_info.update(
{
"make": make,
"model": model,
"serie": serie,
"trim": trim,
"year": year,
"exterior": exterior,
"interior": interior,
})
item.save()
messages.success(request, _("Inventory item created successfully")) messages.success(request, _("Inventory item created successfully"))
return response return response
@ -11005,6 +11055,9 @@ class PurchaseOrderDetailView(LoginRequiredMixin, PermissionRequiredMixin, Detai
for i in po_items_qs.values("po_total_amount", "po_item_status") for i in po_items_qs.values("po_total_amount", "po_item_status")
if i["po_item_status"] != "cancelled" if i["po_item_status"] != "cancelled"
) )
items = [{"total": x.total_amount, "q": x.quantity} for x in po_model.get_itemtxs_data()[0].all()]
po_quantity = sum(item["q"] for item in items)
context['po_quantity']=po_quantity
return context return context
def get(self, request, *args, **kwargs): def get(self, request, *args, **kwargs):
self.object = self.get_object() self.object = self.get_object()
@ -11014,18 +11067,18 @@ class PurchaseOrderDetailView(LoginRequiredMixin, PermissionRequiredMixin, Detai
"item_model", "bill_model" "item_model", "bill_model"
) )
) )
if self.object.po_status == 'fulfilled': if self.object.po_status == 'fulfilled':
context['po_items_list']=po_items_qs context['po_items_list']=po_items_qs
context['vendor']=po_items_qs.first().bill_model.vendor context['vendor']=po_items_qs.first().bill_model.vendor
context['dealer']=request.dealer context['dealer']=request.dealer
# Check if PDF format is requested # Check if PDF format is requested
if request.GET.get('format') == 'pdf': if request.GET.get('format') == 'pdf':
# Use a separate, print-friendly template for the PDF # Use a separate, print-friendly template for the PDF
if request.GET.get('lang')=='en': if request.GET.get('lang')=='en':
html_string = render_to_string( html_string = render_to_string(
"purchase_orders/po_detail_en_pdf.html", "purchase_orders/po_detail_en_pdf.html",
context context
) )
else: else:
@ -11035,9 +11088,8 @@ class PurchaseOrderDetailView(LoginRequiredMixin, PermissionRequiredMixin, Detai
) )
base_url = request.build_absolute_uri('/')
# Use WeasyPrint to generate the PDF pdf = HTML(string=html_string, base_url=base_url).write_pdf()
pdf = HTML(string=html_string).write_pdf()
response = HttpResponse(pdf, content_type="application/pdf") response = HttpResponse(pdf, content_type="application/pdf")
response["Content-Disposition"] = f'attachment; filename="PO_{self.object.po_number}.pdf"' response["Content-Disposition"] = f'attachment; filename="PO_{self.object.po_number}.pdf"'
@ -11223,18 +11275,14 @@ def upload_cars(request, dealer_slug, pk=None):
) )
try: try:
if item: if item:
data = [x.strip() for x in item.item_model.name.split("||")] # data = [x.strip() for x in item.item_model.name.split("||")]
make = models.CarMake.objects.filter(is_sa_import=True).get( make = models.CarMake.objects.get(pk=item.addition_info.get("make"))
name=data[0] model = models.CarModel.objects.get(pk=item.addition_info.get("model"))
) trim = models.CarTrim.objects.get(pk=item.addition_info.get("trim"))
model = make.carmodel_set.get(name=data[1]) serie = models.CarSerie.objects.get(pk=item.addition_info.get("serie"))
trim = models.CarTrim.objects.filter( year = item.addition_info.get("year")
name=data[3], id_car_serie__id_car_model=model.id_car_model exterior = models.ExteriorColors.objects.get(pk=item.addition_info.get("exterior"))
).first() interior = models.InteriorColors.objects.get(pk=item.addition_info.get("interior"))
serie = trim.id_car_serie
year = data[4]
exterior = models.ExteriorColors.objects.get(name=data[5])
interior = models.InteriorColors.objects.get(name=data[6])
receiving_date = timezone.now() receiving_date = timezone.now()
vendor_model = item.bill_model.vendor vendor_model = item.bill_model.vendor
vendor = models.Vendor.objects.get(vendor_model=vendor_model) vendor = models.Vendor.objects.get(vendor_model=vendor_model)

View File

@ -35,7 +35,8 @@
<div class="row my-5"> <div class="row my-5">
<div class="card rounded "> <div class="card rounded ">
<div class="card-header "> <div class="card-header ">
<p class="mb-0">{{ _("Group Details") }}</p> <p class="mb-2">{{ _("Group Details") }}</p>
<a class="btn btn-phoenix-secondary " href="{% url 'group_list' request.dealer.slug %}">{% trans "Group List" %}</a>
</div> </div>
<div class="card-body"> <div class="card-body">
<div class="row"> <div class="row">

View File

@ -87,9 +87,13 @@
didOpen: (toast) => { didOpen: (toast) => {
toast.onmouseenter = Swal.stopTimer; toast.onmouseenter = Swal.stopTimer;
toast.onmouseleave = Swal.resumeTimer; toast.onmouseleave = Swal.resumeTimer;
} }); }
});
{% with last_notif=notifications_|last %}
let lastNotificationId = {{ last_notif.id|default:0 }};
{% endwith %}
let lastNotificationId = {{ notifications_.last.id|default:0 }};
let seenNotificationIds = new Set(); let seenNotificationIds = new Set();
let counter = document.getElementById('notification-counter'); let counter = document.getElementById('notification-counter');
let notificationsContainer = document.getElementById('notifications-container'); let notificationsContainer = document.getElementById('notifications-container');
@ -100,7 +104,6 @@
let initialUnreadCount = {{ notifications_.count|default:0 }}; let initialUnreadCount = {{ notifications_.count|default:0 }};
updateCounter(initialUnreadCount); updateCounter(initialUnreadCount);
fetchInitialNotifications(); fetchInitialNotifications();
function fetchInitialNotifications() { function fetchInitialNotifications() {
@ -108,29 +111,22 @@
.then(response => response.json()) .then(response => response.json())
.then(data => { .then(data => {
if (data.notifications && data.notifications.length > 0) { if (data.notifications && data.notifications.length > 0) {
lastNotificationId = data.notifications[0].id; lastNotificationId = data.notifications[0].id;
seenNotificationIds = new Set(); seenNotificationIds = new Set();
let unreadCount = 0; let unreadCount = 0;
data.notifications.forEach(notification => { data.notifications.forEach(notification => {
seenNotificationIds.add(notification.id); seenNotificationIds.add(notification.id);
if (!notification.is_read) { if (!notification.is_read) unreadCount++;
unreadCount++;
}
}); });
renderNotifications(data.notifications); renderNotifications(data.notifications);
updateCounter(unreadCount); updateCounter(unreadCount);
setTimeout(() => {
connectSSE();
}, 5000);
} }
// Always connect SSE after initial load
setTimeout(() => {
connectSSE();
}, 1000);
}) })
.catch(error => { .catch(error => {
console.error('Error fetching initial notifications:', error); console.error('Error fetching initial notifications:', error);
@ -143,12 +139,12 @@
eventSource.close(); eventSource.close();
} }
// ✅ FIXED URL HERE
eventSource = new EventSource("/sse/notifications/?last_id=" + lastNotificationId); eventSource = new EventSource("/sse/notifications/?last_id=" + lastNotificationId);
eventSource.addEventListener('notification', function(e) { eventSource.addEventListener('notification', function(e) {
try { try {
const data = JSON.parse(e.data); const data = JSON.parse(e.data);
if (seenNotificationIds.has(data.id)) return; if (seenNotificationIds.has(data.id)) return;
seenNotificationIds.add(data.id); seenNotificationIds.add(data.id);
@ -158,6 +154,11 @@
updateCounter('increment'); updateCounter('increment');
if (!notificationsContainer) {
console.warn("Notification container missing, can't render SSE event");
return;
}
const notificationElement = createNotificationElement(data); const notificationElement = createNotificationElement(data);
notificationsContainer.insertAdjacentHTML('afterbegin', notificationElement); notificationsContainer.insertAdjacentHTML('afterbegin', notificationElement);
@ -168,7 +169,7 @@
Toast.fire({ Toast.fire({
icon: 'info', icon: 'info',
html:`${data.message}` html: `${data.message}`
}); });
} catch (error) { } catch (error) {
@ -220,7 +221,7 @@
</div> </div>
</div> </div>
</div> </div>
`; `;
} }
function updateCounter(action) { function updateCounter(action) {
@ -231,12 +232,14 @@
if (notificationCountDiv) { if (notificationCountDiv) {
notificationCountDiv.innerHTML = ` notificationCountDiv.innerHTML = `
<span class="badge bg-danger rounded-pill" id="notification-counter" style="position: absolute; top: 8px; right: 3px; font-size: 0.50rem;">0</span> <span class="badge bg-danger rounded-pill" id="notification-counter" style="position: absolute; top: 8px; right: 3px; font-size: 0.50rem;">0</span>
`; `;
counter = document.getElementById('notification-counter'); counter = document.getElementById('notification-counter');
} }
} }
} }
if (!counter) return;
let currentCount = parseInt(counter.textContent) || 0; let currentCount = parseInt(counter.textContent) || 0;
if (action === 'increment') { if (action === 'increment') {
@ -294,11 +297,11 @@
notificationCard.closest('.notification-card').classList.add('fade-out'); notificationCard.closest('.notification-card').classList.add('fade-out');
setTimeout(() => { setTimeout(() => {
notificationCard.closest('.notification-card').remove(); notificationCard.closest('.notification-card').remove();
}, 200); }, 1000);
} }
} }
}); });
} }
}); });
}); });
</script> </script>

View File

@ -125,7 +125,7 @@
</div> </div>
</div> </div>
<tr class="hover-actions-trigger btn-reveal-trigger position-static"> <tr class="hover-actions-trigger btn-reveal-trigger position-static">
<td class="name align-middle white-space-nowrap ps-0 px-1"> <td class="name align-middle white-space-nowrap px-1">
<div class="d-flex align-items-center"> <div class="d-flex align-items-center">
<div> <div>
<a class="fs-8 fw-bold" <a class="fs-8 fw-bold"
@ -144,7 +144,7 @@
<td class="date align-middle white-space-nowrap text-body-tertiary text-opacity-85 ps-4 text-body-tertiary"> <td class="date align-middle white-space-nowrap text-body-tertiary text-opacity-85 ps-4 text-body-tertiary">
{{ org.created|date }} {{ org.created|date }}
</td> </td>
<td class="align-middle white-space-nowrap text-end pe-0 ps-4"> <td class="align-middle white-space-nowrap text-end pe-2 ps-4">
{% if perms.inventory.change_organization or perms.inventory.delete_organization %} {% if perms.inventory.change_organization or perms.inventory.delete_organization %}
<div class="btn-reveal-trigger position-static"> <div class="btn-reveal-trigger position-static">
<button class="btn btn-sm dropdown-toggle dropdown-caret-none transition-none btn-reveal fs-10" <button class="btn btn-sm dropdown-toggle dropdown-caret-none transition-none btn-reveal fs-10"

View File

@ -19,9 +19,9 @@
{% if po_model.po_status == 'fulfilled' %} {% if po_model.po_status == 'fulfilled' %}
<div class="ms-2"> <div class="ms-2">
<a class="btn btn-phoenix-primary my-2 mx-2" <a class="btn btn-phoenix-primary my-2 mx-2"
href="{{ request.path }}?format=pdf&lang=en"><i class="fa-solid fa-arrow-down me-1"></i>{% trans 'Download PO ENG' %}</a> href="{{ request.path }}?format=pdf&lang=en"><i class="fa-solid fa-arrow-down me-1"></i>{% trans 'Download PO EN' %}</a>
<a class="btn btn-phoenix-primary my-2" <a class="btn btn-phoenix-primary my-2"
href="{{ request.path }}?format=pdf&lang=ar"><i class="fa-solid fa-arrow-down me-1"></i>{% trans 'Download PO ARB' %}</a> href="{{ request.path }}?format=pdf&lang=ar"><i class="fa-solid fa-arrow-down me-1"></i>{% trans 'Download PO AR' %}</a>
</diV> </diV>
{% endif %} {% endif %}
</div> </div>

View File

@ -1,6 +1,6 @@
{% load tenhal_tag %} {% load tenhal_tag %}
{% load custom_filters %} {% load custom_filters %}
{% load i18n static custom_filters num2words_tags %}
<!DOCTYPE html> <!DOCTYPE html>
<html lang="ar" dir="rtl"> <html lang="ar" dir="rtl">
<head> <head>
@ -109,50 +109,94 @@
/* Footer Styles */ /* Footer Styles */
.document-footer { .document-footer {
position: relative;
bottom: 0;
left: 0;
width: 100%;
text-align: center; text-align: center;
font-size: 10px; font-size: 10px;
color: #888; color: #888;
border-top: 1px solid #ddd;
padding-top: 15px; padding-top: 15px;
margin-top: 30px; margin: 0 20mm;
} }
.footer-flex { .footer-flex {
display: flex; display: flex;
justify-content: space-between; justify-content: space-between;
align-items: center; align-items: center;
width: 100%; width: 100%;
} }
.footer-logo {
text-align: center;
}
.footer-logo img {
height: 20px;
width: 20px;
}
.footer-logo p {
font-size: 9px;
font-weight: bold;
}
.footer-powered p {
font-size: 11px;
}
.footer-powered span {
font-weight: lighter;
}
.footer-powered a {
color: #112e40;
text-decoration: none;
font-size: 9px;
}
</style> </style>
</head> </head>
<body> <body>
<div class="document-header"> <div class="document-header">
<div>
<h1>{{ dealer.name }}</h1>
<address>
العنوان&nbsp;:&nbsp;{{ dealer.address }}<br>
البريد الإلكتروني&nbsp;:&nbsp;{{ dealer.user.email }}<br>
الهاتف&nbsp;:&nbsp;{{ dealer.phone_number }}<br>
رقم السجل التجاري&nbsp;:&nbsp;{{ dealer.crn }} &nbsp;|&nbsp; رقم ضريبة القيمة المضافة&nbsp;:&nbsp;{{ dealer.vrn }}
</address>
</div>
<div>
<div class="dealer-logo ">
{% if dealer.logo %}
<img class="rounded-soft"
style="max-width:100px;
max-height:100px"
src="{{dealer.logo.url|default:'' }}"
alt="Dealer Logo" />
{% endif %}
</div>
</div>
<div> <div>
<h1>أمر شراء</h1> <h1>أمر شراء</h1>
<h2 style="font-size: 18px;">{{ po_model.po_number }}</h2> <h2 style="font-size: 18px;">{{ po_model.po_number }}</h2>
</div> </div>
<div>
<h1>{{ dealer.name }}</h1>
<address>
العنوان: {{ dealer.address }}<br>
البريد الإلكتروني: {{ dealer.user.email }}<br>
الهاتف: {{ dealer.phone_number }}<br>
رقم السجل التجاري: {{ dealer.crn }} &nbsp;|&nbsp; رقم ضريبة القيمة المضافة: {{ dealer.vrn }}
</address>
</div>
</div> </div>
<div class="document-details"> <div class="document-details">
<div class="section"> <div class="section">
<h2>التفاصيل:</h2> <h2>التفاصيل:</h2>
<p><span class="label">رقم أمر الشراء:</span> {{ po_model.po_number }}</p> <p><span class="label">رقم أمر الشراء&nbsp;:&nbsp;</span> {{ po_model.po_number }}</p>
<p><span class="label">تاريخ الإصدار:</span> {{ po_model.date_fulfilled|date:"Y/m/d" }}</p> <p><span class="label">تاريخ الإصدار&nbsp;:&nbsp;</span> {{ po_model.date_fulfilled|date:"Y/m/d" }}</p>
</div> </div>
<div class="section"> <div class="section">
<h2>يُرسل إلى:</h2> <h2>يُرسل إلى:</h2>
<p><span class="label">المورد:</span> {{ vendor.vendor_name }}</p> <p><span class="label">المورد&nbsp;:&nbsp;</span> {{ vendor.vendor_name }}</p>
<p><span class="label">البريد الإلكتروني:</span> {{ vendor.email }}</p> <p><span class="label">البريد الإلكتروني&nbsp;:&nbsp;</span> {{ vendor.email }}</p>
<p><span class="label">الهاتف:</span> {{ vendor.phone }}</p> <p><span class="label">الهاتف&nbsp;:&nbsp;</span> {{ vendor.phone }}</p>
<p><span class="label">العنوان:</span> {{ vendor.address_1 }}</p> <p><span class="label">العنوان&nbsp;:&nbsp;</span> {{ vendor.address_1 }}</p>
</div> </div>
</div> </div>
<div class="col-lg-12"> <div class="col-lg-12">
@ -170,13 +214,24 @@
<div class="document-details" style="margin-top: 10px; border-top: 1px solid #ddd; padding-top: 10px;"> <div class="document-details" style="margin-top: 10px; border-top: 1px solid #ddd; padding-top: 10px;">
<div class="section text-right" style="width: 100%;"> <div class="section text-right" style="width: 100%;">
<p class="h4"><span class="label">المبلغ الإجمالي:</span> {{ po_total_amount|floatformat:'2g' }}<span class="icon-saudi_riyal"></span></p> <p class="h4"><span class="label">المبلغ الإجمالي&nbsp;:&nbsp;</span> {{ po_total_amount|currency_format }}<span class="icon-saudi_riyal"></span></p>
</div>
</div>
<hr style="border-bottom:1px solid #ccc; ">
<div class="document-footer">
<div class="footer-logo">
<img src="{% static 'images/logos/logo-d-pdf.png' %}" alt="Logo" />
<p>
<span>Haikal</span>&nbsp;|&nbsp;<span>هيكل</span>
</p>
</div>
<div class="footer-powered">
<p>
<span>Powered&nbsp;by&nbsp;</span>
<a href="https://tenhal.sa"><span>TENHAL</span>&nbsp;|&nbsp;<span>تنحل</span></a>
</p>
</div> </div>
</div> </div>
<div class="document-footer footer-flex">
<p>&copy;&nbsp;<strong>هيكل</strong>&nbsp;{% now "Y" %}&nbsp;جميع الحقوق محفوظة.</p>
<p><strong>تنحل</strong>مدعوم من</p>
</div>
</body> </body>
</html> </html>

View File

@ -103,22 +103,49 @@
text-align: right; text-align: right;
} }
/* Footer Styles */ /* Footer Styles */
.document-footer { .document-footer {
position: relative;
bottom: 0;
left: 0;
width: 100%;
text-align: center; text-align: center;
font-size: 10px; font-size: 10px;
color: #888; color: #888;
border-top: 1px solid #ddd;
padding-top: 15px; padding-top: 15px;
margin-top: 30px; margin: 0 20mm;
}
.footer-flex {
display: flex;
justify-content: space-between;
align-items: center;
width: 100%;
}
.footer-logo {
text-align: center;
}
.footer-logo img {
height: 20px;
width: 20px;
}
.footer-logo p {
font-size: 9px;
font-weight: bold;
}
.footer-powered p {
font-size: 11px;
}
.footer-powered span {
font-weight: lighter;
}
.footer-powered a {
color: #112e40;
text-decoration: none;
font-size: 9px;
} }
.footer-flex {
display: flex;
justify-content: space-between;
align-items: center;
width: 100%;
}
</style> </style>
</head> </head>
<body> <body>
@ -126,12 +153,25 @@
<div> <div>
<h1>{{ dealer.name }}</h1> <h1>{{ dealer.name }}</h1>
<address> <address>
Address: {{ dealer.address}}<br> Address&nbsp;:&nbsp;{{ dealer.address}}<br>
Email: {{ dealer.user.email }}<br> Email&nbsp;:&nbsp;{{ dealer.user.email }}<br>
Phone: {{dealer.phone_number }}<br> Phone&nbsp;:&nbsp;{{dealer.phone_number }}<br>
CRN: {{dealer.crn}}&nbsp;&nbsp;|&nbsp;VRN: {{dealer.vrn}} CRN&nbsp;:&nbsp;{{dealer.crn}}&nbsp;&nbsp;|&nbsp;VRN&nbsp;:&nbsp;{{dealer.vrn}}
</address> </address>
</div> </div>
<div>
<div class="dealer-logo ">
{% if dealer.logo %}
<img class="rounded-soft"
style="max-width:100px;
max-height:100px"
src="{{dealer.logo.url|default:'' }}"
alt="Dealer Logo" />
{% endif %}
</div>
</div>
<div> <div>
<h1>PURCHASE ORDER</h1> <h1>PURCHASE ORDER</h1>
<h2 style="font-size: 18px;">{{ po_model.po_number }}</h2> <h2 style="font-size: 18px;">{{ po_model.po_number }}</h2>
@ -141,15 +181,15 @@
<div class="document-details"> <div class="document-details">
<div class="section"> <div class="section">
<h2>BILL TO:</h2> <h2>BILL TO:</h2>
<p><span class="label">Vendor: {{vendor.vendor_name}}</span> </p> <p><span class="label">Vendor&nbsp;:&nbsp;{{vendor.vendor_name}}</span> </p>
<p><span class="label">Email: {{vendor.email}}</span> </p> <p><span class="label">Email&nbsp;:&nbsp;{{vendor.email}}</span> </p>
<p><span class="label">Phone: {{vendor.phone}}</span> </p> <p><span class="label">Phone&nbsp;:&nbsp;{{vendor.phone}}</span> </p>
<p><span class="label">Address: {{vendor.address_1}}</span> </p> <p><span class="label">Address&nbsp;:&nbsp;{{vendor.address_1}}</span> </p>
</div> </div>
<div class="section"> <div class="section">
<h2>DETAILS:</h2> <h2>DETAILS:</h2>
<p><span class="label">PO Number:</span> {{ po_model.po_number }}</p> <p><span class="label">PO Number&nbsp;:&nbsp;</span> {{ po_model.po_number }}</p>
<p><span class="label">Issue Date:</span> {{ po_model.date_fulfilled|date:"F j, Y" }}</p> <p><span class="label">Issue Date&nbsp;:&nbsp;</span> {{ po_model.date_fulfilled|date:"F j, Y" }}</p>
</div> </div>
</div> </div>
<div class="col-lg-12"> <div class="col-lg-12">
@ -161,14 +201,25 @@
<div class="document-details" style="margin-top: 30px;"> <div class="document-details" style="margin-top: 30px;">
<div class="section text-right"> <div class="section text-right">
<p class="h4"><span class="label">Total Amount:</span> {{ po_total_amount|floatformat:'2g' }}<span class="icon-saudi_riyal"></span></p> <p class="h4"><span class="label">Total Amount&nbsp;:&nbsp;</span> {{ po_total_amount|currency_format }}<span class="icon-saudi_riyal"></span></p>
</div> </div>
</div> </div>
<hr style="border-bottom:1px solid #ccc; ">
<div class="document-footer footer-flex"> <div class="document-footer">
<p>&copy;&nbsp;{% now "Y" %}&nbsp;All rights reserved&nbsp;<strong>Haikal</strong>&nbsp;</p> <div class="footer-logo">
<p>Powered By&nbsp;<strong>Tenhal</strong></p> <img src="{% static 'images/logos/logo-d-pdf.png' %}" alt="Logo" />
<p>
<span>Haikal</span>&nbsp;|&nbsp;<span>هيكل</span>
</p>
</div>
<div class="footer-powered">
<p>
<span>Powered&nbsp;by&nbsp;</span>
<a href="https://tenhal.sa"><span>TENHAL</span>&nbsp;|&nbsp;<span>تنحل</span></a>
</p>
</div>
</div> </div>
</body> </body>
</html> </html>

View File

@ -6,7 +6,7 @@
<tr class="has-text-centered bg-body-highlight"> <tr class="has-text-centered bg-body-highlight">
<th>البند</th> <th>البند</th>
<th>سعر الوحدة</th> <th>سعر الوحدة</th>
<th>كمية أمر الشراء</th> <th>الكمية</th>
<th>المبلغ</th> <th>المبلغ</th>
</tr> </tr>
</thead> </thead>
@ -14,7 +14,7 @@
{% for item in po_items_list %} {% for item in po_items_list %}
<tr> <tr>
<td>{{ item.item_model }}</td> <td>{{ item.item_model }}</td>
<td class="has-text-centered">{{ item.po_unit_cost }}</td> <td class="has-text-centered">{{ item.po_unit_cost|currency_format }}</td>
<td class="has-text-centered">{{ item.po_quantity }}</td> <td class="has-text-centered">{{ item.po_quantity }}</td>
<td class="has-text-centered"> <td class="has-text-centered">
<span class="icon-saudi_riyal"></span>{{ item.po_total_amount | currency_format }} <span class="icon-saudi_riyal"></span>{{ item.po_total_amount | currency_format }}
@ -26,12 +26,12 @@
<tfoot> <tfoot>
<tr> <tr>
<td>إجمالي مبلغ أمر الشراء</td>
<td></td> <td></td>
<td class="has-text-left">إجمالي مبلغ أمر الشراء</td> <td class="has-text-left">{{po_quantity}}</td>
<td class="has-text-weight-bold has-text-centered"> <td class="has-text-weight-bold has-text-centered">
<span class="icon-saudi_riyal"></span>{{ po_model.po_amount | currency_format }} <span class="icon-saudi_riyal"></span>{{ po_model.po_amount | currency_format }}
</td> </td>
<td></td>
</tr> </tr>
</tfoot> </tfoot>

View File

@ -14,7 +14,7 @@
{% for item in po_items_list %} {% for item in po_items_list %}
<tr> <tr>
<td>{{ item.item_model }}</td> <td>{{ item.item_model }}</td>
<td class="has-text-centered">{{ item.po_unit_cost }}</td> <td class="has-text-centered">{{ item.po_unit_cost|currency_format}}</td>
<td class="has-text-centered">{{ item.po_quantity }}</td> <td class="has-text-centered">{{ item.po_quantity }}</td>
<td class="has-text-centered"> <td class="has-text-centered">
<span class="icon-saudi_riyal"></span>{{ item.po_total_amount | currency_format }} <span class="icon-saudi_riyal"></span>{{ item.po_total_amount | currency_format }}
@ -26,13 +26,15 @@
<tfoot> <tfoot>
<tr> <tr>
<td></td> <td>Total PO Amount</td>
<td class="has-text-right">Total PO Amount</td> <td class="has-text-right"></td>
<td>{{po_quantity}}</td>
<td class="has-text-weight-bold has-text-centered"> <td class="has-text-weight-bold has-text-centered">
<span class="icon-saudi_riyal"></span>{{ po_model.po_amount | currency_format }} <span class="icon-saudi_riyal"></span>{{ po_model.po_amount | currency_format }}
</td> </td>
<td></td>
</tr> </tr>
</tfoot> </tfoot>
</table> </table>

View File

@ -134,11 +134,17 @@
{% if perms.django_ledger.change_estimatemodel %} {% if perms.django_ledger.change_estimatemodel %}
<a href="{% url 'send_email' request.dealer.slug estimate.pk %}" <a href="{% url 'send_email' request.dealer.slug estimate.pk %}"
class="btn btn-phoenix-primary me-2"><span class="fa-regular fa-paper-plane me-sm-2"></span><span class="d-none d-sm-inline-block">{% trans 'Send Quotation' %}</span></a> class="btn btn-phoenix-primary me-2"><span class="fa-regular fa-paper-plane me-sm-2"></span><span class="d-none d-sm-inline-block">{% trans 'Send Quotation' %}</span></a>
<a href="{% url 'estimate_print' request.dealer.slug estimate.pk %}" <a href="{% url 'estimate_print' request.dealer.slug estimate.pk %}?lang=en"
class="btn btn-phoenix-secondary" class="btn btn-phoenix-secondary"
target="_blank"> target="_blank">
<span class="d-none d-sm-inline-block"><i class="fas fa-print me-2"></i>{% trans 'Print' %}</span> <span class="d-none d-sm-inline-block"><i class="fas fa-print me-2"></i>{% trans 'Print EN' %}</span>
</a> </a>
<a href="{% url 'estimate_print' request.dealer.slug estimate.pk %}?lang=ar"
class="btn btn-phoenix-secondary"
target="_blank">
<span class="d-none d-sm-inline-block"><i class="fas fa-print me-2"></i>{% trans 'Print AR' %}</span>
</a>
{% endif %} {% endif %}
{% if estimate.sale_orders.first %} {% if estimate.sale_orders.first %}
<!--if sale order exist--> <!--if sale order exist-->

View File

@ -1,264 +0,0 @@
{% load i18n static custom_filters num2words_tags %}
<!DOCTYPE html>
<html lang="ar" dir="rtl">
<head>
<meta charset="UTF-8">
<title>{% trans "Quotation" %}</title>
<style>
/* General Body and Font Styles */
body {
font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, "Helvetica Neue", Arial, sans-serif;
font-size: 12px;
color: #333;
margin: 0;
padding: 0;
}
/* Page Layout and Margins for PDF */
@page {
size: A4;
margin: 20mm;
@top-left {
content: "صفحة " counter(page) " من " counter(pages);
font-size: 10px;
color: #555;
}
}
/* Header Styles */
.document-header {
display: flex;
justify-content: space-between;
align-items: center;
border-bottom: 2px solid #333;
padding-bottom: 15px;
margin-bottom: 20px;
}
.document-header .logo {
max-width: 150px;
height: auto;
}
.document-header h1 {
font-size: 24px;
margin: 0;
color: #0056b3; /* A professional blue */
}
.document-header address {
text-align: right;
font-style: normal;
font-size: 10px;
}
/* Document Details Section */
.document-details {
display: flex;
justify-content: space-between;
margin-bottom: 30px;
line-height: 1.6;
}
.document-details .section {
width: 48%;
}
.document-details h2 {
font-size: 14px;
border-bottom: 1px solid #ccc;
padding-bottom: 5px;
margin-bottom: 10px;
color: #555;
}
.document-details p {
margin: 0;
font-size: 12px;
}
.document-details .label {
font-weight: bold;
color: #555;
}
/* Table Styles */
.table {
width: 100%;
border-collapse: collapse;
margin-top: 20px;
}
.table th, .table td {
border: 1px solid #ddd;
padding: 8px;
text-align: right;
}
.table th {
background-color: #f2f2f2;
font-weight: bold;
}
.table tfoot td {
border-top: 2px solid #333;
font-weight: bold;
}
.text-right {
text-align: left;
}
.text-left {
text-align: right;
}
.text-center {
text-align: center;
}
/* Footer Styles */
.document-footer {
text-align: center;
font-size: 10px;
color: #888;
border-top: 1px solid #ddd;
padding-top: 15px;
margin-top: 30px;
}
.footer-flex {
display: flex;
justify-content: space-between;
align-items: center;
width: 100%;
}
</style>
</head>
<body>
<div class="document-header">
<div>
<h1>{{ request.dealer.name }}</h1>
<address>
العنوان: {{ request.dealer.address }}<br>
البريد الإلكتروني: {{ request.dealer.user.email }}<br>
الهاتف: {{ request.dealer.phone_number }}<br>
رقم السجل التجاري: {{ request.dealer.crn }}&nbsp;&nbsp;|&nbsp;الرقم الضريبي: {{ request.dealer.vrn }}
</address>
</div>
<div>
<h1>عرض سعر</h1>
<h2 style="font-size: 18px;">{{ estimate.estimate_number }}</h2>
</div>
</div>
<div class="document-details">
<div class="section">
<h2>{{ estimate.customer.customer_name }}: إلى</h2>
<p><span class="label">العميل: {{ estimate.customer.customer_name }}</span></p>
<p><span class="label">البريد الإلكتروني: {{ estimate.customer.email |default:"N/A"}}</span></p>
<p><span class="label">الهاتف: {{ estimate.customer.phone_number|default:"N/A" }}</span></p>
<p><span class="label">العنوان: {{ estimate.customer.address_1|default:"N/A" }}</span></p>
</div>
<div class="section text-left">
<h2>التفاصيل:</h2>
<p><span class="label">رقم عرض السعر:</span> {{ estimate.estimate_number }}</p>
<p><span class="label">تاريخ الإصدار:</span> {{ estimate.date_approved|date:"Y/m/d" }}</p>
<p><span class="label">طريقة الدفع:</span> {{ estimate.get_terms_display }}</p>
</div>
</div>
<div class="col-lg-12">
<div class="table-responsive">
<div class="d-flex justify-content-between">
<span class="fs-9 fw-thin">تفاصيل السيارة</span>
</div>
<table class="table table-sm table-bordered m-1">
<thead>
<tr>
<th class="text-wrap text-center align-content-center">
<span class="fs-10">الصانع</span>
</th>
<th class="text-wrap text-center align-content-center">
<span class="fs-10">الموديل</span>
</th>
<th class="text-wrap text-center align-content-center">
<span class="fs-10">السلسلة</span>
</th>
<th class="text-wrap text-center align-content-center">
<span class="fs-10">الفئة</span>
</th>
<th class="text-wrap text-center align-content-center">
<span class="fs-10">السنة</span>
</th>
<th class="text-wrap text-center align-content-center">
<span class="fs-10">رقم الهيكل</span>
</th>
<th class="text-wrap text-center align-content-center">
<span class="fs-10">الكمية</span>
</th>
<th class="text-wrap text-center align-content-center">
<span class="fs-10">سعر الوحدة</span>
</th>
<th class="text-wrap text-center align-content-center">
<span class="fs-10">الخصم</span>
</th>
<th class="text-wrap text-center align-content-center">
<span class="fs-10">الضريبة</span>
</th>
<th class="text-wrap text-center align-content-center">
<span class="fs-10">الإجمالي</span>
</th>
</tr>
</thead>
<tbody>
<tr>
<td class="ps-1 fs-10 align-content-center">{{ data.car.id_car_make.name }}</td>
<td class="ps-1 fs-10 align-content-center">{{ data.car.id_car_model.name }}</td>
<td class="ps-1 fs-10 align-content-center">{{ data.car.id_car_serie.name }}</td>
<td class="ps-1 fs-10 align-content-center">{{ data.car.id_car_trim.name }}</td>
<td class="text-center fs-10 align-content-center">{{ data.car.year }}</td>
<td class="ps-1 fs-10 align-content-center">{{ data.car.vin }}</td>
<td class="text-center fs-10 align-content-center">1</td>
<td class="text-center fs-10 align-content-center">{{ data.car.marked_price|floatformat:2 }}</td>
<td class="text-center fs-10 align-content-center">{{ data.discount_amount|floatformat:2 }}</td>
<td class="text-center fs-10 align-content-center">{{ data.vat_amount|floatformat:2 }}</td>
<td class="text-center fs-10 align-content-center">{{ data.final_price|floatformat:2 }}</td>
</tr>
</tbody>
</table>
</div>
</div>
{% if data.additional_services %}
<div class="col-lg-12">
<div class="table-responsive">
<div class="d-flex justify-content-between">
<span class="fs-9 fw-thin">الخدمات الإضافية</span>
</div>
<table class="table table-sm table-bordered m-1">
<thead>
<tr>
<th class="text-center fs-10 align-content-center">النوع</th>
<th class="text-center fs-10 align-content-center">القيمة</th>
<th class="text-center fs-10 align-content-center">ضريبة الخدمة</th>
<th class="text-center fs-10 align-content-center">الإجمالي</th>
</tr>
</thead>
<tbody>
{% for service in data.additional_services.services %}
<tr>
<td class="ps-1 text-center fs-10 align-content-center">{{ service.0.name }}</td>
<td class="ps-1 text-center fs-10 align-content-center">{{ service.0.price|floatformat:2 }}</td>
<td class="ps-1 text-center fs-10 align-content-center">{{ service.1|floatformat:2 }}</td>
<td class="ps-1 text-center fs-10 align-content-center">{{ service.0.price_|floatformat:2 }}</td>
</tr>
{% endfor %}
</tbody>
</table>
</div>
</div>
{% endif %}
<div class="document-details" style="margin-top: 30px;">
<div class="section text-left">
<p><span class="label">إجمالي ضريبة القيمة المضافة:</span> {{ data.total_vat|floatformat:'2g' }}</p>
<p><span class="label">الإجمالي الكلي:</span> {{ data.grand_total|floatformat:'2g' }}</p>
<p><span class="label">كتابةً:</span> {{ data.grand_total|num_to_words }}</p>
</div>
</div>
<div class="document-footer footer-flex">
<p>&copy;&nbsp;{% now "Y" %}&nbsp;جميع الحقوق محفوظة&nbsp;<strong>هيكل</strong></p>
<p>بواسطة&nbsp;<strong>تنحل</strong></p>
</div>
</body>
</html>

View File

@ -0,0 +1,357 @@
{% load i18n static custom_filters num2words_tags %}
<!DOCTYPE html>
<html lang="ar" dir="rtl">
<head>
<meta charset="UTF-8">
<title>عرض سعر</title>
<style>
/* General Body and Font Styles */
body {
font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, "Helvetica Neue", Arial, sans-serif;
font-size: 12px;
color: #333;
margin: 0;
padding: 0;
position: relative;
}
/* Page Layout and Margins for PDF */
@page {
size: A4;
margin: 20mm;
@top-left {
content: "صفحة " counter(page) " من " counter(pages);
font-size: 10px;
color: #555;
}
}
/* Header Styles */
.document-header {
display: flex;
justify-content: space-between;
align-items: center;
border-bottom: 2px solid #333;
padding-bottom: 15px;
margin-bottom: 20px;
}
.document-header .logo {
max-width: 150px;
height: auto;
}
.document-header h1 {
font-size: 24px;
margin: 0;
color: #0056b3;
}
.document-header address {
text-align: right;
font-style: normal;
font-size: 10px;
}
/* Document Details Section */
.document-details {
display: flex;
justify-content: space-between;
margin-bottom: 30px;
line-height: 1.6;
}
.document-details .section {
width: 48%;
}
.document-details h2 {
font-size: 14px;
border-bottom: 1px solid #ccc;
padding-bottom: 5px;
margin-bottom: 10px;
color: #555;
}
.document-details p {
margin: 0;
font-size: 12px;
}
.document-details .label {
font-weight: bold;
color: #555;
}
/* Table Styles */
.table {
width: 80%;
border-collapse: collapse;
margin: 20px auto; /* Centering the table */
}
.table th, .table td {
border: 1px solid #ddd;
padding: 8px;
text-align: right;
}
.table th {
background-color: #f2f2f2;
font-weight: bold;
}
.table tfoot td {
border-top: 2px solid #333;
font-weight: bold;
}
.text-right {
text-align: left;
}
.text-left {
text-align: right;
}
.text-center {
text-align: center;2px solid #333;
}
/* Responsive and layout helpers */
.table-container {
width: 100%;
overflow-x: auto;
margin-bottom: 20px;
}
.table-header {
display: flex;
justify-content: space-between;
align-items: center;
margin-bottom: 5px;
}
.table-header span {
font-size: 9px;
font-weight: lighter;
}
/* Footer Styles */
.document-footer {
position: relative;
bottom: 0;
left: 0;
width: 100%;
text-align: center;
font-size: 10px;
color: #888;
padding-top: 15px;
margin: 0 20mm;
}
.footer-flex {
display: flex;
justify-content: space-between;
align-items: center;
width: 100%;
}
.footer-logo {
text-align: center;
}
.footer-logo img {
height: 20px;
width: 20px;
}
.footer-logo p {
font-size: 9px;
font-weight: bold;
}
.footer-powered p {
font-size: 11px;
}
.footer-powered span {
font-weight: lighter;
}
.footer-powered a {
color: #112e40;
text-decoration: none;
font-size: 9px;
}
</style>
</head>
<body>
<div class="document-header">
<div>
<h1>{{ dealer_info.name }}</h1>
<address>
العنوان&nbsp;:&nbsp;{{ dealer_info.address }}<br>
البريد الإلكتروني&nbsp;:&nbsp;{{ dealer_info.user.email }}<br>
الهاتف&nbsp;:&nbsp;{{ dealer_info.phone_number }}<br>
رقم السجل التجاري&nbsp;:&nbsp;{{dealer_info.crn }}<br>
الرقم الضريبي&nbsp;:&nbsp;{{ dealer_info.vrn }}
</address>
</div>
<div>
<div class="dealer-logo ">
{% if dealer_info.logo %}
<img class="rounded-soft"
style="max-width:100px;
max-height:100px"
src="{{dealer_info.logo.url|default:'' }}"
alt="Dealer Logo" />
{% endif %}
</div>
</div>
<div>
<h1>عرض سعر</h1>
<h2 style="font-size: 18px;">{{ estimate.estimate_number }}</h2>
</div>
</div>
<div class="document-details">
<div class="section">
<h2>{{ customer_obj.full_name }}: إلى</h2>
<p><span class="label">العميل&nbsp;:&nbsp;{{ customer_obj.full_name }}</span></p>
<p><span class="label">البريد الإلكتروني&nbsp;:&nbsp;{{ customer_obj.email |default:"N/A"}}</span></p>
<p><span class="label">الهاتف&nbsp;:&nbsp;{{ customer_obj.phone_number|default:"N/A" }}</span></p>
<p><span class="label">العنوان&nbsp;:&nbsp;{{ customer_obj.address|default:"N/A" }}</span></p>
</div>
<div class="section text-left">
<h2>التفاصيل:</h2>
<p><span class="label">رقم عرض السعر&nbsp;:&nbsp;</span> {{estimate.estimate_number }}</p>
<p><span class="label">تاريخ الإصدار&nbsp;:&nbsp;</span> {{ estimate.date_approved|date:"Y/m/d" }}</p>
<p><span class="label">طريقة الدفع&nbsp;:&nbsp;</span> {{ estimate.get_terms_display }}</p>
</div>
</div>
<div class="table-container">
<div class="table-header">
<span>تفاصيل السيارة</span>
</div>
<table class="table">
<thead>
<tr>
<th class="text-center">
<span>الصانع</span>
</th>
<th class="text-center">
<span>الموديل</span>
</th>
<th class="text-center">
<span>السلسلة</span>
</th>
<th class="text-center">
<span>الفئة</span>
</th>
<th class="text-center">
<span>السنة</span>
</th>
<th class="text-center">
<span>رقم الهيكل</span>
</th>
</tr>
</thead>
<tbody>
<tr>
<td class="text-center">{{ data.car.id_car_make.name }}</td>
<td class="text-center">{{ data.car.id_car_model.name }}</td>
<td class="text-center">{{ data.car.id_car_serie.name }}</td>
<td class="text-center">{{ data.car.id_car_trim.name }}</td>
<td class="text-center">{{ data.car.year }}</td>
<td class="text-center">{{ data.car.vin }}</td>
</tr>
</tbody>
</table>
</div>
<div class="table-container">
<div class="table-header">
<span>التفاصيل المالية</span>
</div>
<table class="table">
<thead>
<tr>
<th class="text-center">
<span>الكمية</span>
</th>
<th class="text-center">
<span>سعر الوحدة</span>
</th>
<th class="text-center">
<span>الخصم</span>
</th>
<th class="text-center">
<span>الضريبة</span>
</th>
<th class="text-center">
<span>الإجمالي</span>
</th>
</tr>
</thead>
<tbody>
<tr>
<td class="text-center">1</td>
<td class="text-center">{{ data.car.marked_price|currency_format}}</td>
<td class="text-center">{{ data.discount_amount|currency_format}}</td>
<td class="text-center">{{ data.vat_amount|currency_format }}</td>
<td class="text-center">{{ data.final_price|currency_format }}</td>
</tr>
</tbody>
</table>
</div>
{% if data.additional_services %}
<div class="table-container">
<div class="table-header">
<span>الخدمات الإضافية</span>
</div>
<table class="table">
<thead>
<tr>
<th class="text-center">النوع</th>
<th class="text-center">القيمة</th>
<th class="text-center">ضريبة الخدمة</th>
<th class="text-center">الإجمالي</th>
</tr>
</thead>
<tbody>
{% for service in data.additional_services.services %}
<tr>
<td class="text-center">{{ service.0.name }}</td>
<td class="text-center">{{ service.0.price|currency_format }}</td>
<td class="text-center">{{ service.1|currency_format}}</td>
<td class="text-center">{{ service.0.price_|currency_format}}</td>
</tr>
{% endfor %}
</tbody>
<tfoot>
<tr>
<td class="text-center"></td>
<td class="text-center">{{ data.total_services_amount|currency_format}}</td>
<td class="text-center">{{ data.total_services_vat|currency_format}}</td>
<td class="text-center">{{ data.total_services_amount_|currency_format}}</td>
</tr>
</tfoot>
</table>
</div>
{% endif %}
<div class="document-details" style="margin-top: 30px; ">
<div class="section text-left">
<p><span class="label">إجمالي ضريبة القيمة المضافة&nbsp;:&nbsp;</span> {{ data.total_vat|currency_format }}</p>
<p><span class="label">الإجمالي الكلي&nbsp;:&nbsp;</span> {{ data.grand_total|currency_format}}</p>
<p><span class="label">كتابةً&nbsp;:&nbsp;</span> {{ data.grand_total|num_to_words }}</p>
</div>
</div>
<hr style="border-bottom:1px solid #ccc; ">
<div class="document-footer">
<div class="footer-logo">
<img src="{% static 'images/logos/logo-d-pdf.png' %}" alt="Logo" />
<p>
<span>Haikal</span>&nbsp;|&nbsp;<span>هيكل</span>
</p>
</div>
<div class="footer-powered">
<p>
<span>Powered&nbsp;by&nbsp;</span>
<a href="https://tenhal.sa"><span>TENHAL</span>&nbsp;|&nbsp;<span>تنحل</span></a>
</p>
</div>
</div>
</body>
</html>

View File

@ -0,0 +1,357 @@
{% load static custom_filters num2words_tags %}
<!DOCTYPE html>
<html lang="en" dir="ltr">
<head>
<meta charset="UTF-8">
<title>Quotation</title>
<style>
/* General Body and Font Styles */
body {
font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, "Helvetica Neue", Arial, sans-serif;
font-size: 12px;
color: #333;
margin: 0;
padding: 0;
position: relative;
}
/* Page Layout and Margins for PDF */
@page {
size: A4;
margin: 20mm;
@top-left {
content: "صفحة " counter(page) " من " counter(pages);
font-size: 10px;
color: #555;
}
}
/* Header Styles */
.document-header {
display: flex;
justify-content: space-between;
align-items: center;
border-bottom: 2px solid #333;
padding-bottom: 15px;
margin-bottom: 20px;
}
.document-header .logo {
max-width: 150px;
height: auto;
}
.document-header h1 {
font-size: 24px;
margin: 0;
color: #0056b3;
}
.document-header address {
text-align: right;
font-style: normal;
font-size: 10px;
}
/* Document Details Section */
.document-details {
display: flex;
justify-content: space-between;
margin-bottom: 30px;
line-height: 1.6;
}
.document-details .section {
width: 48%;
}
.document-details h2 {
font-size: 14px;
border-bottom: 1px solid #ccc;
padding-bottom: 5px;
margin-bottom: 10px;
color: #555;
}
.document-details p {
margin: 0;
font-size: 12px;
}
.document-details .label {
font-weight: bold;
color: #555;
}
/* Table Styles */
.table {
width: 80%;
border-collapse: collapse;
margin: 20px auto; /* Centering the table */
}
.table th, .table td {
border: 1px solid #ddd;
padding: 8px;
text-align: right;
}
.table th {
background-color: #f2f2f2;
font-weight: bold;
}
.table tfoot td {
border-top: 2px solid #333;
font-weight: bold;
}
.text-right {
text-align: left;
}
.text-left {
text-align: right;
}
.text-center {
text-align: center;2px solid #333;
}
/* Responsive and layout helpers */
.table-container {
width: 100%;
overflow-x: auto;
margin-bottom: 20px;
}
.table-header {
display: flex;
justify-content: space-between;
align-items: center;
margin-bottom: 5px;
}
.table-header span {
font-size: 9px;
font-weight: lighter;
}
/* Footer Styles */
.document-footer {
position: relative;
bottom: 0;
left: 0;
width: 100%;
text-align: center;
font-size: 10px;
color: #888;
padding-top: 15px;
margin: 0 20mm;
}
.footer-flex {
display: flex;
justify-content: space-between;
align-items: center;
width: 100%;
}
.footer-logo {
text-align: center;
}
.footer-logo img {
height: 20px;
width: 20px;
}
.footer-logo p {
font-size: 9px;
font-weight: bold;
}
.footer-powered p {
font-size: 11px;
}
.footer-powered span {
font-weight: lighter;
}
.footer-powered a {
color: #112e40;
text-decoration: none;
font-size: 9px;
}
</style>
</head>
<body>
<div class="document-header">
<div>
<h1>{{ dealer_info.name }}</h1>
<address>
Address&nbsp;:&nbsp;{{ dealer_info.address }}<br>
Email&nbsp;:&nbsp;{{ dealer_info.user.email }}<br>
Phone&nbsp;:&nbsp;{{ dealer_info.phone_number }}<br>
Commercial Registration No.&nbsp;:&nbsp;{{dealer_info.crn }}<br>
VAT No.&nbsp;:&nbsp;{{ dealer_info.vrn }}
</address>
</div>
<div>
<div class="dealer-logo ">
{% if dealer_info.logo %}
<img class="rounded-soft"
style="max-width:100px;
max-height:100px"
src="{{dealer_info.logo.url|default:'' }}"
alt="Dealer Logo" />
{% endif %}
</div>
</div>
<div>
<h1>Quotation</h1>
<h2 style="font-size: 18px;">{{ estimate.estimate_number }}</h2>
</div>
</div>
<div class="document-details">
<div class="section">
<h2>To: {{ customer_obj.full_name }}</h2>
<p><span class="label">Customer&nbsp;:&nbsp;{{ customer_obj.full_name }}</span></p>
<p><span class="label">Email&nbsp;:&nbsp;{{ customer_obj.email |default:"N/A"}}</span></p>
<p><span class="label">Phone&nbsp;:&nbsp;{{ customer_obj.phone_number|default:"N/A" }}</span></p>
<p><span class="label">Address&nbsp;:&nbsp;{{ customer_obj.address|default:"N/A" }}</span></p>
</div>
<div class="section text-right">
<h2>Details:</h2>
<p><span class="label">Quotation Number&nbsp;:&nbsp;</span> {{ estimate.estimate_number }}</p>
<p><span class="label">Issue Date&nbsp;:&nbsp;</span> {{ estimate.date_approved|date:"Y/m/d" }}</p>
<p><span class="label">Payment Method&nbsp;:&nbsp;</span> {{ estimate.get_terms_display }}</p>
</div>
</div>
<div class="table-container">
<div class="table-header">
<span>Car Details</span>
</div>
<table class="table">
<thead>
<tr>
<th class="text-center">
<span>Make</span>
</th>
<th class="text-center">
<span>Model</span>
</th>
<th class="text-center">
<span>Series</span>
</th>
<th class="text-center">
<span>Trim</span>
</th>
<th class="text-center">
<span>Year</span>
</th>
<th class="text-center">
<span>VIN</span>
</th>
</tr>
</thead>
<tbody>
<tr>
<td class="text-center">{{ data.car.id_car_make.name }}</td>
<td class="text-center">{{ data.car.id_car_model.name }}</td>
<td class="text-center">{{ data.car.id_car_serie.name }}</td>
<td class="text-center">{{ data.car.id_car_trim.name }}</td>
<td class="text-center">{{ data.car.year }}</td>
<td class="text-center">{{ data.car.vin }}</td>
</tr>
</tbody>
</table>
</div>
<div class="table-container">
<div class="table-header">
<span>Financial Details</span>
</div>
<table class="table">
<thead>
<tr>
<th class="text-center">
<span>Quantity</span>
</th>
<th class="text-center">
<span>Unit Price</span>
</th>
<th class="text-center">
<span>Discount</span>
</th>
<th class="text-center">
<span>VAT</span>
</th>
<th class="text-center">
<span>Total</span>
</th>
</tr>
</thead>
<tbody>
<tr>
<td class="text-center">1</td>
<td class="text-center">{{ data.car.marked_price|currency_format}}</td>
<td class="text-center">{{ data.discount_amount|currency_format }}</td>
<td class="text-center">{{ data.vat_amount|currency_format}}</td>
<td class="text-center">{{ data.final_price|currency_format}}</td>
</tr>
</tbody>
</table>
</div>
{% if data.additional_services %}
<div class="table-container">
<div class="table-header">
<span>Additional Services</span>
</div>
<table class="table">
<thead>
<tr>
<th class="text-center">Type</th>
<th class="text-center">Value</th>
<th class="text-center">Service VAT</th>
<th class="text-center">Total</th>
</tr>
</thead>
<tbody>
{% for service in data.additional_services.services %}
<tr>
<td class="text-center">{{ service.0.name }}</td>
<td class="text-center">{{ service.0.price|currency_format }}</td>
<td class="text-center">{{ service.1|currency_format}}</td>
<td class="text-center">{{ service.0.price_|currency_format}}</td>
</tr>
{% endfor %}
</tbody>
<tfoot>
<tr>
<td class="text-center"></td>
<td class="text-center">{{ data.total_services_amount|currency_format}}</td>
<td class="text-center">{{ data.total_services_vat|currency_format}}</td>
<td class="text-center">{{ data.total_services_amount_|currency_format }}</td>
</tr>
</tfoot>
</table>
</div>
{% endif %}
<div class="document-details" style="margin-top: 30px; ">
<div class="section text-right">
<p><span class="label">Total VAT&nbsp;:&nbsp;</span> {{ data.total_vat|currency_format}}</p>
<p><span class="label">Grand Total&nbsp;:&nbsp;</span> {{ data.grand_total|currency_format }}</p>
<p><span class="label">In words&nbsp;:&nbsp;</span> {{ data.grand_total|num_to_words }}</p>
</div>
</div>
<hr style="border-bottom:1px solid #ccc; ">
<div class="document-footer">
<div class="footer-logo">
<img src="{% static 'images/logos/logo-d-pdf.png' %}" alt="Logo" />
<p>
<span>Haikal</span>&nbsp;|&nbsp;<span>هيكل</span>
</p>
</div>
<div class="footer-powered">
<p>
<span>Powered&nbsp;by&nbsp;</span>
<a href="https://tenhal.sa"><span>TENHAL</span>&nbsp;|&nbsp;<span>تنحل</span></a>
</p>
</div>
</div>
</body>
</html>

View File

@ -144,6 +144,16 @@
{% endif %} {% endif %}
<a href="{% url 'invoice_preview' request.dealer.slug invoice.pk %}" <a href="{% url 'invoice_preview' request.dealer.slug invoice.pk %}"
class="btn btn-phoenix-primary"><span class="d-none d-sm-inline-block"><i class="fa-regular fa-eye"></i> {% trans 'Preview' %}</span></a> class="btn btn-phoenix-primary"><span class="d-none d-sm-inline-block"><i class="fa-regular fa-eye"></i> {% trans 'Preview' %}</span></a>
<a href="{% url 'invoice_preview' request.dealer.slug invoice.pk %}?lang=en"
class="btn btn-phoenix-secondary"
target="_blank">
<span class="d-none d-sm-inline-block"><i class="fas fa-print me-2"></i>{% trans 'Print EN' %}</span>
</a>
<a href="{% url 'invoice_preview' request.dealer.slug invoice.pk %}?lang=ar"
class="btn btn-phoenix-secondary"
target="_blank">
<span class="d-none d-sm-inline-block"><i class="fas fa-print me-2"></i>{% trans 'Print AR' %}</span>
</a>
</div> </div>
</div> </div>
{{ invoice.amount_owned }} {{ invoice.amount_owned }}

View File

@ -14,9 +14,9 @@
</h2> </h2>
</div> </div>
</div> </div>
<div class="col-auto"> {% comment %} <div class="col-auto">
<div class="d-flex">{% include 'partials/search_box.html' %}</div> <div class="d-flex">{% include 'partials/search_box.html' %}</div>
</div> </div> {% endcomment %}
</div> </div>
<div class="table-responsive px-1 scrollbar"> <div class="table-responsive px-1 scrollbar">
<table class="table align-items-center table-flush"> <table class="table align-items-center table-flush">

View File

@ -1,367 +1,333 @@
{% load i18n static custom_filters num2words_tags %} {% load i18n static custom_filters num2words_tags %}
<!DOCTYPE html> <!DOCTYPE html>
<html lang="ar" dir="rtl"> <html lang="ar">
<head> <head>
<meta charset="UTF-8"> <meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0"> <title>{% trans "Invoice Preview" %}</title>
<title>{% trans "Invoice" %}</title>
<link href="{% static 'css/theme.min.css' %}"
type="text/css"
rel="stylesheet"
id="style-default">
<link href="{% static 'css/user.min.css' %}"
type="text/css"
rel="stylesheet"
id="user-style-default">
<link href="{% static 'css/custom.css' %}" type="text/css" rel="stylesheet">
<link href="https://fonts.googleapis.com/css2?family=Roboto:wght@300;400;500;700&display=swap"
rel="stylesheet">
<style>
body {
font-family: 'Roboto', sans-serif;
margin: 0;
padding: 0;
background-color: #f8f9fa;
}
.invoice-container { <style>
width: 210mm; /* General Body and Font Styles */
min-height: 297mm; body {
padding: 10mm; font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, "Helvetica Neue", Arial, sans-serif;
margin: 10mm auto; font-size: 14px;
background: white; color: #333;
border-radius: 5px; background-color: #f7f7f7;
box-shadow: 0 0 5px rgba(0, 0, 0, 0.1); margin: 0;
display: flex; padding: 20px;
flex-direction: column; }
}
.invoice-content { /* Container for the document content */
flex-grow: 1; .document-container {
} max-width: 800px;
margin: 0 auto;
background-color: #fff;
padding: 40px;
box-shadow: 0 4px 10px rgba(0, 0, 0, 0.1);
border-radius: 8px;
}
.invoice-header { /* Header Styles */
text-align: center; .document-header {
border-bottom: 2px solid #dee2e6; display: flex;
padding-bottom: 10px; justify-content: space-between;
margin-bottom: 20px; align-items: center;
} border-bottom: 2px solid #333;
padding-bottom: 15px;
margin-bottom: 20px;
}
.document-header .logo {
max-width: 150px;
height: auto;
}
.document-header h1 {
font-size: 24px;
margin: 0;
color: #0056b3;
}
.document-header h2 {
font-size: 18px;
margin: 0;
}
.document-header address {
text-align: right;
font-style: normal;
font-size: 10px;
}
.qr-code { /* Document Details Section */
text-align: center; .document-details {
margin-top: 10px; display: flex;
} justify-content: space-between;
margin-bottom: 30px;
line-height: 1.6;
}
.document-details .section {
width: 48%;
}
.document-details h2 {
font-size: 14px;
border-bottom: 1px solid #ccc;
padding-bottom: 5px;
margin-bottom: 10px;
color: #555;
}
.document-details p {
margin: 0;
font-size: 12px;
}
.document-details .label {
font-weight: bold;
color: #555;
}
.qr-code img { /* Table Styles */
width: 3cm; .table {
height: 3cm; width: 100%;
border-radius: 0.3333333333rem; border-collapse: collapse;
} margin: 20px 0;
font-size: 12px;
}
.table th, .table td {
border: 1px solid #ddd;
padding: 8px;
text-align: right;
}
.table th {
background-color: #f2f2f2;
font-weight: bold;
}
.table tfoot td {
border-top: 2px solid #333;
font-weight: bold;
}
.text-right {
text-align: left;
}
.text-left {
text-align: right;
}
.text-center {
text-align: center;
}
.invoice-details, .invoice-table { /* Responsive and layout helpers */
font-size: 14px; .table-container {
} width: 100%;
overflow-x: auto;
margin-bottom: 20px;
}
.table-header {
display: flex;
justify-content: space-between;
align-items: center;
margin-bottom: 5px;
}
.table-header span {
font-size: 14px;
font-weight: normal;
}
.invoice-table th { /* Footer Styles */
background-color: #f8f9fa; .document-footer {
font-weight: 600; text-align: center;
} font-size: 10px;
color: #888;
.invoice-total { border-top: 1px solid #ddd;
text-align: right; padding-top: 15px;
font-size: 16px; margin-top: 30px;
font-weight: 600; }
margin-top: 10px; .footer-logo img {
} height: 20px;
width: 20px;
.footer-note { }
margin-top: auto; .footer-logo p {
padding-top: 10mm; font-size: 9px;
font-size: 13px; font-weight: bold;
display: flex; margin: 5px 0 0;
justify-content: space-between; }
align-items: center; .footer-powered p {
border-top: 2px solid #dee2e6; /* Add a top border to separate from content */ font-size: 11px;
} margin: 0;
}
.logo-img img { .footer-powered span {
width: 10mm; font-weight: lighter;
height: 10mm; }
} .footer-powered a {
</style> color: #112e40;
</head> text-decoration: none;
<body> font-size: 9px;
<div class="row p-2"> }
<div class="col-2"> </style>
<button class="btn btn-sm btn-phoenix-danger w-100" </head>
onclick="window.history.back()">الرجوع&nbsp;/&nbsp;Back</button> <body>
<div class="document-container">
<div class="document-header">
<div>
<h1>{{ dealer_info.name }}</h1>
<address>
العنوان&nbsp;:&nbsp;{{ dealer_info.address }}<br>
البريد الإلكتروني&nbsp;:&nbsp;{{ dealer_info.user.email }}<br>
الهاتف&nbsp;:&nbsp;{{ dealer_info.phone_number }}<br>
رقم السجل التجاري&nbsp;:&nbsp;{{dealer_info.crn }}&nbsp;&nbsp;|&nbsp;الرقم الضريبي&nbsp;:&nbsp;{{ dealer_info.vrn }}
</address>
</div> </div>
<div class="col-2"> <div>
<button class="btn btn-sm btn-phoenix-primary w-100" id="download-pdf">تحميل&nbsp;/&nbsp;Download</button> <h1>عرض سعر</h1>
<h2>{{ estimate.estimate_number }}</h2>
</div> </div>
<div class="col-8"></div>
</div> </div>
<div class="invoice-container" id="invoice-content">
<div class="invoice-content"> <div class="document-details">
<div class="invoice-header"> <div class="section">
<h5 class="fs-5"> <h2>{{ estimate.customer.customer_name }}: إلى</h2>
<span>Invoice</span>&nbsp;/&nbsp;<span>فاتورة</span> <p><span class="label">العميل&nbsp;:&nbsp;{{ customer_obj.full_name }}</span></p>
</h5> <p><span class="label">البريد الإلكتروني&nbsp;:&nbsp;{{ customer_obj.email |default:"N/A"}}</span></p>
</div> <p><span class="label">الهاتف&nbsp;:&nbsp;{{ customer_obj.phone_number|default:"N/A" }}</span></p>
<div class="invoice-details p-1"> <p><span class="label">العنوان&nbsp;:&nbsp;{{ customer_obj.address|default:"N/A" }}</span></p>
<div class="d-flex justify-content-around align-items-end">
<div class="d-flex justify-content-start align-items-center">
<div class="qr-code">
<img src="{% static 'qr_code/Marwan_qr.png' %}" alt="QR Code">
</div>
</div>
<div class="dealer-logo ">
{% if request.dealer.logo %}
<img class="rounded-soft"
style="max-width:150px;
max-height:150px"
src="{{ request.dealer.logo.url|default:'' }}"
alt="Dealer Logo" />
{% endif %}
</div>
</div>
<table class="table table-sm table-responsive border-gray-50">
<tr>
<td class="ps-1">
<strong>Dealership Name</strong>
</td>
<td class="text-center">{{ request.dealer.name }}</td>
<td class="text-end">
<strong>اسم الوكالة</strong>
</td>
</tr>
<tr>
<td class="ps-1">
<strong>Dealership Address</strong>
</td>
<td class="text-center">{{ request.dealer.address }}</td>
<td class="text-end">
<strong>عنوان الوكالة</strong>
</td>
</tr>
<tr>
<td class="ps-1">
<strong>Phone</strong>
</td>
<td class="text-center">{{ request.dealer.phone_number }}</td>
<td class="text-end">
<strong>جوال</strong>
</td>
</tr>
<tr>
<td>
<strong>VAT Number</strong>
</td>
<td class="text-center">{{ request.dealer.vrn }}</td>
<td class="text-end">
<strong>الرقم الضريبي</strong>
</td>
</tr>
</table>
<table class="table table-sm table-bordered border-gray-50 ">
<tr>
<td class="ps-1">
<strong>Invoice&nbsp;Number</strong>
</td>
<td class="text-center">{{ invoice.invoice_number }}</td>
<td class="text-end p-1">
<strong>رقم الفاتورة</strong>
</td>
</tr>
<tr>
<td class="ps-1">
<strong>Date</strong>
</td>
<td class="text-center">{{ invoice.date_approved| date:"Y/m/d" }}</td>
<td class="text-end p-1">
<strong>التاريخ</strong>
</td>
</tr>
<tr>
<td class="ps-1">
<strong>Customer Name</strong>
</td>
<td class="text-center">{{ invoice.customer.customer_name }}</td>
<td class="text-end p-1">
<strong>اسم العميل</strong>
</td>
</tr>
<tr>
<td class="ps-1">
<strong>Email</strong>
</td>
<td class="text-center">{{ invoice.customer.email |default:"N/A" }}</td>
<td class="text-end p-1">
<strong>البريد الإلكتروني</strong>
</td>
</tr>
<tr>
<td class="ps-1">
<strong>Terms</strong>
</td>
<td class="text-center">{{ invoice.get_terms_display }}</td>
<td class="text-end p-1">
<strong>شروط الدفع</strong>
</td>
</tr>
</table>
</div>
<div class="d-flex justify-content-between">
<span class="fs-9 fw-thin">Car Details</span>
<span class="fs-9 fw-thin">تفاصيل السيارة</span>
</div>
<div class="invoice-table p-1">
<table class="table table-sm table-bordered m-1">
<thead>
<tr>
<th class="text-wrap text-center align-content-center">
<span class="fs-10">Make</span> / <span class="fs-10">الصانع</span>
</th>
<th class="text-wrap text-center align-content-center">
<span class="fs-10">Model</span> / <span class="fs-10">الموديل</span>
</th>
<th class="text-wrap text-center align-content-center">
<span class="fs-10">Series</span> / <span class="fs-10">السلسلة</span>
</th>
<th class="text-wrap text-center align-content-center">
<span class="fs-10">Trim</span> / <span class="fs-10">الفئة</span>
</th>
<th class="text-wrap text-center align-content-center">
<span class="fs-10">Year</span> / <span class="fs-10">السنة</span>
</th>
<th class="text-wrap text-center align-content-center">
<span class="fs-10">VIN</span> / <span class="fs-10">رقم الهيكل</span>
</th>
<th class="text-wrap text-center align-content-center">
<span class="fs-10">Quantity</span> / <span class="fs-10">الكمية</span>
</th>
<th class="text-wrap text-center align-content-center">
<span class="fs-10">Unit Price</span> / <span class="fs-10">سعر الوحدة</span>
</th>
<th class="text-wrap text-center align-content-center">
<span class="fs-10">Discount</span> / <span class="fs-10">الخصم</span>
</th>
<th class="text-wrap text-center align-content-center">
<span class="fs-10">VAT</span> / <span class="fs-10">الضريبة</span>
</th>
<th class="text-wrap text-center align-content-center">
<span class="fs-10">Total</span> / <span class="fs-10">الإجمالي</span>
</th>
</tr>
</thead>
<tbody>
<tr>
<td class="ps-1 fs-10 align-content-center">{{ data.car.id_car_make.name }}</td>
<td class="ps-1 fs-10 align-content-center">{{ data.car.id_car_model.name }}</td>
<td class="ps-1 fs-10 align-content-center">{{ data.car.id_car_serie.name }}</td>
<td class="ps-1 fs-10 align-content-center">{{ data.car.id_car_trim.name }}</td>
<td class="text-center fs-10 align-content-center">{{ data.car.year }}</td>
<td class="ps-1 fs-10 align-content-center">{{ data.car.vin }}</td>
<td class="text-center fs-10 align-content-center">1</td>
<td class="text-center fs-10 align-content-center">{{ data.car.marked_price |floatformat:2 }}</td>
<td class="text-center fs-10 align-content-center">{{ data.discount_amount |floatformat:2 }}</td>
<td class="text-center fs-10 align-content-center">{{ data.vat_amount|floatformat:2 }}</td>
<td class="text-center fs-10 align-content-center">{{ data.final_price|floatformat:2 }}</td>
</tr>
</tbody>
</table>
</div>
<div class="d-flex justify-content-between">
<span class="fs-9 fw-thin">Additional&nbsp;Services</span>
<span class="fs-9 fw-thin">الخدمات&nbsp;الإضافية</span>
</div>
{% if data.additional_services %}
<div class="invoice-table p-1">
<table class="table table-sm table-bordered m-1">
<thead>
<tr>
<th class="text-center fs-10 align-content-center">Type&nbsp;/&nbsp;النوع</th>
<th class="text-center fs-10 align-content-center">Price&nbsp;/&nbsp;السعر</th>
<th class="text-center fs-10 align-content-center">Service VAT&nbsp;/&nbsp;ضريبة الخدمة</th>
<th class="text-center fs-10 align-content-center">
<span class="fs-10">Total</span> / <span class="fs-10">الإجمالي</span>
</th>
</tr>
</thead>
<tbody>
{% for service in data.additional_services.services %}
<tr>
<td class="ps-1 text-start fs-10 align-content-center">{{ service.0.name }}</td>
<td class="ps-1 text-center fs-10 align-content-center">{{ service.0.price|floatformat }}</td>
<td class="ps-1 text-center fs-10 align-content-center">{{ service.1|floatformat }}</td>
<td class="ps-1 text-center fs-10 align-content-center">{{ service.0.price_|floatformat }}</td>
</tr>
{% endfor %}
</tbody>
</table>
</div>
{% endif %}
<div class="invoice-total d-flex justify-content-end">
<div class="table-responsive">
<table class="table table-sm table-responsive">
<tr>
<td class="text-start ps-1">
<strong class="fs-9">Total VAT</strong>
</td>
<td class="text-center">
<span class="fs-9">{{ data.total_vat|floatformat }} <span class="icon-saudi_riyal"></span></span>
</td>
<td class="text-end">
<strong class="fs-9">إجمالي&nbsp;ضريبة&nbsp;القيمة&nbsp;المضافة</strong>
</td>
</tr>
<tr>
<td class="text-start ps-1">
<strong class="fs-9">Grand Total</strong>
</td>
<td class="text-center">
<span class="fs-9">{{ data.grand_total|floatformat }}&nbsp;<span class="icon-saudi_riyal"></span></span>
</td>
<td class="text-end">
<strong class="fs-9">الإجمالي&nbsp;الكلي</strong>
</td>
</tr>
<tr>
<td class="text-end" colspan="3">
<span class="fs-9 fw-bold">كتابةً:&nbsp;</span><span class="fs-9">{{ data.grand_total|num_to_words }}&nbsp;<span class="icon-saudi_riyal"></span></span>
</td>
</tr>
</table>
</div>
</div>
</div> </div>
<div class="footer-note"> <div class="section text-left">
<div class="logo-img text-center"> <h2>التفاصيل:</h2>
<img src="{% static 'images/logos/logo-d-pdf.png' %}" alt="Logo" /> <p><span class="label">رقم عرض السعر&nbsp;:&nbsp;</span> {{ estimate.estimate_number }}</p>
<p class="fs-9 fw-bold"> <p><span class="label">تاريخ الإصدار&nbsp;:&nbsp;</span> {{ estimate.date_approved|date:"Y/m/d" }}</p>
<span>Haikal</span>&nbsp;|&nbsp;<span>هيكل</span> <p><span class="label">طريقة الدفع&nbsp;:&nbsp;</span> {{ estimate.get_terms_display }}</p>
</p> </div>
</div>
<div class="table-container">
<div class="table-header">
<span>تفاصيل السيارة</span>
</div>
<table class="table">
<thead>
<tr>
<th class="text-center">
<span>الصانع</span>
</th>
<th class="text-center">
<span>الموديل</span>
</th>
<th class="text-center">
<span>السلسلة</span>
</th>
<th class="text-center">
<span>الفئة</span>
</th>
<th class="text-center">
<span>السنة</span>
</th>
<th class="text-center">
<span>رقم الهيكل</span>
</th>
</tr>
</thead>
<tbody>
<tr>
<td class="text-center">{{ data.car.id_car_make.name }}</td>
<td class="text-center">{{ data.car.id_car_model.name }}</td>
<td class="text-center">{{ data.car.id_car_serie.name }}</td>
<td class="text-center">{{ data.car.id_car_trim.name }}</td>
<td class="text-center">{{ data.car.year }}</td>
<td class="text-center">{{ data.car.vin }}</td>
</tr>
</tbody>
</table>
</div>
<div class="table-container">
<div class="table-header">
<span>التفاصيل المالية</span>
</div>
<table class="table">
<thead>
<tr>
<th class="text-center">
<span>الكمية</span>
</th>
<th class="text-center">
<span>سعر الوحدة</span>
</th>
<th class="text-center">
<span>الخصم</span>
</th>
<th class="text-center">
<span>الضريبة</span>
</th>
<th class="text-center">
<span>الإجمالي</span>
</th>
</tr>
</thead>
<tbody>
<tr>
<td class="text-center">1</td>
<td class="text-center">{{ data.car.marked_price|floatformat:2 }}</td>
<td class="text-center">{{ data.discount_amount|floatformat:2 }}</td>
<td class="text-center">{{ data.vat_amount|floatformat:2 }}</td>
<td class="text-center">{{ data.final_price|floatformat:2 }}</td>
</tr>
</tbody>
</table>
</div>
{% if data.additional_services %}
<div class="table-container">
<div class="table-header">
<span>الخدمات الإضافية</span>
</div> </div>
<p class="fs-11"> <table class="table">
<span class="fw-thin">Powered&nbsp;by&nbsp;</span> <thead>
<a class="text-decoration-none fs-9" <tr>
href="https://tenhal.sa" <th class="text-center">النوع</th>
style="color: #112e40"><span>TENHAL</span>&nbsp;|&nbsp;<span>تنحل</span></a> <th class="text-center">القيمة</th>
<th class="text-center">ضريبة الخدمة</th>
<th class="text-center">الإجمالي</th>
</tr>
</thead>
<tbody>
{% for service in data.additional_services.services %}
<tr>
<td class="text-center">{{ service.0.name }}</td>
<td class="text-center">{{ service.0.price|floatformat:2 }}</td>
<td class="text-center">{{ service.1|floatformat:2 }}</td>
<td class="text-center">{{ service.0.price_|floatformat:2 }}</td>
</tr>
{% endfor %}
</tbody>
<tfoot>
<tr>
<td class="text-center"></td>
<td class="text-center">{{ data.total_services_amount|floatformat:'2g'}}</td>
<td class="text-center">{{ data.total_services_vat|floatformat:'2g'}}</td>
<td class="text-center">{{ data.total_services_amount_|floatformat:'2g' }}</td>
</tr>
</tfoot>
</table>
</div>
{% endif %}
<div class="document-details" style="margin-top: 30px; ">
<div class="section text-left">
<p><span class="label">إجمالي ضريبة القيمة المضافة&nbsp;:&nbsp;</span> {{ data.total_vat|floatformat:'2g' }}</p>
<p><span class="label">الإجمالي الكلي&nbsp;:&nbsp;</span> {{ data.grand_total|floatformat:'2g' }}</p>
<p><span class="label">كتابةً&nbsp;:&nbsp;</span> {{ data.grand_total|num_to_words }}</p>
</div>
</div>
<hr style="border-bottom:1px solid #ccc; ">
<div class="document-footer">
<div class="footer-logo">
<img src="{% static 'images/logos/logo-d-pdf.png' %}" alt="Logo" />
<p>
<span>Haikal</span>&nbsp;|&nbsp;<span>هيكل</span>
</p>
</div>
<div class="footer-powered">
<p>
<span>Powered&nbsp;by&nbsp;</span>
<a href="https://tenhal.sa"><span>TENHAL</span>&nbsp;|&nbsp;<span>تنحل</span></a>
</p> </p>
</div> </div>
</div> </div>
<script src="{% static 'vendors/bootstrap/bootstrap.min.js' %}"></script> </div>
<script src="{% static 'js/html2pdf.bundle.min.js' %}"></script> </body>
<script> </html>
document.getElementById('download-pdf').addEventListener('click', function () {
html2pdf().from(document.getElementById('invoice-content')).set({
margin: 0,
filename: "{{ invoice.invoice_number }}_{{ invoice.customer.customer_name  }}_{{invoice.date_approved|date:'Y-m-d' }}.pdf",
image: { type: 'jpeg', quality: 0.98 },
html2canvas: { scale: 3 },
jsPDF: { unit: 'mm', format: 'a3', orientation: 'portrait' }
}).save();
});
</script>
</body>
</html>

View File

@ -0,0 +1,359 @@
{% load static custom_filters num2words_tags %}
<!DOCTYPE html>
<html lang="ar" dir="rtl">
<head>
<meta charset="UTF-8">
<title>فاتورة</title>
<style>
/* General Body and Font Styles */
body {
font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, "Helvetica Neue", Arial, sans-serif;
font-size: 12px;
color: #333;
margin: 0;
padding: 0;
position: relative;
}
/* Page Layout and Margins for PDF */
@page {
size: A4;
margin: 20mm;
@top-left {
content: "صفحة " counter(page) " من " counter(pages);
font-size: 10px;
color: #555;
}
}
/* Header Styles */
.document-header {
display: flex;
justify-content: space-between;
align-items: center;
border-bottom: 2px solid #333;
padding-bottom: 15px;
margin-bottom: 20px;
}
.document-header .logo {
max-width: 150px;
height: auto;
}
.document-header h1 {
font-size: 24px;
margin: 0;
color: #0056b3;
}
.document-header address {
text-align: right;
font-style: normal;
font-size: 10px;
}
/* Document Details Section */
.document-details {
display: flex;
justify-content: space-between;
margin-bottom: 30px;
line-height: 1.6;
}
.document-details .section {
width: 48%;
}
.document-details h2 {
font-size: 14px;
border-bottom: 1px solid #ccc;
padding-bottom: 5px;
margin-bottom: 10px;
color: #555;
}
.document-details p {
margin: 0;
font-size: 12px;
}
.document-details .label {
font-weight: bold;
color: #555;
}
/* Table Styles */
.table {
width: 80%;
border-collapse: collapse;
margin: 20px auto; /* Centering the table */
}
.table th, .table td {
border: 1px solid #ddd;
padding: 8px;
text-align: right;
}
.table th {
background-color: #f2f2f2;
font-weight: bold;
}
.table tfoot td {
border-top: 2px solid #333;
font-weight: bold;
}
.text-right {
text-align: left;
}
.text-left {
text-align: right;
}
.text-center {
text-align: center;2px solid #333;
}
/* Responsive and layout helpers */
.table-container {
width: 100%;
overflow-x: auto;
margin-bottom: 20px;
}
.table-header {
display: flex;
justify-content: space-between;
align-items: center;
margin-bottom: 5px;
}
.table-header span {
font-size: 9px;
font-weight: lighter;
}
/* Footer Styles */
.document-footer {
position: relative;
bottom: 0;
left: 0;
width: 100%;
text-align: center;
font-size: 10px;
color: #888;
padding-top: 15px;
margin: 0 20mm;
}
.footer-flex {
display: flex;
justify-content: space-between;
align-items: center;
width: 100%;
}
.footer-logo {
text-align: center;
}
.footer-logo img {
height: 20px;
width: 20px;
}
.footer-logo p {
font-size: 9px;
font-weight: bold;
}
.footer-powered p {
font-size: 11px;
}
.footer-powered span {
font-weight: lighter;
}
.footer-powered a {
color: #112e40;
text-decoration: none;
font-size: 9px;
}
</style>
</head>
<body>
<div class="document-header">
<div>
<h1>{{ dealer_info.name }}</h1>
<address>
العنوان&nbsp;:&nbsp;{{ dealer_info.address }}<br>
البريد الإلكتروني&nbsp;:&nbsp;{{ dealer_info.user.email }}<br>
الهاتف&nbsp;:&nbsp;{{ dealer_info.phone_number }}<br>
رقم السجل التجاري&nbsp;:&nbsp;{{dealer_info.crn }}<br>
الرقم الضريبي&nbsp;:&nbsp;{{ dealer_info.vrn }}
</address>
</div>
<div>
<div class="dealer-logo ">
{% if dealer_info.logo %}
<img class="rounded-soft"
style="max-width:100px;
max-height:100px"
src="{{dealer_info.logo.url|default:'' }}"
alt="Dealer Logo" />
{% endif %}
</div>
</div>
<div>
<h1>فاتورة</h1>
<h2 style="font-size: 18px;">{{ invoice.invoice_number }}</h2>
</div>
</div>
<div class="document-details">
<div class="section">
<h2>{{ customer_obj.full_name }}: إلى</h2>
<p><span class="label">العميل&nbsp;:&nbsp;{{ customer_obj.full_name }}</span></p>
<p><span class="label">البريد الإلكتروني&nbsp;:&nbsp;{{ customer_obj.email |default:"N/A"}}</span></p>
<p><span class="label">الهاتف&nbsp;:&nbsp;{{ customer_obj.phone_number|default:"N/A" }}</span></p>
<p><span class="label">العنوان&nbsp;:&nbsp;{{ customer_obj.address|default:"N/A" }}</span></p>
</div>
<div class="section text-left">
<h2>التفاصيل:</h2>
<p><span class="label">رقم عرض السعر&nbsp;:&nbsp;</span> {{ invoice.invoice_number }}</p>
<p><span class="label">تاريخ الإصدار&nbsp;:&nbsp;</span> {{ invoice.date_approved|date:"Y/m/d" }}</p>
<p><span class="label">طريقة الدفع&nbsp;:&nbsp;</span> {{ invoice.get_terms_display }}</p>
</div>
</div>
<div class="table-container">
<div class="table-header">
<span>تفاصيل السيارة</span>
</div>
<table class="table">
<thead>
<tr>
<th class="text-center">
<span>الصانع</span>
</th>
<th class="text-center">
<span>الموديل</span>
</th>
<th class="text-center">
<span>السلسلة</span>
</th>
<th class="text-center">
<span>الفئة</span>
</th>
<th class="text-center">
<span>السنة</span>
</th>
<th class="text-center">
<span>رقم الهيكل</span>
</th>
</tr>
</thead>
<tbody>
<tr>
<td class="text-center">{{ data.car.id_car_make.name }}</td>
<td class="text-center">{{ data.car.id_car_model.name }}</td>
<td class="text-center">{{ data.car.id_car_serie.name }}</td>
<td class="text-center">{{ data.car.id_car_trim.name }}</td>
<td class="text-center">{{ data.car.year }}</td>
<td class="text-center">{{ data.car.vin }}</td>
</tr>
</tbody>
</table>
</div>
<div class="table-container">
<div class="table-header">
<span>التفاصيل المالية</span>
</div>
<table class="table">
<thead>
<tr>
<th class="text-center">
<span>الكمية</span>
</th>
<th class="text-center">
<span>سعر الوحدة</span>
</th>
<th class="text-center">
<span>الخصم</span>
</th>
<th class="text-center">
<span>الضريبة</span>
</th>
<th class="text-center">
<span>الإجمالي</span>
</th>
</tr>
</thead>
<tbody>
<tr>
<td class="text-center">1</td>
<td class="text-center">{{ data.car.marked_price|currency_format }}</td>
<td class="text-center">{{ data.discount_amount|currency_format}}</td>
<td class="text-center">{{ data.vat_amount|currency_format}}</td>
<td class="text-center">{{ data.final_price|currency_format }}</td>
</tr>
</tbody>
</table>
</div>
{% if data.additional_services %}
<div class="table-container">
<div class="table-header">
<span>الخدمات الإضافية</span>
</div>
<table class="table">
<thead>
<tr>
<th class="text-center">النوع</th>
<th class="text-center">القيمة</th>
<th class="text-center">ضريبة الخدمة</th>
<th class="text-center">الإجمالي</th>
</tr>
</thead>
<tbody>
{% for service in data.additional_services.services %}
<tr>
<td class="text-center">{{ service.0.name }}</td>
<td class="text-center">{{ service.0.price|currency_format}}</td>
<td class="text-center">{{ service.1|currency_format }}</td>
<td class="text-center">{{ service.0.price_|currency_format}}</td>
</tr>
{% endfor %}
</tbody>
<tfoot>
<tr>
<td class="text-center"></td>
<td class="text-center">{{ data.total_services_amount|currency_format}}</td>
<td class="text-center">{{ data.total_services_vat|currency_format}}</td>
<td class="text-center">{{ data.total_services_amount_|currency_format }}</td>
</tr>
</tfoot>
</table>
</div>
{% endif %}
<div class="document-details" style="margin-top: 30px; ">
<div class="section text-left">
<p><span class="label">إجمالي ضريبة القيمة المضافة&nbsp;:&nbsp;</span> {{ data.total_vat|currency_format }}</p>
<p><span class="label">الإجمالي الكلي&nbsp;:&nbsp;</span> {{ data.grand_total|currency_format}}</p>
<p><span class="label">كتابةً&nbsp;:&nbsp;</span> {{ data.grand_total|num_to_words }}</p>
</div>
</div>
<hr style="border-bottom:1px solid #ccc; ">
<div class="document-footer">
<div class="footer-logo">
<img src="{% static 'images/logos/logo-d-pdf.png' %}" alt="Logo" />
<p>
<span>Haikal</span>&nbsp;|&nbsp;<span>هيكل</span>
</p>
</div>
<div class="footer-powered">
<p>
<span>Powered&nbsp;by&nbsp;</span>
<a href="https://tenhal.sa"><span>TENHAL</span>&nbsp;|&nbsp;<span>تنحل</span></a>
</p>
</div>
</div>
</body>
</html>

View File

@ -0,0 +1,357 @@
{% load static custom_filters num2words_tags %}
<!DOCTYPE html>
<html lang="en" dir="ltr">
<head>
<meta charset="UTF-8">
<title>Invoice</title>
<style>
/* General Body and Font Styles */
body {
font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, "Helvetica Neue", Arial, sans-serif;
font-size: 12px;
color: #333;
margin: 0;
padding: 0;
position: relative;
}
/* Page Layout and Margins for PDF */
@page {
size: A4;
margin: 20mm;
@top-left {
content: "صفحة " counter(page) " من " counter(pages);
font-size: 10px;
color: #555;
}
}
/* Header Styles */
.document-header {
display: flex;
justify-content: space-between;
align-items: center;
border-bottom: 2px solid #333;
padding-bottom: 15px;
margin-bottom: 20px;
}
.document-header .logo {
max-width: 150px;
height: auto;
}
.document-header h1 {
font-size: 24px;
margin: 0;
color: #0056b3;
}
.document-header address {
text-align: right;
font-style: normal;
font-size: 10px;
}
/* Document Details Section */
.document-details {
display: flex;
justify-content: space-between;
margin-bottom: 30px;
line-height: 1.6;
}
.document-details .section {
width: 48%;
}
.document-details h2 {
font-size: 14px;
border-bottom: 1px solid #ccc;
padding-bottom: 5px;
margin-bottom: 10px;
color: #555;
}
.document-details p {
margin: 0;
font-size: 12px;
}
.document-details .label {
font-weight: bold;
color: #555;
}
/* Table Styles */
.table {
width: 80%;
border-collapse: collapse;
margin: 20px auto; /* Centering the table */
}
.table th, .table td {
border: 1px solid #ddd;
padding: 8px;
text-align: right;
}
.table th {
background-color: #f2f2f2;
font-weight: bold;
}
.table tfoot td {
border-top: 2px solid #333;
font-weight: bold;
}
.text-right {
text-align: left;
}
.text-left {
text-align: right;
}
.text-center {
text-align: center;2px solid #333;
}
/* Responsive and layout helpers */
.table-container {
width: 100%;
overflow-x: auto;
margin-bottom: 20px;
}
.table-header {
display: flex;
justify-content: space-between;
align-items: center;
margin-bottom: 5px;
}
.table-header span {
font-size: 9px;
font-weight: lighter;
}
/* Footer Styles */
.document-footer {
position: relative;
bottom: 0;
left: 0;
width: 100%;
text-align: center;
font-size: 10px;
color: #888;
padding-top: 15px;
margin: 0 20mm;
}
.footer-flex {
display: flex;
justify-content: space-between;
align-items: center;
width: 100%;
}
.footer-logo {
text-align: center;
}
.footer-logo img {
height: 20px;
width: 20px;
}
.footer-logo p {
font-size: 9px;
font-weight: bold;
}
.footer-powered p {
font-size: 11px;
}
.footer-powered span {
font-weight: lighter;
}
.footer-powered a {
color: #112e40;
text-decoration: none;
font-size: 9px;
}
</style>
</head>
<body>
<div class="document-header">
<div>
<h1>{{ dealer_info.name }}</h1>
<address>
Address&nbsp;:&nbsp;{{ dealer_info.address }}<br>
Email&nbsp;:&nbsp;{{ dealer_info.user.email }}<br>
Phone&nbsp;:&nbsp;{{ dealer_info.phone_number }}<br>
Commercial Registration No.&nbsp;:&nbsp;{{dealer_info.crn }}<br>
VAT No.&nbsp;:&nbsp;{{ dealer_info.vrn }}
</address>
</div>
<div>
<div class="dealer-logo ">
{% if dealer_info.logo %}
<img class="rounded-soft"
style="max-width:100px;
max-height:100px"
src="{{dealer_info.logo.url|default:'' }}"
alt="Dealer Logo" />
{% endif %}
</div>
</div>
<div>
<h1>Invoice</h1>
<h2 style="font-size: 18px;">{{ invoice.invoice_number }}</h2>
</div>
</div>
<div class="document-details">
<div class="section">
<h2>To: {{ customer_obj.full_name }}</h2>
<p><span class="label">Customer&nbsp;:&nbsp;{{ customer_obj.full_name }}</span></p>
<p><span class="label">Email&nbsp;:&nbsp;{{ customer_obj.email |default:"N/A"}}</span></p>
<p><span class="label">Phone&nbsp;:&nbsp;{{ customer_obj.phone_number|default:"N/A" }}</span></p>
<p><span class="label">Address&nbsp;:&nbsp;{{ customer_obj.address|default:"N/A" }}</span></p>
</div>
<div class="section text-right">
<h2>Details:</h2>
<p><span class="label">Quotation Number&nbsp;:&nbsp;</span> {{ invoice.invoice_number }}</p>
<p><span class="label">Issue Date&nbsp;:&nbsp;</span> {{ invoice.date_approved|date:"Y/m/d" }}</p>
<p><span class="label">Payment Method&nbsp;:&nbsp;</span> {{ invoiceget_terms_display }}</p>
</div>
</div>
<div class="table-container">
<div class="table-header">
<span>Car Details</span>
</div>
<table class="table">
<thead>
<tr>
<th class="text-center">
<span>Make</span>
</th>
<th class="text-center">
<span>Model</span>
</th>
<th class="text-center">
<span>Series</span>
</th>
<th class="text-center">
<span>Trim</span>
</th>
<th class="text-center">
<span>Year</span>
</th>
<th class="text-center">
<span>VIN</span>
</th>
</tr>
</thead>
<tbody>
<tr>
<td class="text-center">{{ data.car.id_car_make.name }}</td>
<td class="text-center">{{ data.car.id_car_model.name }}</td>
<td class="text-center">{{ data.car.id_car_serie.name }}</td>
<td class="text-center">{{ data.car.id_car_trim.name }}</td>
<td class="text-center">{{ data.car.year }}</td>
<td class="text-center">{{ data.car.vin }}</td>
</tr>
</tbody>
</table>
</div>
<div class="table-container">
<div class="table-header">
<span>Financial Details</span>
</div>
<table class="table">
<thead>
<tr>
<th class="text-center">
<span>Quantity</span>
</th>
<th class="text-center">
<span>Unit Price</span>
</th>
<th class="text-center">
<span>Discount</span>
</th>
<th class="text-center">
<span>VAT</span>
</th>
<th class="text-center">
<span>Total</span>
</th>
</tr>
</thead>
<tbody>
<tr>
<td class="text-center">1</td>
<td class="text-center">{{ data.car.marked_price|currency_format}}</td>
<td class="text-center">{{ data.discount_amount|currency_format }}</td>
<td class="text-center">{{ data.vat_amount|currency_format}}</td>
<td class="text-center">{{ data.final_price|currency_format }}</td>
</tr>
</tbody>
</table>
</div>
{% if data.additional_services %}
<div class="table-container">
<div class="table-header">
<span>Additional Services</span>
</div>
<table class="table">
<thead>
<tr>
<th class="text-center">Type</th>
<th class="text-center">Value</th>
<th class="text-center">Service VAT</th>
<th class="text-center">Total</th>
</tr>
</thead>
<tbody>
{% for service in data.additional_services.services %}
<tr>
<td class="text-center">{{ service.0.name }}</td>
<td class="text-center">{{ service.0.price|currency_format }}</td>
<td class="text-center">{{ service.1|currency_format}}</td>
<td class="text-center">{{ service.0.price_|currency_format}}</td>
</tr>
{% endfor %}
</tbody>
<tfoot>
<tr>
<td class="text-center"></td>
<td class="text-center">{{ data.total_services_amount|currency_format}}</td>
<td class="text-center">{{ data.total_services_vat|currency_format}}</td>
<td class="text-center">{{ data.total_services_amount_|currency_format }}</td>
</tr>
</tfoot>
</table>
</div>
{% endif %}
<div class="document-details" style="margin-top: 30px; ">
<div class="section text-right">
<p><span class="label">Total VAT&nbsp;:&nbsp;</span> {{ data.total_vat|currency_format}}</p>
<p><span class="label">Grand Total&nbsp;:&nbsp;</span> {{ data.grand_total|currency_format}}</p>
<p><span class="label">In words&nbsp;:&nbsp;</span> {{ data.grand_total|num_to_words }}</p>
</div>
</div>
<hr style="border-bottom:1px solid #ccc; ">
<div class="document-footer">
<div class="footer-logo">
<img src="{% static 'images/logos/logo-d-pdf.png' %}" alt="Logo" />
<p>
<span>Haikal</span>&nbsp;|&nbsp;<span>هيكل</span>
</p>
</div>
<div class="footer-powered">
<p>
<span>Powered&nbsp;by&nbsp;</span>
<a href="https://tenhal.sa"><span>TENHAL</span>&nbsp;|&nbsp;<span>تنحل</span></a>
</p>
</div>
</div>
</body>
</html>

View File

@ -15,9 +15,9 @@
</h2> </h2>
</div> </div>
</div> </div>
<div class="col-auto"> {% comment %} <div class="col-auto">
<div class="d-flex">{% include 'partials/search_box.html' %}</div> <div class="d-flex">{% include 'partials/search_box.html' %}</div>
</div> </div> {% endcomment %}
</div> </div>
<div class="table-responsive scrollbar mx-n1 px-1"> <div class="table-responsive scrollbar mx-n1 px-1">
<table class="table align-items-center table-flush"> <table class="table align-items-center table-flush">

View File

@ -101,7 +101,7 @@
<a class="fs-8 fw-bold" <a class="fs-8 fw-bold"
href="{% url 'vendor_detail' request.dealer.slug vendor.slug %}">{{ vendor.arabic_name }}</a> href="{% url 'vendor_detail' request.dealer.slug vendor.slug %}">{{ vendor.arabic_name }}</a>
<div class="d-flex align-items-center"> <div class="d-flex align-items-center">
<p class="mb-0 text-body-highlight fw-semibold fs-9 me-2">{{ vendor.name }}</p> <p class="mb-0 text-body-highlight fw-semibold fs-9 me-2">{{ vendor.name|title }}</p>
<!--<span class="badge badge-phoenix badge-phoenix-primary">{{ vendor.vendor_model.uuid }}</span>--> <!--<span class="badge badge-phoenix badge-phoenix-primary">{{ vendor.vendor_model.uuid }}</span>-->
</div> </div>
</div> </div>