Merge pull request 'Dashboards and Sale reports: Sale reports finances update and dashboards cards logic' (#180) from frontend into main

Reviewed-on: #180
This commit is contained in:
ismail 2025-08-17 14:00:59 +03:00
commit 870bcfdcec
23 changed files with 2892 additions and 1500 deletions

View File

@ -583,6 +583,7 @@ class AdditionalServices(models.Model, LocalizedNameMixin):
"price_": str(self.price_),
"taxable": self.taxable,
"uom": self.uom,
"service_tax":str(self.service_tax)
}
@property
@ -593,6 +594,13 @@ class AdditionalServices(models.Model, LocalizedNameMixin):
if self.taxable
else self.price
)
@property
def service_tax(self):
vat = VatRate.objects.filter(dealer=self.dealer, is_active=True).first()
return (
Decimal(self.price * vat.rate)
)
class Meta:
verbose_name = _("Additional Services")
@ -865,19 +873,64 @@ class Car(Base):
#
@property
def get_additional_services_amount(self):
return sum([Decimal(x.price) for x in self.additional_services.all()])
@property
def get_additional_services_amount_(self):
return sum([Decimal(x.price_) for x in self.additional_services.all()])
@property
def get_additional_services_vat(self):
vat = VatRate.objects.filter(dealer=self.dealer,is_active=True).first()
return sum([Decimal((x.price)*(vat.rate)) for x in self.additional_services.all()])
def get_additional_services(self):
return {"services": [x for x in self.additional_services.all()],"total":self.get_additional_services_amount}
vat = VatRate.objects.filter(dealer=self.dealer,is_active=True).first()
return {"services": [[x,(x.price)*(vat.rate)] for x in self.additional_services.all()],
"total_":self.get_additional_services_amount_,
"total":self.get_additional_services_amount,
"services_vat":self.get_additional_services_vat}
@property
def final_price(self):
return Decimal(self.marked_price -self.discount)
@property
def vat_amount(self):
vat = VatRate.objects.filter(dealer=self.dealer,is_active=True).first()
return Decimal(self.marked_price) * (vat.rate / 100)
return Decimal(self.final_price) * (vat.rate)
@property
def total_vat(self):
return Decimal(self.marked_price) + Decimal(self.vat_amount)
def total_services_and_car_vat(self):
return self.vat_amount+self.get_additional_services()['services_vat']
@property
def final_price_plus_vat(self):
return Decimal(self.final_price) + Decimal(self.vat_amount)
@property
def final_price_plus_services_plus_vat(self):
return Decimal(self.final_price_plus_vat) + Decimal(self.get_additional_services()['total_']) #total services with vat and car_sell price with vat
# to be used after invoice is created
@property
def invoice(self):
return self.item_model.invoicemodel_set.first() or None
@property
def estimate(self):
return getattr(self.invoice,'ce_model',None)
@property
def discount(self):
if not self.estimate:
return 0
try:
instance = ExtraInfo.objects.get(
dealer=self.dealer,
content_type=ContentType.objects.get_for_model(EstimateModel),
object_id=self.estimate.pk,
)
return Decimal(instance.data.get('discount',0))
except ExtraInfo.DoesNotExist:
return Decimal(0)
# def get_discount_amount(self,estimate,user):
# try:
# instance = models.ExtraInfo.objects.get(

View File

@ -40,12 +40,15 @@ urlpatterns = [
views.assign_car_makes,
name="assign_car_makes",
),
path(
"dashboards/manager/",
views.ManagerDashboard.as_view(),
name="manager_dashboard",
),
#dashboards
path("dashboards/dealer/", views.DealerDashboard,name="dealer_dashboard"),
path( "dashboards/manager/", views.ManagerDashboard.as_view(),name="manager_dashboard"),
path("dashboards/sales/", views.SalesDashboard.as_view(), name="sales_dashboard"),
path("dashboards/accountant/", views.AccountantDashboard.as_view(), name="accountant_dashboard"),
path("dashboards/inventory/", views.InventoryDashboard.as_view(), name="inventory_dashboard"),
path("cars/inventory/table/", views.CarListViewTable.as_view(), name="car_table"),
path("export/format/", TableExport.export, name="export"),
@ -772,6 +775,7 @@ urlpatterns = [
views.EstimateDetailView.as_view(),
name="estimate_detail",
),
path('<slug:dealer_slug>/sales/estimates/print/<uuid:pk>/', views.EstimatePrintView.as_view(), name='estimate_print'),
path(
"<slug:dealer_slug>/sales/estimates/create/",
views.create_estimate,

View File

@ -1288,18 +1288,24 @@ def get_finance_data(estimate,dealer):
)
discount = extra_info.data.get("discount", 0)
discount = Decimal(discount)
vat_amount = car.marked_price * vat.rate
additional_services = car.get_additional_services()
discounted_price=(Decimal(car.marked_price) - discount)
vat_amount = discounted_price * vat.rate
total_services_vat=sum([ x[1] for x in additional_services.get("services")])
total_vat=vat_amount+total_services_vat
return {
"car": car,
"discounted_price": (Decimal(car.marked_price) - discount) or 0,
"discounted_price": discounted_price or 0,
"price_before_discount": car.marked_price,
"vat_amount": vat_amount,
"vat_rate": vat.rate,
"discount_amount": discount,
"additional_services": additional_services,
"grand_total": (car.marked_price - discount) + vat_amount + additional_services.get("total")
"final_price": discounted_price+ vat_amount,
"total_services_vat":total_services_vat,
"total_vat":total_vat,
"grand_total": discounted_price + vat_amount + additional_services.get("total")
}

View File

@ -17,7 +17,7 @@ from random import randint
from decimal import Decimal
from io import TextIOWrapper
from django.apps import apps
from datetime import datetime, timedelta
from datetime import datetime, timedelta,date
from calendar import month_name
from pyzbar.pyzbar import decode
from urllib.parse import urlparse, urlunparse
@ -396,7 +396,164 @@ class TestView(TemplateView):
template_name = "inventory/cars_list_api.html"
# class DealerDashboard(LoginRequiredMixin, TemplateView):
# """
# ManagerDashboard class is a view handling the dashboard for a manager.
# Provides functionality to manage and view various statistics and data specific
# to the dealer associated with the authenticated manager. It uses a specific
# template and ensures authentication before granting access. The class
# aggregates data about cars, leads, financial statistics, and other related
# business information for display in the manager's dashboard.
# :ivar template_name: Path to the template used for rendering the manager's dashboard.
# :type template_name: str
# """
# template_name = "dashboards/dealer_dashbaord.html"
# # def dispatch(self, request, *args, **kwargs):
# # if not request.user.is_authenticated:
# # return redirect("welcome")
# # if not getattr(request.user, "dealer", False):
# # return HttpResponseForbidden(
# # "You are not authorized to view this dashboard."
# # )
# # return super().dispatch(request, *args, **kwargs)
# def get_context_data(self, **kwargs):
# context = super().get_context_data(**kwargs)
# dealer = get_user_type(self.request)
# entity = dealer.entity
# qs = models.Car.objects.filter(dealer=dealer)
# total_cars = qs.count()
# # stats = models.CarFinance.objects.filter(car__dealer=dealer).aggregate(
# # total_cost_price=Sum("cost_price"),
# # total_selling_price=Sum("selling_price"),
# # )
# # total_cost_price = stats["total_cost_price"] or 0
# # total_selling_price = stats["total_selling_price"] or 0
# # total_profit = total_selling_price - total_cost_price
# # new_leads = models.Lead.objects.filter(
# # dealer=dealer, status=models.Status.NEW
# # ).count()
# # pending_leads = models.Lead.objects.filter(
# # dealer=dealer, status=models.Status.CONTACTED
# # ).count()
# # canceled_leads = models.Lead.objects.filter(
# # dealer=dealer, status=models.Status.UNQUALIFIED
# # ).count()
# # car_status_qs = qs.values("status").annotate(count=Count("status"))
# # car_status = {status: count for status, count in car_status_qs}
# # car_by_make_qs = qs.values("id_car_make__name").annotate(count=Count("id"))
# # car_by_make = list(car_by_make_qs)
# # context["dealer"] = dealer
# # context["total_activity"] = models.UserActivityLog.objects.filter(
# # user=dealer.user
# # ).count()
# # context["total_cars"] = total_cars
# # context["total_reservations"] = models.CarReservation.objects.filter(
# # reserved_until__gte=timezone.now()
# # ).count()
# # context["total_cost_price"] = total_cost_price
# # context["total_selling_price"] = total_selling_price
# # context["total_profit"] = total_profit
# # context["new_leads"] = new_leads
# # context["pending_leads"] = pending_leads
# # context["canceled_leads"] = canceled_leads
# # context["car"] = json.dumps(car_by_make)
# # context["available_cars"] =qs.filter(status='available').count()
# # context["sold_cars"] = qs.filter(status='sold').count()
# # context["reserved_cars"] = qs.filter(status='reserved').count()
# # context["hold_cars"] =qs.filter(status='hold').count()
# # context["damaged_cars"] = qs.filter(status='damaged').count()
# # context["transfer_cars"] = qs.filter(status='transfer').count()
# # context["present_inventory_count"]=total_cars-context["sold_cars"]-context["damaged_cars"]
# # cars_sold=qs.filter(status='sold')
# # cars_sold.aggregate(total_inventory_value=sum())
# context["customers"] = entity.get_customers().count()
# context["staff"] = models.Staff.objects.filter(dealer=dealer).count()
# context["total_leads"] = models.Lead.objects.filter(dealer=dealer).count()
# context["invoices"] = entity.get_invoices().count()
# context["estimates"] = entity.get_estimates().count()
# context["purchase_orders"] = entity.get_purchase_orders().count()
# return context
def DealerDashboard(request):
dealer = request.dealer
cars_sold = models.Car.objects.filter(dealer=dealer, status='sold')
today_local = timezone.localdate()
start_date_str = request.GET.get('start_date')
end_date_str = request.GET.get('end_date')
if not start_date_str:
start_date = today_local - timedelta(days=30)
else:
start_date = timezone.datetime.strptime(start_date_str, '%Y-%m-%d').date()
if not end_date_str:
end_date = today_local
else:
end_date = timezone.datetime.strptime(end_date_str, '%Y-%m-%d').date()
# The database query will automatically be handled in a timezone-aware manner
cars_sold_filtered = cars_sold.filter(
sold_date__date__gte=start_date,
sold_date__date__lte=end_date
)
print(cars_sold_filtered)
# # Calculate summary data for the filtered results
total_cars_sold=cars_sold_filtered.count()
print(total_cars_sold)
total_revenue_from_cars = sum([ car.final_price for car in cars_sold_filtered])
total_cost_of_cars_sold=sum([car.cost_price for car in cars_sold_filtered])
net_profit_from_cars=total_revenue_from_cars-total_cost_of_cars_sold
total_vat_collected_from_cars=sum([car.vat_amount for car in cars_sold_filtered])
total_revenue_from_services=sum([car.get_additional_services()['total'] for car in cars_sold_filtered])
total_vat_collected_from_services=sum([car.get_additional_services()['services_vat'] for car in cars_sold_filtered])
total_vat_collected = total_vat_collected_from_cars+total_vat_collected_from_services
total_revenue_generated=total_revenue_from_cars+total_revenue_from_services
total_discount = sum([car.discount for car in cars_sold_filtered])
context = {
'start_date': start_date,
'end_date': end_date,
'cars_sold': cars_sold_filtered,
'total_cars_sold':total_cars_sold,
'total_cost_of_cars_sold':total_cost_of_cars_sold,
'total_revenue_from_cars':total_revenue_from_cars,
'net_profit_from_cars':net_profit_from_cars,
'total_vat_collected_from_cars': total_vat_collected_from_cars,
'total_discount_on_cars':total_discount,
'total_revenue_from_services':total_revenue_from_services,
'total_vat_collected_from_services': total_vat_collected_from_services,
'total_revenue_generated': total_revenue_generated,
'total_vat_collected':total_vat_collected,
}
return render(request, 'dashboards/dealer_dashboard.html', context)
class ManagerDashboard(LoginRequiredMixin, TemplateView):
"""
@ -412,7 +569,7 @@ class ManagerDashboard(LoginRequiredMixin, TemplateView):
:type template_name: str
"""
template_name = "dashboards/manager.html"
template_name = "dashboards/manager_dashboard.html"
# def dispatch(self, request, *args, **kwargs):
# if not request.user.is_authenticated:
@ -495,6 +652,9 @@ class ManagerDashboard(LoginRequiredMixin, TemplateView):
return context
class SalesDashboard(LoginRequiredMixin, TemplateView):
"""
SalesDashboard class provides a view for the sales dashboard.
@ -513,7 +673,152 @@ class SalesDashboard(LoginRequiredMixin, TemplateView):
:type template_name: str
"""
template_name = "dashboards/sales.html"
template_name = "dashboards/sales_dashboard.html"
def get_context_data(self, **kwargs):
context = super().get_context_data(**kwargs)
dealer = self.request.dealer
staff = getattr(self.request, "staff", None)
total_cars = models.Car.objects.filter(dealer=dealer).count()
total_reservations = models.CarReservation.objects.filter(
reserved_by=self.request.user, reserved_until__gte=timezone.now()
).count()
available_cars = models.Car.objects.filter(
dealer=dealer, status=models.CarStatusChoices.AVAILABLE
).count()
sold_cars = models.Car.objects.filter(
dealer=dealer, status=models.CarStatusChoices.SOLD
).count()
reserved_cars = models.Car.objects.filter(
dealer=dealer, status=models.CarStatusChoices.RESERVED
).count()
hold_cars = models.Car.objects.filter(
dealer=dealer, status=models.CarStatusChoices.HOLD
).count()
damaged_cars = models.Car.objects.filter(
dealer=dealer, status=models.CarStatusChoices.DAMAGED
).count()
transfer_cars = models.Car.objects.filter(
dealer=dealer, status=models.CarStatusChoices.TRANSFER
).count()
reserved_percentage = reserved_cars / total_cars * 100
sold_percentage = sold_cars / total_cars * 100
qs = (
models.Car.objects.values("id_car_make__name")
.annotate(count=Count("id"))
.order_by("id_car_make__name")
)
car_by_make = list(qs)
context["dealer"] = dealer
context["staff"] = staff
context["total_cars"] = total_cars
context["total_reservations"] = total_reservations
context["reserved_percentage"] = reserved_percentage
context["sold_percentage"] = sold_percentage
context["available_cars"] = available_cars
context["sold_cars"] = sold_cars
context["reserved_cars"] = reserved_cars
context["hold_cars"] = hold_cars
context["damaged_cars"] = damaged_cars
context["transfer_cars"] = transfer_cars
context["car"] = json.dumps(car_by_make)
return context
class InventoryDashboard(LoginRequiredMixin, TemplateView):
"""
SalesDashboard class provides a view for the sales dashboard.
This class is responsible for generating the context data required to
display various statistics and information on the sales dashboard. It
inherits from `LoginRequiredMixin` and `TemplateView`, ensuring only
authenticated users can access the view. The dashboard includes data
such as the total number of cars, reservations, sold percentages,
reserved percentages, and cars categorized by various statuses. The
purpose of this dashboard is to provide dealership staff an overview
of their inventory and related sales metrics.
:ivar template_name: The name of the HTML template used for rendering
the sales dashboard.
:type template_name: str
"""
template_name = "dashboards/inventory_dashboard.html"
def get_context_data(self, **kwargs):
context = super().get_context_data(**kwargs)
dealer = self.request.dealer
staff = getattr(self.request, "staff", None)
total_cars = models.Car.objects.filter(dealer=dealer).count()
total_reservations = models.CarReservation.objects.filter(
reserved_by=self.request.user, reserved_until__gte=timezone.now()
).count()
available_cars = models.Car.objects.filter(
dealer=dealer, status=models.CarStatusChoices.AVAILABLE
).count()
sold_cars = models.Car.objects.filter(
dealer=dealer, status=models.CarStatusChoices.SOLD
).count()
reserved_cars = models.Car.objects.filter(
dealer=dealer, status=models.CarStatusChoices.RESERVED
).count()
hold_cars = models.Car.objects.filter(
dealer=dealer, status=models.CarStatusChoices.HOLD
).count()
damaged_cars = models.Car.objects.filter(
dealer=dealer, status=models.CarStatusChoices.DAMAGED
).count()
transfer_cars = models.Car.objects.filter(
dealer=dealer, status=models.CarStatusChoices.TRANSFER
).count()
reserved_percentage = reserved_cars / total_cars * 100
sold_percentage = sold_cars / total_cars * 100
qs = (
models.Car.objects.values("id_car_make__name")
.annotate(count=Count("id"))
.order_by("id_car_make__name")
)
car_by_make = list(qs)
context["dealer"] = dealer
context["staff"] = staff
context["total_cars"] = total_cars
context["total_reservations"] = total_reservations
context["reserved_percentage"] = reserved_percentage
context["sold_percentage"] = sold_percentage
context["available_cars"] = available_cars
context["sold_cars"] = sold_cars
context["reserved_cars"] = reserved_cars
context["hold_cars"] = hold_cars
context["damaged_cars"] = damaged_cars
context["transfer_cars"] = transfer_cars
context["car"] = json.dumps(car_by_make)
return context
class AccountantDashboard(LoginRequiredMixin, TemplateView):
"""
SalesDashboard class provides a view for the sales dashboard.
This class is responsible for generating the context data required to
display various statistics and information on the sales dashboard. It
inherits from `LoginRequiredMixin` and `TemplateView`, ensuring only
authenticated users can access the view. The dashboard includes data
such as the total number of cars, reservations, sold percentages,
reserved percentages, and cars categorized by various statuses. The
purpose of this dashboard is to provide dealership staff an overview
of their inventory and related sales metrics.
:ivar template_name: The name of the HTML template used for rendering
the sales dashboard.
:type template_name: str
"""
template_name = "dashboards/accountant_dashboard.html"
def get_context_data(self, **kwargs):
context = super().get_context_data(**kwargs)
@ -4598,7 +4903,7 @@ def create_estimate(request, dealer_slug, slug=None):
"quantity": 1,
"unit_cost": round(float(i.marked_price)),
"unit_revenue": round(float(i.marked_price)),
"total_amount": round(float(i.total_vat)),# TODO : check later
"total_amount": round(float(i.final_price_plus_vat)),# TODO : check later
}
)
@ -4619,7 +4924,7 @@ def create_estimate(request, dealer_slug, slug=None):
# "unit_cost": instance.cost_price,
# "unit_revenue": instance.marked_price,
# "quantity": Decimal(quantities),
# "total_amount": instance.total_vat * int(quantities),# TODO : check later
# "total_amount": instance.final_price_plus_vat * int(quantities),# TODO : check later
# }
# }
@ -4777,6 +5082,7 @@ class EstimateDetailView(LoginRequiredMixin, PermissionRequiredMixin, DetailView
# calculator = CarFinanceCalculator(estimate)
# finance_data = calculator.get_finance_data()
finance_data = get_finance_data(estimate,dealer)
invoice_obj = InvoiceModel.objects.all().filter(ce_model=estimate).first()
kwargs["data"] = finance_data
@ -4785,7 +5091,7 @@ class EstimateDetailView(LoginRequiredMixin, PermissionRequiredMixin, DetailView
car = estimate.get_itemtxs_data()[0].first().item_model.car
selected_items = car.additional_services.filter(dealer=dealer)
form = forms.AdditionalFinancesForm()
form.fields["additional_finances"].queryset = form.fields["additional_finances"].queryset.filter(dealer=dealer) # TODO : check later
form.fields["additional_finances"].queryset = form.fields["additional_finances"].queryset.filter(dealer=dealer) #
form.initial["additional_finances"] = selected_items
kwargs["additionals_form"] = form
except Exception as e:
@ -4793,6 +5099,16 @@ class EstimateDetailView(LoginRequiredMixin, PermissionRequiredMixin, DetailView
return super().get_context_data(**kwargs)
class EstimatePrintView(EstimateDetailView):
"""
A view to render a printer-friendly version of the estimate.
It reuses the data-fetching logic from EstimateDetailView but
uses a dedicated, stripped-down print template.
"""
template_name = "sales/estimates/estimate_preview.html"
@login_required
@permission_required("inventory.add_saleorder", raise_exception=True)
def create_sale_order(request, dealer_slug, pk):
@ -4844,7 +5160,8 @@ def create_sale_order(request, dealer_slug, pk):
f"KeyError: 'car_info' or 'status' key missing when attempting to update status to 'sold' for item.item_model PK: {getattr(item.item_model, 'pk', 'N/A')}."
)
pass
item.item_model.car.sold_date=timezone.now() # to be checked added by faheed
item.item_model.car.save()# to be checked added byfaheed
item.item_model.car.mark_as_sold()
messages.success(request, "Sale Order created successfully")
@ -10491,7 +10808,7 @@ def upload_cars(request, dealer_slug, pk=None):
response = redirect("upload_cars", dealer_slug=dealer_slug, pk=pk)
if po_item.status == "uploaded":
messages.add_message(request, messages.ERROR, "Item already uploaded.")
messages.add_message(request, messages.SUCCESS, "Item uploaded Sucessfully.")
return redirect(
"view_items_inventory",
dealer_slug=dealer_slug,
@ -10810,12 +11127,14 @@ def purchase_report_csv_export(request,dealer_slug):
def car_sale_report_view(request, dealer_slug):
dealer = get_object_or_404(models.Dealer, slug=dealer_slug)
cars_sold = models.Car.objects.filter(dealer=dealer, status='sold')
# Get filter parameters from the request
selected_make = request.GET.get('make')
selected_model = request.GET.get('model')
selected_serie = request.GET.get('serie')
selected_year = request.GET.get('year')
selected_stock_type=request.GET.get('stock_type')
# Apply filters to the queryset
if selected_make:
@ -10826,78 +11145,115 @@ def car_sale_report_view(request, dealer_slug):
cars_sold = cars_sold.filter(id_car_serie__name=selected_serie)
if selected_year:
cars_sold = cars_sold.filter(year=selected_year)
if selected_stock_type:
cars_sold = cars_sold.filter(stock_type=selected_stock_type)
# Get distinct values for filter dropdowns
makes = models.Car.objects.filter(dealer=dealer, status='sold').values_list('id_car_make__name', flat=True).distinct()
models_qs = models.Car.objects.filter(dealer=dealer, status='sold').values_list('id_car_model__name', flat=True).distinct()
series = models.Car.objects.filter(dealer=dealer, status='sold').values_list(
'id_car_serie__name', flat=True).distinct()
series = models.Car.objects.filter(dealer=dealer, status='sold').values_list('id_car_serie__name', flat=True).distinct()
stock_types=models.Car.objects.filter(dealer=dealer, status='sold').values_list('stock_type', flat=True).distinct()
years = models.Car.objects.filter(dealer=dealer, status='sold').values_list('year', flat=True).distinct().order_by('-year')
# # Calculate summary data for the filtered results
total_cars_sold=cars_sold.count()
total_revenue_from_cars = sum([ car.final_price for car in cars_sold])
total_vat_on_cars=sum([car.vat_amount for car in cars_sold])
total_revenue = cars_sold.aggregate(total_revenue=Sum('finances__marked_price'))['total_revenue'] or 0
# total_vat = cars_sold.aggregate(total_vat=Sum('finances__vat_amount'))['total_vat'] or 0
total_discount = cars_sold.aggregate(total_discount=Sum('finances__discount_amount'))['total_discount'] or 0
total_revenue_from_additonals=sum([car.get_additional_services()['total'] for car in cars_sold])
total_vat_from_additonals=sum([car.get_additional_services()['services_vat'] for car in cars_sold])
total_vat_collected = total_vat_on_cars+total_vat_from_additonals
total_revenue_collected=total_revenue_from_cars+total_revenue_from_additonals
total_discount = sum([car.discount for car in cars_sold])
current_time = timezone.now().strftime("%Y-%m-%d %H:%M:%S")
context = {
'cars_sold': cars_sold,
'total_cars_sold':total_cars_sold,
'current_time': current_time,
'dealer': dealer,
'total_revenue': total_revenue,
# 'total_vat': total_vat,
'total_revenue_from_cars': total_revenue_from_cars,
'total_revenue_from_additonals':total_revenue_from_additonals,
'total_revenue_collected': total_revenue_collected,
'total_vat_on_cars':total_vat_on_cars,
'total_vat_from_additonals':total_vat_from_additonals,
'total_vat_collected':total_vat_collected,
'total_discount': total_discount,
'makes': makes,
'models': models_qs,
'series': series,
'years': years,
'stock_types':stock_types,
'selected_make': selected_make,
'selected_model': selected_model,
'selected_serie': selected_serie,
'selected_year': selected_year,
'selected_stock_type':selected_stock_type,
}
return render(request, 'ledger/reports/car_sale_report.html', context)
def car_sale_report_csv_export(request,dealer_slug):
@login_required
def car_sale_report_csv_export(request, dealer_slug):
response = HttpResponse(content_type='text/csv')
current_time = timezone.now().strftime("%Y-%m-%d %H:%M:%S")
current_time = timezone.now().strftime("%Y-%m-%d_%H-%M-%S")
filename = f"sales_report_{dealer_slug}_{current_time}.csv"
response['Content-Disposition'] = f'attachment; filename="{filename}"'
writer = csv.writer(response)
header=[
'Make',
'VIN',
'Model',
'Year',
'Serie',
'Trim',
'Mileage',
'Stock Type',
'Created Date',
'Sold Date',
'Cost Price',
'Marked Price',
'Discount Amount',
'Selling Price',
'Tax Amount',
'Invoice Number',
# Define the CSV header based on your HTML table headers
header = [
'VIN', 'Make', 'Model', 'Year', 'Serie', 'Trim', 'Mileage',
'Stock Type', 'Created Date', 'Sold Date', 'Cost Price',
'Marked Price', 'Discount Amount', 'Selling Price',
'VAT on Car', 'Services Price', 'VAT on Services', 'Final Total',
'Invoice Number'
]
writer.writerow(header)
dealer=get_object_or_404(models.Dealer,slug=dealer_slug)
cars_sold=models.Car.objects.filter(dealer=dealer,status='sold')
dealer = get_object_or_404(models.Dealer, slug=dealer_slug)
cars_sold = models.Car.objects.filter(dealer=dealer, status='sold')
# Apply filters from the request, just like in your HTML view
selected_make = request.GET.get('make')
selected_model = request.GET.get('model')
selected_serie = request.GET.get('serie')
selected_year = request.GET.get('year')
selected_stock_type = request.GET.get('stock_type')
if selected_make:
cars_sold = cars_sold.filter(id_car_make__name=selected_make)
if selected_model:
cars_sold = cars_sold.filter(id_car_model__name=selected_model)
if selected_serie:
cars_sold = cars_sold.filter(id_car_serie__name=selected_serie)
if selected_year:
cars_sold = cars_sold.filter(year=selected_year)
if selected_stock_type:
cars_sold = cars_sold.filter(stock_type=selected_stock_type)
# Write the data for the filtered cars
for car in cars_sold:
writer.writerow([
# Fetching data for the additional services
additional_services = car.get_additional_services()
services_total_price = additional_services['total']
services_vat_amount = additional_services['services_vat']
# Checking for the invoice number to avoid errors on cars without one
invoice_number = None
sold_date = None
if car.invoice:
invoice_number = car.invoice.invoice_number
sold_date = car.invoice.date_paid
writer.writerow([
car.vin,
car.id_car_make.name,
car.id_car_model.name,
@ -10907,13 +11263,16 @@ def car_sale_report_csv_export(request,dealer_slug):
car.mileage if car.mileage else '0',
car.stock_type,
car.created_at.strftime("%Y-%m-%d %H:%M:%S") if car.created_at else '',
car.sold_date.strftime("%Y-%m-%d %H:%M:%S") if car.sold_date else '',
sold_date.strftime("%Y-%m-%d %H:%M:%S") if sold_date else '',
car.cost_price,
car.marked_price,
car.discount_amount,
car.selling_price,
car.vat_amount, # TODO : check later
car.item_model.invoicemodel_set.first().invoice_number
car.discount, # Ensure this property returns a number
car.final_price, # Selling Price without VAT
car.vat_amount, # VAT on the car
services_total_price, # Total services without VAT
services_vat_amount, # VAT on services
car.final_price_plus_services_plus_vat,
invoice_number,
])
return response

View File

@ -0,0 +1,218 @@
{% extends 'base.html' %}
{% block content %}
<div class="main-content flex-grow-1 container-fluid mt-4 mb-3">
<div class="d-flex justify-content-between align-items-center mb-5 pb-3 border-bottom">
<h2 class="h3 fw-bold mb-0">Accountant Dashboard<i class="fas fa-chart-area text-primary ms-2"></i></h2>
<div class="dropdown">
<button class="btn btn-outline-secondary dropdown-toggle" type="button" data-bs-toggle="dropdown" aria-expanded="false">
Last 30 Days
</button>
<ul class="dropdown-menu dropdown-menu-end shadow">
<li><a class="dropdown-item" href="#">Today</a></li>
<li><a class="dropdown-item" href="#">Last 7 Days</a></li>
<li><a class="dropdown-item" href="#">Last 90 Days</a></li>
</ul>
</div>
</div>
<div class="row g-4 mb-5">
<div class="col-sm-6 col-md-4 col-lg-3">
<div class="card h-100 shadow-sm border-0">
<div class="card-body d-flex flex-column justify-content-between p-4">
<div>
<p class="text-uppercase text-muted fw-bold small mb-1">Total Revenue</p>
<h4 class="fw-bolder text-primary mb-3">$1.25M</h4>
</div>
<span class="badge bg-success-subtle text-success fw-bold p-2 rounded-pill d-inline-flex align-self-start">
+8% from last month
</span>
</div>
</div>
</div>
<div class="col-sm-6 col-md-4 col-lg-3">
<div class="card h-100 shadow-sm border-0">
<div class="card-body d-flex flex-column justify-content-between p-4">
<div>
<p class="text-uppercase text-muted fw-bold small mb-1">Net Profit</p>
<h4 class="fw-bolder text-success mb-3">$1.25M</h4>
</div>
<span class="badge bg-success-subtle text-success fw-bold p-2 rounded-pill d-inline-flex align-self-start">
+8% from last month
</span>
</div>
</div>
</div>
<div class="col-sm-6 col-md-4 col-lg-3">
<div class="card h-100 shadow-sm border-0">
<div class="card-body d-flex flex-column justify-content-between p-4">
<div>
<p class="text-uppercase text-muted fw-bold small mb-1">Gross Profit</p>
<h4 class="fw-bolder text-info mb-3">$1.25M</h4>
</div>
<span class="badge bg-success-subtle text-success fw-bold p-2 rounded-pill d-inline-flex align-self-start">
+8% from last month
</span>
</div>
</div>
</div>
<div class="col-sm-6 col-md-4 col-lg-3">
<div class="card h-100 shadow-sm border-0">
<div class="card-body d-flex flex-column justify-content-between p-4">
<div>
<p class="text-uppercase text-muted fw-bold small mb-1">Total Expense</p>
<h4 class="fw-bolder text-danger mb-3">$1.25M</h4>
</div>
<span class="badge bg-danger-subtle text-danger fw-bold p-2 rounded-pill d-inline-flex align-self-start">
+8% from last month
</span>
</div>
</div>
</div>
</div>
<h4 class="h5 fw-bold mb-3 text-dark">Breakdown by Vehicle Type</h4>
<div class="row g-4 mb-5">
<div class="col-md-6 col-lg-3">
<div class="card h-100 p-4 shadow-sm border-0">
<p class="text-primary fw-bold mb-1">New Cars</p>
<h4 class="fw-bolder mb-0">$1.25M</h4>
<p class="text-muted small">Total Revenue</p>
</div>
</div>
<div class="col-md-6 col-lg-3">
<div class="card h-100 p-4 shadow-sm border-0">
<p class="text-success fw-bold mb-1">Used Cars</p>
<h4 class="fw-bolder mb-0">$0.75M</h4>
<p class="text-muted small">Total Revenue</p>
</div>
</div>
<div class="col-md-6 col-lg-3">
<div class="card h-100 p-4 shadow-sm border-0">
<p class="text-info fw-bold mb-1">New Cars</p>
<h4 class="fw-bolder mb-0">$0.25M</h4>
<p class="text-muted small">Net Profit</p>
</div>
</div>
<div class="col-md-6 col-lg-3">
<div class="card h-100 p-4 shadow-sm border-0">
<p class="text-warning fw-bold mb-1">Used Cars</p>
<h4 class="fw-bolder mb-0">$0.15M</h4>
<p class="text-muted small">Net Profit</p>
</div>
</div>
</div>
<div class="row g-4">
<div class="col-12">
<div class="card h-100 shadow-sm border-0">
<div class="card-header bg-white border-bottom-0">
<h5 class="fw-bold mb-0 text-dark">Monthly Revenue & Profit</h5>
</div>
<div class="card-body" style="height: 400px;">
<canvas id="revenueProfitChart"></canvas>
</div>
</div>
</div>
</div>
</div>
<script src="https://cdn.jsdelivr.net/npm/chart.js"></script>
{% endblock %}
{% block customJS %}
<script>
// Define a color palette that aligns with the Phoenix template
const primaryColor = '#7249b6'; // A vibrant purple
const secondaryColor = '#8193a6'; // A muted gray/blue
const successColor = '#00d074'; // A bright green
const dangerColor = '#e63757'; // A deep red
const ctx = document.getElementById('revenueProfitChart').getContext('2d');
new Chart(ctx, {
type: 'line',
data: {
labels: ['Jan', 'Feb', 'Mar', 'Apr', 'May', 'Jun', 'Jul', 'Aug', 'Sep', 'Oct', 'Nov', 'Dec'],
datasets: [
{
label: 'Monthly Revenue',
data: [120000, 150000, 130000, 180000, 200000, 175000, 190000, 220000, 210000, 250000, 240000, 280000],
borderColor: primaryColor,
backgroundColor: 'rgba(114, 73, 182, 0.1)',
tension: 0.4,
fill: true,
pointBackgroundColor: primaryColor,
pointRadius: 5,
pointHoverRadius: 8
},
{
label: 'Monthly Net Profit',
data: [25000, 35000, 28000, 40000, 45000, 38000, 42000, 50000, 48000, 55000, 52000, 60000],
borderColor: successColor,
backgroundColor: 'rgba(0, 208, 116, 0.1)',
tension: 0.4,
fill: true,
pointBackgroundColor: successColor,
pointRadius: 5,
pointHoverRadius: 8
}
]
},
options: {
responsive: true,
maintainAspectRatio: false,
plugins: {
legend: {
display: true,
labels: {
color: secondaryColor,
boxWidth: 20
}
},
tooltip: {
backgroundColor: 'rgba(33, 37, 41, 0.9)',
titleColor: 'white',
bodyColor: 'white',
padding: 10,
callbacks: {
label: function(context) {
let label = context.dataset.label || '';
if (label) {
label += ': ';
}
if (context.parsed.y !== null) {
label += new Intl.NumberFormat('en-US', { style: 'currency', currency: 'USD' }).format(context.parsed.y);
}
return label;
}
}
}
},
scales: {
x: {
grid: {
color: 'rgba(0, 0, 0, 0.05)'
},
ticks: {
color: secondaryColor
},
border: {
color: secondaryColor
}
},
y: {
grid: {
color: 'rgba(0, 0, 0, 0.05)'
},
ticks: {
color: secondaryColor
},
border: {
color: secondaryColor
}
}
}
}
});
</script>
{% endblock %}

View File

@ -0,0 +1,349 @@
{% extends 'base.html' %}
{% load i18n %}
{% load tenhal_tag %}
{% block title %}
{{ _("Dealership Dashboard") |capfirst }}
{% endblock title %}
{% block content%}
<div class="main-content flex-grow-1 container-fluid mt-4 mb-3">
<div class="d-flex flex-column flex-md-row justify-content-between align-items-md-center mb-5 pb-3 border-bottom">
<h2 class="h3 fw-bold text-dark mb-3 mb-md-0">Business Health Dashboard <i class="fas fa-chart-area text-primary ms-2"></i></h2>
<form method="GET" class="date-filter-form">
<div class="row g-3">
<div class="col-12 col-md-4">
<label for="start-date" class="form-label">Start Date</label>
<input type="date" class="form-control" id="start-date" name="start_date"
value="{{ start_date|date:'Y-m-d' }}" required>
</div>
<div class="col-12 col-md-4">
<label for="end-date" class="form-label">End Date</label>
<input type="date" class="form-control" id="end-date" name="end_date"
value="{{ end_date|date:'Y-m-d' }}" required>
</div>
<div class="col-12 col-md-4 d-flex align-items-end">
<button type="submit" class="btn btn-primary w-100">Apply Filter</button>
</div>
</div>
</form>
</div>
<div class="row g-4 mb-5">
<div class="col-sm-6 col-md-4 col-lg-3">
<div class="card h-100 shadow-sm border-0">
<div class="card-body d-flex flex-column justify-content-between p-4">
<div>
<p class="text-uppercase text-muted fw-bold small mb-1">Total Cost of Cars Sold</p>
<h4 class="fw-bolder text-secondary mb-3">{{total_cost_of_cars_sold|floatformat:2}}<span class="icon-saudi_riyal"></span></h4>
</div>
<span class="badge bg-success-subtle text-success fw-bold p-2 rounded-pill d-inline-flex align-self-start">
+8% from last month
</span>
</div>
</div>
</div>
<div class="col-sm-6 col-md-4 col-lg-3">
<div class="card h-100 shadow-sm border-0">
<div class="card-body d-flex flex-column justify-content-between p-4">
<div>
<p class="text-uppercase text-muted fw-bold small mb-1">Total Revenue from cars</p>
<h4 class="fw-bolder text-primary mb-3">{{total_revenue_from_cars|floatformat:2}}<span class="icon-saudi_riyal"></span></h4>
</div>
<span class="badge bg-success-subtle text-success fw-bold p-2 rounded-pill d-inline-flex align-self-start">
+8% from last month
</span>
</div>
</div>
</div>
<div class="col-sm-6 col-md-4 col-lg-3">
<div class="card h-100 shadow-sm border-0">
<div class="card-body d-flex flex-column justify-content-between p-4">
<div>
<p class="text-uppercase text-muted fw-bold small mb-1">Net Profit From Cars</p>
<h4 class="fw-bolder text-success mb-3">{{net_profit_from_cars|floatformat:2}}<span class="icon-saudi_riyal"></span></h4>
</div>
<span class="badge bg-success-subtle text-success fw-bold p-2 rounded-pill d-inline-flex align-self-start">
+8% from last month
</span>
</div>
</div>
</div>
<div class="col-sm-6 col-md-4 col-lg-3">
<div class="card h-100 shadow-sm border-0">
<div class="card-body d-flex flex-column justify-content-between p-4">
<div>
<p class="text-uppercase text-muted fw-bold small mb-1">Total Discount on cars</p>
<h4 class="fw-bolder text-primary mb-3">{{total_discount_on_cars|floatformat:2}}<span class="icon-saudi_riyal"></span></h4>
</div>
<span class="badge bg-success-subtle text-success fw-bold p-2 rounded-pill d-inline-flex align-self-start">
+8% from last month
</span>
</div>
</div>
</div>
<div class="col-sm-6 col-md-4 col-lg-3">
<div class="card h-100 shadow-sm border-0">
<div class="card-body d-flex flex-column justify-content-between p-4">
<div>
<p class="text-uppercase text-muted fw-bold small mb-1">Total VAT collected from cars</p>
<h4 class="fw-bolder text-primary mb-3">{{total_vat_collected_from_cars|floatformat:2}}<span class="icon-saudi_riyal"></span></h4>
</div>
<span class="badge bg-success-subtle text-success fw-bold p-2 rounded-pill d-inline-flex align-self-start">
+8% from last month
</span>
</div>
</div>
</div>
<div class="col-sm-6 col-md-4 col-lg-3">
<div class="card h-100 shadow-sm border-0">
<div class="card-body d-flex flex-column justify-content-between p-4">
<div>
<p class="text-uppercase text-muted fw-bold small mb-1">Total Revenue from Services</p>
<h4 class="fw-bolder text-primary mb-3">{{total_revenue_from_services|floatformat:2}}<span class="icon-saudi_riyal"></span></h4>
</div>
<span class="badge bg-success-subtle text-success fw-bold p-2 rounded-pill d-inline-flex align-self-start">
+8% from last month
</span>
</div>
</div>
</div>
<div class="col-sm-6 col-md-4 col-lg-3">
<div class="card h-100 shadow-sm border-0">
<div class="card-body d-flex flex-column justify-content-between p-4">
<div>
<p class="text-uppercase text-muted fw-bold small mb-1">Total VAT collected from Services</p>
<h4 class="fw-bolder text-primary mb-3">{{total_vat_collected_from_services|floatformat:2}}<span class="icon-saudi_riyal"></span></h4>
</div>
<span class="badge bg-success-subtle text-success fw-bold p-2 rounded-pill d-inline-flex align-self-start">
+8% from last month
</span>
</div>
</div>
</div>
<div class="col-sm-6 col-md-4 col-lg-3">
<div class="card h-100 shadow-sm border-0">
<div class="card-body d-flex flex-column justify-content-between p-4">
<div>
<p class="text-uppercase text-muted fw-bold small mb-1">{% trans "Total Revenue Generated" %}</p>
<h4 class="fw-bolder text-primary mb-3">{{total_revenue_generated|floatformat:2}}<span class="icon-saudi_riyal"></span></h4>
</div>
<span class="badge bg-success-subtle text-success fw-bold p-2 rounded-pill d-inline-flex align-self-start">
+8% from last month
</span>
</div>
</div>
</div>
<div class="col-sm-6 col-md-4 col-lg-3">
<div class="card h-100 shadow-sm border-0">
<div class="card-body d-flex flex-column justify-content-between p-4">
<div>
<p class="text-uppercase text-muted fw-bold small mb-1">{% trans "Total VAT Collected" %}</p>
<h4 class="fw-bolder text-primary mb-3">{{total_vat_collected|floatformat:2}}<span class="icon-saudi_riyal"></span></h4>
</div>
<span class="badge bg-success-subtle text-success fw-bold p-2 rounded-pill d-inline-flex align-self-start">
+8% from last month
</span>
</div>
</div>
</div>
<div class="col-sm-6 col-md-4 col-lg-3">
<div class="card h-100 shadow-sm border-0">
<div class="card-body d-flex flex-column justify-content-between p-4">
<div>
<p class="text-uppercase text-muted fw-bold small mb-1">Total Expense</p>
<h4 class="fw-bolder text-danger mb-3">$1.25M</h4>
</div>
<span class="badge bg-danger-subtle text-danger fw-bold p-2 rounded-pill d-inline-flex align-self-start">
-2% from last month
</span>
</div>
</div>
</div>
<div class="col-sm-6 col-md-4 col-lg-3">
<div class="card h-100 shadow-sm border-0">
<div class="card-body d-flex flex-column justify-content-between p-4">
<div>
<p class="text-uppercase text-muted fw-bold small mb-1">Gross Profit</p>
<h4 class="fw-bolder text-info mb-3">$1.25M</h4>
</div>
<span class="badge bg-success-subtle text-success fw-bold p-2 rounded-pill d-inline-flex align-self-start">
+8% from last month
</span>
</div>
</div>
</div>
<div class="col-sm-6 col-md-4 col-lg-3">
<div class="card h-100 shadow-sm border-0">
<div class="card-body d-flex flex-column justify-content-between p-4">
<div>
<p class="text-uppercase text-muted fw-bold small mb-1">Total VAT Collected</p>
<h4 class="fw-bolder text-primary mb-3">{{total_vat_collected|floatformat:2}}<span class="icon-saudi_riyal"></span></h4>
</div>
<span class="badge bg-success-subtle text-success fw-bold p-2 rounded-pill d-inline-flex align-self-start">
+8% from last month
</span>
</div>
</div>
</div>
<div class="col-sm-6 col-md-4 col-lg-3">
<div class="card h-100 shadow-sm border-0">
<div class="card-body d-flex flex-column justify-content-between p-4">
<div>
<p class="text-uppercase text-muted fw-bold small mb-1">Total Cars Sold</p>
<h4 class="fw-bolder text-success mb-3">{{ total_cars_sold }}</h4>
</div>
<span class="badge bg-success-subtle text-success fw-bold p-2 rounded-pill d-inline-flex align-self-start">
+5 units from last month
</span>
</div>
</div>
</div>
</div>
<div class="row g-4">
<div class="col-lg-8">
<div class="card h-100 shadow-sm border-0">
<div class="card-header bg-white border-bottom-0">
<h5 class="fw-bold mb-0 text-dark">Monthly Revenue & Profit</h5>
</div>
<div class="card-body" style="height: 400px;">
<canvas id="revenueProfitChart"></canvas>
</div>
</div>
</div>
<div class="col-lg-4">
<div class="card h-100 shadow-sm border-0">
<div class="card-header bg-white border-bottom-0">
<h5 class="fw-bold mb-0 text-dark">Monthly Cars Sold</h5>
</div>
<div class="card-body d-flex align-items-center justify-content-center" style="height: 400px;">
<canvas id="CarsSoldByMonthChart"></canvas>
</div>
</div>
</div>
</div>
</div>
<script src="https://cdn.jsdelivr.net/npm/chart.js"></script>
{% endblock content %}
{% block customJS %}
<script>
// Define a color palette that aligns with the Phoenix template
const primaryColor = '#7249b6'; // A vibrant purple
const secondaryColor = '#8193a6'; // A muted gray/blue
const successColor = '#00d074'; // A bright green
const dangerColor = '#e63757'; // A deep red
// Monthly Cars Sold (Bar Chart)
const ctx1 = document.getElementById('CarsSoldByMonthChart').getContext('2d');
new Chart(ctx1, {
type: 'bar',
data: {
labels: ['Jan', 'Feb', 'Mar', 'Apr', 'May', 'Jun', 'Jul', 'Aug', 'Sep', 'Oct', 'Nov', 'Dec'],
datasets: [{
label: 'Total Cars Sold',
data: [2, 3, 10, 4, 30, 12, 8, 9, 20, 12, 15, 35],
backgroundColor: primaryColor,
borderColor: primaryColor,
borderWidth: 1
}]
},
options: {
responsive: true,
maintainAspectRatio: false,
plugins: {
legend: { display: false }
},
scales: {
y: {
beginAtZero: true,
grid: { color: 'rgba(0, 0, 0, 0.05)' },
ticks: { color: secondaryColor }
},
x: {
grid: { display: false },
ticks: { color: secondaryColor }
}
}
}
});
// Monthly Revenue & Profit (Line Chart)
const ctx2 = document.getElementById('revenueProfitChart').getContext('2d');
new Chart(ctx2, {
type: 'line',
data: {
labels: ['Jan', 'Feb', 'Mar', 'Apr', 'May', 'Jun', 'Jul', 'Aug', 'Sep', 'Oct', 'Nov', 'Dec'],
datasets: [
{
label: 'Monthly Revenue',
data: [120000, 150000, 130000, 180000, 200000, 175000, 190000, 220000, 210000, 250000, 240000, 280000],
borderColor: primaryColor,
backgroundColor: 'rgba(114, 73, 182, 0.1)', // Using primaryColor with transparency
tension: 0.4,
fill: true,
pointBackgroundColor: primaryColor,
pointRadius: 5,
pointHoverRadius: 8
},
{
label: 'Monthly Net Profit',
data: [25000, 35000, 28000, 40000, 45000, 38000, 42000, 50000, 48000, 55000, 52000, 60000],
borderColor: successColor,
backgroundColor: 'rgba(0, 208, 116, 0.1)', // Using successColor with transparency
tension: 0.4,
fill: true,
pointBackgroundColor: successColor,
pointRadius: 5,
pointHoverRadius: 8
}
]
},
options: {
responsive: true,
maintainAspectRatio: false,
plugins: {
legend: {
display: true,
labels: { color: '#495057', boxWidth: 20 }
},
tooltip: {
backgroundColor: 'rgba(33, 37, 41, 0.9)',
titleColor: 'white',
bodyColor: 'white',
padding: 10,
callbacks: {
label: function(context) {
let label = context.dataset.label || '';
if (label) {
label += ': ';
}
if (context.parsed.y !== null) {
label += new Intl.NumberFormat('en-US', { style: 'currency', currency: 'USD' }).format(context.parsed.y);
}
return label;
}
}
}
},
scales: {
x: {
grid: { color: 'rgba(0, 0, 0, 0.05)' },
ticks: { color: secondaryColor },
border: { color: secondaryColor }
},
y: {
grid: { color: 'rgba(0, 0, 0, 0.05)' },
ticks: { color: secondaryColor },
border: { color: secondaryColor }
}
}
}
});
</script>
{% endblock %}

View File

@ -0,0 +1,300 @@
{% extends 'base.html' %}
{% block content %}
<div class="main-content flex-grow-1 container-fluid mt-4 mb-3">
<div class="d-flex justify-content-between align-items-center mb-5 pb-3 border-bottom">
<h2 class="h3 fw-bold mb-0">Inventory Dashboard <i class="fas fa-warehouse text-primary ms-2"></i></h2>
<div class="dropdown">
<button class="btn btn-outline-secondary dropdown-toggle" type="button" data-bs-toggle="dropdown" aria-expanded="false">
Last 30 Days
</button>
<ul class="dropdown-menu dropdown-menu-end shadow">
<li><a class="dropdown-item" href="#">Today</a></li>
<li><a class="dropdown-item" href="#">Last 7 Days</a></li>
<li><a class="dropdown-item" href="#">Last 90 Days</a></li>
</ul>
</div>
</div>
<div class="row g-4 mb-5">
<div class="col-sm-6 col-lg-3">
<div class="card h-100 shadow-sm border-0">
<div class="card-body d-flex flex-column justify-content-between p-4">
<div>
<p class="text-uppercase text-muted fw-bold small mb-1">Total Cars in Inventory</p>
<h4 class="fw-bolder text-primary mb-3">{{ total_cars_in_inventory }}</h4>
</div>
<span class="badge bg-success-subtle text-success fw-bold p-2 rounded-pill d-inline-flex align-self-start">
+5 units from last month
</span>
</div>
</div>
</div>
<div class="col-sm-6 col-lg-3">
<div class="card h-100 shadow-sm border-0">
<div class="card-body d-flex flex-column justify-content-between p-4">
<div>
<p class="text-uppercase text-muted fw-bold small mb-1">New Cars in Inventory</p>
<h4 class="fw-bolder text-success mb-3">{{ new_cars_in_inventory }}</h4>
</div>
<span class="badge bg-success-subtle text-success fw-bold p-2 rounded-pill d-inline-flex align-self-start">
+5 units from last month
</span>
</div>
</div>
</div>
<div class="col-sm-6 col-lg-3">
<div class="card h-100 shadow-sm border-0">
<div class="card-body d-flex flex-column justify-content-between p-4">
<div>
<p class="text-uppercase text-muted fw-bold small mb-1">Used Cars in Inventory</p>
<h4 class="fw-bolder text-info mb-3">{{ old_cars_in_inventory }}</h4>
</div>
<span class="badge bg-success-subtle text-success fw-bold p-2 rounded-pill d-inline-flex align-self-start">
+5 units from last month
</span>
</div>
</div>
</div>
<div class="col-sm-6 col-lg-3">
<div class="card h-100 shadow-sm border-0">
<div class="card-body d-flex flex-column justify-content-between p-4">
<div>
<p class="text-uppercase text-muted fw-bold small mb-1">Inventory Value</p>
<h4 class="fw-bolder text-warning mb-3">$5.8M</h4>
</div>
<span class="badge bg-danger-subtle text-danger fw-bold p-2 rounded-pill d-inline-flex align-self-start">
-2% from last month
</span>
</div>
</div>
</div>
</div>
<div class="row g-4 mb-5">
<div class="col-sm-6 col-lg-3">
<div class="card h-100 p-4 shadow-sm border-0">
<p class="text-warning fw-bold mb-1">New Car Value</p>
<h4 class="fw-bolder mb-0">$3.2M</h4>
<p class="text-muted small">Total new cars value</p>
</div>
</div>
<div class="col-sm-6 col-lg-3">
<div class="card h-100 p-4 shadow-sm border-0">
<p class="text-info fw-bold mb-1">Used Car Value</p>
<h4 class="fw-bolder mb-0">$2.6M</h4>
<p class="text-muted small">Total used cars value</p>
</div>
</div>
<div class="col-sm-6 col-lg-3">
<div class="card h-100 p-4 shadow-sm border-0">
<p class="text-danger fw-bold mb-1">Average Time on Lot</p>
<h4 class="fw-bolder mb-0">10 days</h4>
<p class="text-muted small">Average for all vehicles</p>
</div>
</div>
<div class="col-sm-6 col-lg-3">
<div class="card h-100 p-4 shadow-sm border-0">
<p class="text-danger fw-bold mb-1">Aging Inventory</p>
<h4 class="fw-bolder mb-0">12 cars</h4>
<span class="badge bg-success-subtle text-success fw-bold p-2 rounded-pill d-inline-flex align-self-start">
-4 cars from last month
</span>
</div>
</div>
</div>
<!--charts-->
<div class="row g-4">
<div class="col-lg-6">
<div class="card h-100 shadow-sm border-0">
<div class="card-header bg-white border-bottom-0">
<h5 class="fw-bold mb-0 text-dark">Inventory by Make</h5>
</div>
<div class="card-body d-flex align-items-center justify-content-center" style="height: 400px;">
<canvas id="inventoryByBrandChart"></canvas>
</div>
</div>
</div>
<div class="col-lg-6">
<div class="card h-100 shadow-sm border-0">
<div class="card-header bg-white border-bottom-0">
<h5 class="fw-bold mb-0 text-dark">Car Models by Make in Inventory</h5>
</div>
<div class="row justify-content-center">
<div class="col-md-6 mb-4 mt-3">
<label for="carMakeSelect" class="form-label fs-8 mb-2">Select a Car Make:</label>
<select id="carMakeSelect" class="form-select">
<option value="" disabled selected>-- Choose a make --</option>
</select>
</div>
</div>
<div class="card-body d-flex align-items-center justify-content-center" style="height: 400px;">
<canvas id="inventoryChart"></canvas>
</div>
</div>
</div>
</div>
</div>
<script src="https://cdn.jsdelivr.net/npm/chart.js"></script>
{% endblock content %}
{% block customJS %}
<script>
// Define a custom color palette that matches the Phoenix template
const primaryColor = '#7249b6'; // A vibrant purple
const secondaryColor = '#8193a6'; // A muted gray/blue
const successColor = '#00d074'; // A bright green
const infoColor = '#0085ff'; // A medium blue
const warningColor = '#ffc83b'; // A yellow
const dangerColor = '#e63757'; // A deep red
const ctx4 = document.getElementById('inventoryByBrandChart').getContext('2d');
// Chart.js configuration for the Pie Chart
new Chart(ctx4, {
type: 'pie',
data: {
labels: ['Toyota', 'Ford', 'Honda', 'BMW', 'Other'],
datasets: [{
label: 'Car Count by Make',
data: [45, 30, 25, 15, 10], // Sample data for car counts
backgroundColor: [
primaryColor,
successColor,
warningColor,
infoColor,
secondaryColor
],
hoverOffset: 15,
}]
},
options: {
responsive: true,
maintainAspectRatio: false,
plugins: {
legend: {
position: 'right', // Places the legend on the right side
labels: {
color: secondaryColor, // Use a muted color for a subtle look
font: {
size: 14
}
}
},
tooltip: {
backgroundColor: 'rgba(33, 37, 41, 0.9)', // Bootstrap dark
titleColor: '#fff',
bodyColor: '#fff',
callbacks: {
label: function(context) {
const label = context.label || '';
const value = context.parsed || 0;
const total = context.dataset.data.reduce((a, b) => a + b, 0);
const percentage = ((value / total) * 100).toFixed(2);
return `${label}: ${value} cars (${percentage}%)`;
}
}
}
}
}
});
//chart for number of cars of each model in the inventory
// Sample data representing your inventory
const inventoryData = [
{ make: 'Ford', model: 'Mustang', count: 5 },
{ make: 'Ford', model: 'F-150', count: 12 },
{ make: 'Ford', model: 'Explorer', count: 8 },
{ make: 'Toyota', model: 'Camry', count: 10 },
{ make: 'Toyota', model: 'Corolla', count: 15 },
{ make: 'Toyota', model: 'RAV4', count: 7 },
{ make: 'BMW', model: 'X5', count: 4 },
{ make: 'BMW', model: '3 Series', count: 6 },
{ make: 'BMW', model: 'i8', count: 2 },
];
// Get the unique list of car makes for the dropdown
const carMakes = [...new Set(inventoryData.map(item => item.make))];
const selectElement = document.getElementById('carMakeSelect');
const canvasElement = document.getElementById('inventoryChart');
let chartInstance = null; // Stores the current chart instance
// Populate the dropdown menu
carMakes.forEach(make => {
const option = document.createElement('option');
option.value = make;
option.textContent = make;
selectElement.appendChild(option);
});
// Function to create or update the chart
function renderChart(data, type = 'bar') {
// Destroy the old chart instance if it exists
if (chartInstance) {
chartInstance.destroy();
}
const chartLabels = data.map(item => item.model);
const chartValues = data.map(item => item.count);
// Generate random colors for the chart segments
const backgroundColor = chartLabels.map(() => `rgba(${Math.floor(Math.random() * 255)}, ${Math.floor(Math.random() * 255)}, ${Math.floor(Math.random() * 255)}, 0.6)`);
const borderColor = backgroundColor.map(color => color.replace('0.6', '1'));
// Create the new chart
chartInstance = new Chart(canvasElement, {
type: type, // Can be 'bar' or 'pie'
data: {
labels: chartLabels,
datasets: [{
label: 'Number of Cars',
data: chartValues,
backgroundColor: backgroundColor,
borderColor: borderColor,
borderWidth: 1
}]
},
options: {
responsive: true,
scales: {
y: {
beginAtZero: true
}
},
plugins: {
legend: {
display: true
}
}
}
});
}
// Event listener for the dropdown menu
selectElement.addEventListener('change', (event) => {
const selectedMake = event.target.value;
if (selectedMake) {
const filteredData = inventoryData.filter(item => item.make === selectedMake);
// The second parameter can be 'pie' to switch chart types
renderChart(filteredData, 'bar'); // Use 'bar' for a bar chart
}
});
</script>
{% endblock %}

View File

@ -1,641 +0,0 @@
{% extends 'base.html' %}
{% block content %}
<div class="main-content flex-grow-1 mt-4 mb-3">
<div class="d-flex justify-content-between align-items-center mb-4">
<p class="fs-2">Dashboard Overview<i class="fas fa-chart-area ms-3 text-primary fs-3"></i></p>
<div class="dropdown">
<button class="btn btn-outline-secondary dropdown-toggle" type="button" data-bs-toggle="dropdown">
Last 30 Days
</button>
<ul class="dropdown-menu">
<li><a class="dropdown-item" href="#">Today</a></li>
<li><a class="dropdown-item" href="#">Last 7 Days</a></li>
<li><a class="dropdown-item" href="#">Last 90 Days</a></li>
</ul>
</div>
</div>
<div class="row g-4 mb-5">
<div class="col-md-6 col-lg-3">
<div class="card h-100 p-3">
<div class="card-body">
<h5 class="card-title text-primary">Total Revenue</h5>
<p class="stat-value">$1.25M</p>
<span class="text-success small">+8% from last month</span>
</div>
</div>
</div>
<div class="col-md-6 col-lg-3">
<div class="card h-100 p-3">
<div class="card-body">
<h5 class="card-title text-primary">Net Profit</h5>
<p class="stat-value">$1.25M</p>
<span class="text-success small">+8% from last month</span>
</div>
</div>
</div>
<div class="col-md-6 col-lg-3">
<div class="card h-100 p-3">
<div class="card-body">
<h5 class="card-title text-primary">Gross Profit</h5>
<p class="stat-value">$1.25M</p>
<span class="text-success small">+8% from last month</span>
</div>
</div>
</div>
<div class="col-md-6 col-lg-3">
<div class="card h-100 p-3">
<div class="card-body">
<h5 class="card-title text-danger">Total Expense</h5>
<p class="stat-value">$1.25M</p>
<span class="text-success small">+8% from last month</span>
</div>
</div>
</div>
<div class="col-md-6 col-lg-3">
<div class="card h-100 p-3">
<div class="card-body">
<h5 class="card-title text-primary">Total VAT collected</h5>
<p class="stat-value">$1.25M</p>
<span class="text-success small">+8% from last month</span>
</div>
</div>
</div>
<div class="col-md-6 col-lg-3">
<div class="card h-100 p-3">
<div class="card-body">
<h5 class="card-title text-success">Cars Sold</h5>
<p class="stat-value">{{sold_cars}}</p>
<span class="text-success small">+5 units from last month</span>
</div>
</div>
</div>
<div class="col-md-6 col-lg-3">
<div class="card h-100 p-3">
<div class="card-body">
<h5 class="card-title text-danger">Average Time on Lot</h5>
<p class="stat-value">10 days</p>
</div>
</div>
</div>
<div class="col-md-6 col-lg-3">
<div class="card h-100 p-3">
<div class="card-body">
<h5 class="card-title text-warning">Inventory Value</h5>
<p class="stat-value">$5.8M</p>
<span class="text-danger small">-2% from last month</span>
</div>
</div>
</div>
<div class="col-md-6 col-lg-3">
<div class="card h-100 p-3">
<div class="card-body">
<h5 class="card-title text-danger">Aging Inventory</h5>
<p class="stat-value">12</p>
<span class="text-success small">-4 cars from last month</span>
</div>
</div>
</div>
</div>
<div class="row g-4">
<div class="col-lg-8">
<div class="card h-100">
<div class="card-header">Monthly Revenue & Profit</div>
<div class="card-body chart-container">
<canvas id="revenueProfitChart"></canvas>
</div>
</div>
</div>
<div class="col-lg-4">
<div class="card h-100">
<div class="card-header">Monthly Cars Sold</div>
<div class="card-body d-flex align-items-center justify-content-center">
<canvas id="CarsSoldByMonthChart"></canvas>
</div>
</div>
</div>
<div class="col-lg-6">
<div class="card h-100">
<div class="card-header">Inventory By Make</div>
<div class="card-body d-flex align-items-center justify-content-center">
<canvas id="salesByBrandChart"></canvas>
</div>
</div>
</div>
<div class="col-lg-6">
<div class="card h-100">
<div class="card-header">Sales By Make</div>
<div class="card-body d-flex align-items-center justify-content-center">
<canvas id="inventoryByBrandChart"></canvas>
</div>
</div>
</div>
<div class="col-lg-6">
<div class="card h-100">
<div class="card-header">Salesperson Performance</div>
<div class="card-body chart-container">
<canvas id="salespersonChart"></canvas>
</div>
</div>
</div>
<div class="col-lg-6">
<div class="card h-100">
<div class="card-header">Top 5 Lead Sources</div>
<div class="card-body chart-container">
<canvas id="leadSourcesChart"></canvas>
</div>
</div>
</div>
<div class="col-lg-12">
<div class="card h-100">
<div class="card-header">Appointments by Weekday</div>
<div class="card-body chart-container">
<canvas id="appointmentsChart"></canvas>
</div>
</div>
</div>
<div class="col-lg-12">
<div class="card h-100">
<div class="card-header">Lead Conversion Funnel</div>
<div class="card-body d-flex align-items-center justify-content-center">
<canvas id="leadFunnelChart"></canvas>
</div>
</div>
</div>
</div>
</div>
<script src="https://cdn.jsdelivr.net/npm/chart.js"></script>
{% endblock content %}
{% block customJS%}
<script>
ctx1=document.getElementById('CarsSoldByMonthChart')
new Chart(ctx1,{
type: 'bar',
data: {
labels:['January','February','March','April','May','June','July','August','September','October', 'November','December' ],
datasets: [{
label: 'Total Cars Sold',
data: [2,3,10,4,30,12,8,9,20,12,15,35],
borderWidth: 1
}]
},
options: {
scales: {
y: {
beginAtZero: true
}
}
}
});
// Get the canvas context for the chart
const ctx2 = document.getElementById('revenueProfitChart').getContext('2d');
// Chart.js configuration
new Chart(ctx2, {
type: 'line', // Use a line chart
data: {
labels: ['Jan', 'Feb', 'Mar', 'Apr', 'May', 'Jun', 'Jul', 'Aug', 'Sep', 'Oct', 'Nov', 'Dec'],
datasets: [
{
label: 'Monthly Revenue',
data: [120000, 150000, 130000, 180000, 200000, 175000, 190000, 220000, 210000, 250000, 240000, 280000],
borderColor: '#007bff', // A vibrant blue
backgroundColor: 'rgba(0, 123, 255, 0.2)',
tension: 0.4, // Smooths the line
fill: true, // Fills the area under the line
pointBackgroundColor: '#007bff',
pointRadius: 5,
pointHoverRadius: 8
},
{
label: 'Monthly Net Profit',
data: [25000, 35000, 28000, 40000, 45000, 38000, 42000, 50000, 48000, 55000, 52000, 60000],
borderColor: '#28a745', // A strong green
backgroundColor: 'rgba(40, 167, 69, 0.2)',
tension: 0.4,
fill: true,
pointBackgroundColor: '#28a745',
pointRadius: 5,
pointHoverRadius: 8
}
]
},
options: {
responsive: true,
maintainAspectRatio: false,
plugins: {
legend: {
display: true,
labels: {
color: '#495057', // Darker legend text
boxWidth: 20
}
},
tooltip: {
backgroundColor: 'rgba(52, 58, 64, 0.9)', // Darker tooltip background
titleColor: 'white',
bodyColor: 'white',
callbacks: {
label: function(context) {
let label = context.dataset.label || '';
if (label) {
label += ': ';
}
if (context.parsed.y !== null) {
label += new Intl.NumberFormat('en-US', { style: 'currency', currency: 'USD' }).format(context.parsed.y);
}
return label;
}
}
}
},
scales: {
x: {
grid: {
color: 'rgba(0, 0, 0, 0.1)' // Lighter grid lines for better contrast
},
ticks: {
color: '#495057' // Darker text for x-axis labels
},
border: {
color: '#adb5bd' // A subtle border color
}
},
y: {
grid: {
color: 'rgba(0, 0, 0, 0.1)'
},
ticks: {
color: '#495057' // Darker text for y-axis labels
},
border: {
color: '#adb5bd'
}
}
}
}
});
// Get the canvas context for the chart
const ctx3 = document.getElementById('salesByBrandChart').getContext('2d');
// Chart.js configuration for the Pie Chart
new Chart(ctx3, {
type: 'pie',
data: {
labels: ['Toyota', 'Ford', 'Honda', 'BMW', 'Other'],
datasets: [{
label: 'Car Count by Make',
data: [45, 30, 25, 15, 10], // Sample data for car counts
backgroundColor: [
'#007bff', // Blue
'#28a745', // Green
'#ffc107', // Yellow
'#dc3545', // Red
'#6c757d' // Gray
],
hoverOffset: 15,
}]
},
options: {
responsive: true,
maintainAspectRatio: false,
plugins: {
legend: {
position: 'right', // Places the legend on the right side
labels: {
color: '#343a40', // Dark text color for labels
font: {
size: 14
}
}
},
tooltip: {
backgroundColor: 'rgba(52, 58, 64, 0.9)',
titleColor: '#fff',
bodyColor: '#fff',
callbacks: {
label: function(context) {
const label = context.label || '';
const value = context.parsed || 0;
const total = context.dataset.data.reduce((a, b) => a + b, 0);
const percentage = ((value / total) * 100).toFixed(2);
return `${label}: ${value} cars (${percentage}%)`;
}
}
}
}
}
});
// Get the canvas context for the chart
const ctx4 = document.getElementById('inventoryByBrandChart').getContext('2d');
// Chart.js configuration for the Pie Chart
new Chart(ctx4, {
type: 'pie',
data: {
labels: ['Toyota', 'Ford', 'Honda', 'BMW', 'Other'],
datasets: [{
label: 'Car Count by Make',
data: [45, 30, 25, 15, 10], // Sample data for car counts
backgroundColor: [
'#007bff', // Blue
'#28a745', // Green
'#ffc107', // Yellow
'#dc3545', // Red
'#6c757d' // Gray
],
hoverOffset: 15,
}]
},
options: {
responsive: true,
maintainAspectRatio: false,
plugins: {
legend: {
position: 'right', // Places the legend on the right side
labels: {
color: '#343a40', // Dark text color for labels
font: {
size: 14
}
}
},
tooltip: {
backgroundColor: 'rgba(52, 58, 64, 0.9)',
titleColor: '#fff',
bodyColor: '#fff',
callbacks: {
label: function(context) {
const label = context.label || '';
const value = context.parsed || 0;
const total = context.dataset.data.reduce((a, b) => a + b, 0);
const percentage = ((value / total) * 100).toFixed(2);
return `${label}: ${value} cars (${percentage}%)`;
}
}
}
}
}
});
// Get the canvas context for the chart
const ctx_salesperson = document.getElementById('salespersonChart').getContext('2d');
// Chart.js configuration for the Salesperson Performance Bar Chart
new Chart(ctx_salesperson, {
type: 'bar',
data: {
labels: ['John Doe', 'Jane Smith', 'Peter Jones', 'Mary Brown'],
datasets: [{
label: 'Cars Sold',
data: [15, 22, 18, 25], // Sample data for cars sold by each salesperson
backgroundColor: [
'#007bff', // Blue
'#28a745', // Green
'#ffc107', // Yellow
'#dc3545' // Red
],
borderWidth: 1
}]
},
options: {
responsive: true,
maintainAspectRatio: false,
plugins: {
legend: {
display: false // Hide the legend for a single dataset
},
title: {
display: true,
text: 'Salesperson Performance',
font: {
size: 16
}
}
},
scales: {
x: {
title: {
display: true,
text: 'Salesperson Name'
},
ticks: {
color: '#495057' // Dark text for x-axis labels
}
},
y: {
beginAtZero: true,
title: {
display: true,
text: 'Number of Cars Sold'
},
ticks: {
color: '#495057' // Dark text for y-axis labels
}
}
}
}
});
// Get the canvas context for the chart
const ctx_leadSources = document.getElementById('leadSourcesChart').getContext('2d');
// Chart.js configuration for the Top 5 Lead Sources Bar Chart
new Chart(ctx_leadSources, {
type: 'bar',
data: {
labels: ['Showroom', 'Referrals', 'WhatsApp', 'Facebook', 'TikTok'], // Labels from the provided list
datasets: [{
label: 'Number of Leads',
data: [45, 35, 25, 20, 15], // Sample data for leads from each source
backgroundColor: 'rgba(54, 162, 235, 0.8)', // A consistent blue color for the bars
borderColor: 'rgba(54, 162, 235, 1)',
borderWidth: 1
}]
},
options: {
indexAxis: 'y', // Makes it a horizontal bar chart
responsive: true,
maintainAspectRatio: false,
plugins: {
legend: {
display: false
},
title: {
display: true,
text: 'Top 5 Lead Sources',
font: {
size: 16
}
}
},
scales: {
x: {
beginAtZero: true,
title: {
display: true,
text: 'Number of Leads',
color: '#495057'
},
ticks: {
color: '#495057'
}
},
y: {
ticks: {
color: '#495057',
font: {
size: 8 // Decreases the font size to fit more text
}
}
}
}
}
});
// Get the canvas context for the chart
const ctx_appointments = document.getElementById('appointmentsChart').getContext('2d');
// Chart.js configuration for the Appointments by Weekday Bar Chart
new Chart(ctx_appointments, {
type: 'bar',
data: {
labels: ['Monday', 'Tuesday', 'Wednesday', 'Thursday', 'Friday', 'Saturday', 'Sunday'],
datasets: [{
label: 'Number of Appointments',
data: [10, 15, 20, 18, 25, 30, 12], // Sample data for appointments per day
backgroundColor: 'rgba(75, 192, 192, 0.8)', // A consistent color for the bars
borderColor: 'rgba(75, 192, 192, 1)',
borderWidth: 1
}]
},
options: {
responsive: true,
maintainAspectRatio: false,
plugins: {
legend: {
display: false
},
title: {
display: true,
text: 'Appointments by Weekday',
font: {
size: 16
}
}
},
scales: {
x: {
title: {
display: true,
text: 'Day of the Week',
color: '#495057'
},
ticks: {
color: '#495057'
}
},
y: {
beginAtZero: true,
title: {
display: true,
text: 'Number of Appointments',
color: '#495057'
},
ticks: {
color: '#495057'
}
}
}
}
});
// Get the canvas context for the funnel chart
const ctx_funnel = document.getElementById('leadFunnelChart').getContext('2d');
// Sample data for the funnel stages
const funnelData = {
labels: ['Initial Contact', 'Qualified Leads', 'Test Drives', 'Negotiation', 'Closed Deals'],
data: [250, 180, 120, 80, 45], // Example lead counts at each stage
};
new Chart(ctx_funnel, {
type: 'bar',
data: {
labels: funnelData.labels,
datasets: [{
label: 'Number of Leads',
data: funnelData.data,
backgroundColor: [
'rgba(0, 123, 255, 0.8)',
'rgba(0, 123, 255, 0.7)',
'rgba(0, 123, 255, 0.6)',
'rgba(0, 123, 255, 0.5)',
'rgba(0, 123, 255, 0.4)'
],
borderColor: 'rgba(0, 123, 255, 1)',
borderWidth: 1
}]
},
options: {
indexAxis: 'y', // Makes it a horizontal bar chart
responsive: true,
maintainAspectRatio: false,
plugins: {
legend: {
display: false
},
title: {
display: true,
text: 'Lead Conversion Funnel',
font: {
size: 16
}
},
tooltip: {
callbacks: {
label: function(context) {
const totalLeads = funnelData.data[0];
const currentLeads = context.parsed.x;
const percentage = ((currentLeads / totalLeads) * 100).toFixed(1);
return `Leads: ${currentLeads} (${percentage}%)`;
}
}
}
},
scales: {
x: {
beginAtZero: true,
display: false // Hide the x-axis to make it look like a funnel
},
y: {
ticks: {
color: '#495057'
}
}
}
}
});
</script>
{% endblock %}

View File

@ -0,0 +1,367 @@
{% extends 'base.html' %}
{% block content %}
<div class="main-content flex-grow-1 container-fluid mt-4 mb-3">
<div class="d-flex justify-content-between align-items-center mb-5 pb-3 border-bottom">
<h2 class="h3 fw-bold mb-0">Manager Dashboard<i class="fas fa-chart-area text-primary ms-2"></i></h2>
<div class="dropdown">
<button class="btn btn-outline-secondary dropdown-toggle" type="button" data-bs-toggle="dropdown" aria-expanded="false">
Last 30 Days
</button>
<ul class="dropdown-menu dropdown-menu-end shadow">
<li><a class="dropdown-item" href="#">Today</a></li>
<li><a class="dropdown-item" href="#">Last 7 Days</a></li>
<li><a class="dropdown-item" href="#">Last 90 Days</a></li>
</ul>
</div>
</div>
<div class="row g-4 mb-5">
<div class="col-sm-6 col-md-4 col-lg-3">
<div class="card h-100 shadow-sm border-0">
<div class="card-body d-flex flex-column justify-content-between p-4">
<div>
<p class="text-uppercase text-muted fw-bold small mb-1">Total Revenue</p>
<h4 class="fw-bolder text-primary mb-3">$1.25M</h4>
</div>
<span class="badge bg-success-subtle text-success fw-bold p-2 rounded-pill d-inline-flex align-self-start">
+8% from last month
</span>
</div>
</div>
</div>
<div class="col-sm-6 col-md-4 col-lg-3">
<div class="card h-100 shadow-sm border-0">
<div class="card-body d-flex flex-column justify-content-between p-4">
<div>
<p class="text-uppercase text-muted fw-bold small mb-1">Net Profit</p>
<h4 class="fw-bolder text-success mb-3">$1.25M</h4>
</div>
<span class="badge bg-success-subtle text-success fw-bold p-2 rounded-pill d-inline-flex align-self-start">
+8% from last month
</span>
</div>
</div>
</div>
<div class="col-sm-6 col-md-4 col-lg-3">
<div class="card h-100 shadow-sm border-0">
<div class="card-body d-flex flex-column justify-content-between p-4">
<div>
<p class="text-uppercase text-muted fw-bold small mb-1">Total Expense</p>
<h4 class="fw-bolder text-danger mb-3">$1.25M</h4>
</div>
<span class="badge bg-danger-subtle text-danger fw-bold p-2 rounded-pill d-inline-flex align-self-start">
+3% from last month
</span>
</div>
</div>
</div>
<div class="col-sm-6 col-md-4 col-lg-3">
<div class="card h-100 shadow-sm border-0">
<div class="card-body d-flex flex-column justify-content-between p-4">
<div>
<p class="text-uppercase text-muted fw-bold small mb-1">Total Cars Sold</p>
<h4 class="fw-bolder text-success mb-3">{{ sold_cars }}</h4>
</div>
<span class="badge bg-success-subtle text-success fw-bold p-2 rounded-pill d-inline-flex align-self-start">
+5 units from last month
</span>
</div>
</div>
</div>
<div class="col-sm-6 col-md-4 col-lg-3">
<div class="card h-100 shadow-sm border-0">
<div class="card-body d-flex flex-column justify-content-between p-4">
<div>
<p class="text-uppercase text-muted fw-bold small mb-1">Avg. Time on Lot</p>
<h4 class="fw-bolder text-warning mb-3">10 days</h4>
</div>
<span class="badge bg-danger-subtle text-danger fw-bold p-2 rounded-pill d-inline-flex align-self-start">
+2 days from last month
</span>
</div>
</div>
</div>
<div class="col-sm-6 col-md-4 col-lg-3">
<div class="card h-100 shadow-sm border-0">
<div class="card-body d-flex flex-column justify-content-between p-4">
<div>
<p class="text-uppercase text-muted fw-bold small mb-1">Inventory Value</p>
<h4 class="fw-bolder text-primary mb-3">$5.8M</h4>
</div>
<span class="badge bg-danger-subtle text-danger fw-bold p-2 rounded-pill d-inline-flex align-self-start">
-2% from last month
</span>
</div>
</div>
</div>
<div class="col-sm-6 col-md-4 col-lg-3">
<div class="card h-100 shadow-sm border-0">
<div class="card-body d-flex flex-column justify-content-between p-4">
<div>
<p class="text-uppercase text-muted fw-bold small mb-1">Aging Inventory</p>
<h4 class="fw-bolder text-danger mb-3">12 units</h4>
</div>
<span class="badge bg-success-subtle text-success fw-bold p-2 rounded-pill d-inline-flex align-self-start">
-4 cars from last month
</span>
</div>
</div>
</div>
<div class="col-sm-6 col-md-4 col-lg-3">
<div class="card h-100 shadow-sm border-0">
<div class="card-body d-flex flex-column justify-content-between p-4">
<div>
<p class="text-uppercase text-muted fw-bold small mb-1">Gross Profit</p>
<h4 class="fw-bolder text-info mb-3">$1.25M</h4>
</div>
<span class="badge bg-success-subtle text-success fw-bold p-2 rounded-pill d-inline-flex align-self-start">
+8% from last month
</span>
</div>
</div>
</div>
</div>
<div class="row g-4 mb-5">
<div class="col-lg-8">
<div class="card h-100 shadow-sm border-0">
<div class="card-header bg-white border-bottom-0">
<h5 class="fw-bold mb-0 text-dark">Monthly Revenue & Profit</h5>
</div>
<div class="card-body" style="height: 400px;">
<canvas id="revenueProfitChart"></canvas>
</div>
</div>
</div>
<div class="col-lg-4">
<div class="card h-100 shadow-sm border-0">
<div class="card-header bg-white border-bottom-0">
<h5 class="fw-bold mb-0 text-dark">Monthly Cars Sold</h5>
</div>
<div class="card-body d-flex align-items-center justify-content-center" style="height: 400px;">
<canvas id="CarsSoldByMonthChart"></canvas>
</div>
</div>
</div>
</div>
<div class="row g-4">
<div class="col-lg-6">
<div class="card h-100 shadow-sm border-0">
<div class="card-header bg-white border-bottom-0">
<h5 class="fw-bold mb-0 text-dark">Sales by Make</h5>
</div>
<div class="card-body d-flex align-items-center justify-content-center" style="height: 400px;">
<canvas id="salesByBrandChart"></canvas>
</div>
</div>
</div>
<div class="col-lg-6">
<div class="card h-100 shadow-sm border-0">
<div class="card-header bg-white border-bottom-0">
<h5 class="fw-bold mb-0 text-dark">Top Salesperson Performance</h5>
</div>
<div class="card-body" style="height: 400px;">
<canvas id="salespersonChart"></canvas>
</div>
</div>
</div>
</div>
</div>
<script src="https://cdn.jsdelivr.net/npm/chart.js"></script>
{% endblock content %}
{% block customJS %}
<script>
// Define a color palette that aligns with the Phoenix template
const primaryColor = '#7249b6'; // A vibrant purple
const secondaryColor = '#8193a6'; // A muted gray/blue
const successColor = '#00d074'; // A bright green
const warningColor = '#ffc107'; // A strong yellow
const dangerColor = '#e63757'; // A deep red
const chartColors = ['#00d27a', '#7249b6', '#32b9ff', '#e63757', '#ffc107'];
// Monthly Cars Sold (Bar Chart)
const ctx1 = document.getElementById('CarsSoldByMonthChart').getContext('2d');
new Chart(ctx1, {
type: 'bar',
data: {
labels: ['Jan', 'Feb', 'Mar', 'Apr', 'May', 'Jun', 'Jul', 'Aug', 'Sep', 'Oct', 'Nov', 'Dec'],
datasets: [{
label: 'Total Cars Sold',
data: [2, 3, 10, 4, 30, 12, 8, 9, 20, 12, 15, 35],
backgroundColor: primaryColor,
borderColor: primaryColor,
borderWidth: 1
}]
},
options: {
responsive: true,
maintainAspectRatio: false,
plugins: {
legend: { display: false }
},
scales: {
y: {
beginAtZero: true,
grid: { color: 'rgba(0, 0, 0, 0.05)' },
ticks: { color: secondaryColor }
},
x: {
grid: { display: false },
ticks: { color: secondaryColor }
}
}
}
});
// Monthly Revenue & Profit (Line Chart)
const ctx2 = document.getElementById('revenueProfitChart').getContext('2d');
new Chart(ctx2, {
type: 'line',
data: {
labels: ['Jan', 'Feb', 'Mar', 'Apr', 'May', 'Jun', 'Jul', 'Aug', 'Sep', 'Oct', 'Nov', 'Dec'],
datasets: [
{
label: 'Monthly Revenue',
data: [120000, 150000, 130000, 180000, 200000, 175000, 190000, 220000, 210000, 250000, 240000, 280000],
borderColor: primaryColor,
backgroundColor: 'rgba(114, 73, 182, 0.1)', // Using primaryColor with transparency
tension: 0.4,
fill: true,
pointBackgroundColor: primaryColor,
pointRadius: 5,
pointHoverRadius: 8
},
{
label: 'Monthly Net Profit',
data: [25000, 35000, 28000, 40000, 45000, 38000, 42000, 50000, 48000, 55000, 52000, 60000],
borderColor: successColor,
backgroundColor: 'rgba(0, 208, 116, 0.1)', // Using successColor with transparency
tension: 0.4,
fill: true,
pointBackgroundColor: successColor,
pointRadius: 5,
pointHoverRadius: 8
}
]
},
options: {
responsive: true,
maintainAspectRatio: false,
plugins: {
legend: {
display: true,
labels: { color: '#495057', boxWidth: 20 }
},
tooltip: {
backgroundColor: 'rgba(33, 37, 41, 0.9)',
titleColor: 'white',
bodyColor: 'white',
padding: 10,
callbacks: {
label: function(context) {
let label = context.dataset.label || '';
if (label) {
label += ': ';
}
if (context.parsed.y !== null) {
label += new Intl.NumberFormat('en-US', { style: 'currency', currency: 'USD' }).format(context.parsed.y);
}
return label;
}
}
}
},
scales: {
x: {
grid: { color: 'rgba(0, 0, 0, 0.05)' },
ticks: { color: secondaryColor },
border: { color: secondaryColor }
},
y: {
grid: { color: 'rgba(0, 0, 0, 0.05)' },
ticks: { color: secondaryColor },
border: { color: secondaryColor }
}
}
}
});
// Sales by Make (Pie Chart)
const ctx3 = document.getElementById('salesByBrandChart').getContext('2d');
new Chart(ctx3, {
type: 'pie',
data: {
labels: ['Toyota', 'Ford', 'Honda', 'BMW', 'Other'],
datasets: [{
label: 'Car Count by Make',
data: [45, 30, 25, 15, 10],
backgroundColor: chartColors,
hoverOffset: 15,
}]
},
options: {
responsive: true,
maintainAspectRatio: false,
plugins: {
legend: {
position: 'right',
labels: { color: '#343a40', font: { size: 14 } }
},
tooltip: {
backgroundColor: 'rgba(33, 37, 41, 0.9)',
titleColor: '#fff',
bodyColor: '#fff',
callbacks: {
label: function(context) {
const label = context.label || '';
const value = context.parsed || 0;
const total = context.dataset.data.reduce((a, b) => a + b, 0);
const percentage = ((value / total) * 100).toFixed(2);
return `${label}: ${value} cars (${percentage}%)`;
}
}
}
}
}
});
// Salesperson Performance (Bar Chart)
const ctx_salesperson = document.getElementById('salespersonChart').getContext('2d');
new Chart(ctx_salesperson, {
type: 'bar',
data: {
labels: ['John Doe', 'Jane Smith', 'Peter Jones', 'Mary Brown'],
datasets: [{
label: 'Cars Sold',
data: [15, 22, 18, 25],
backgroundColor: chartColors,
borderWidth: 1
}]
},
options: {
responsive: true,
maintainAspectRatio: false,
plugins: {
legend: { display: false },
title: { display: true, text: 'Top Salesperson Performance', font: { size: 16 } }
},
scales: {
x: {
title: { display: true, text: 'Salesperson Name', color: secondaryColor },
ticks: { color: secondaryColor }
},
y: {
beginAtZero: true,
title: { display: true, text: 'Number of Cars Sold', color: secondaryColor },
ticks: { color: secondaryColor }
}
}
}
});
</script>
{% endblock %}

View File

@ -0,0 +1,356 @@
{% extends 'base.html' %}
{% block content %}
<div class="main-content flex-grow-1 container-fluid mt-4 mb-3">
<div class="d-flex justify-content-between align-items-center mb-5 pb-3 border-bottom">
<h2 class="h3 fw-bold mb-0">Sales & Leads Dashboard <i class="fas fa-chart-area text-primary ms-2"></i></h2>
<div class="dropdown">
<button class="btn btn-outline-secondary dropdown-toggle" type="button" data-bs-toggle="dropdown" aria-expanded="false">
Last 30 Days
</button>
<ul class="dropdown-menu dropdown-menu-end shadow">
<li><a class="dropdown-item" href="#">Today</a></li>
<li><a class="dropdown-item" href="#">Last 7 Days</a></li>
<li><a class="dropdown-item" href="#">Last 90 Days</a></li>
</ul>
</div>
</div>
<div class="row g-4 mb-5">
<div class="col-sm-6 col-md-4">
<div class="card h-100 shadow-sm border-0">
<div class="card-body d-flex flex-column justify-content-between p-4">
<div>
<p class="text-uppercase text-muted fw-bold small mb-1">Total Cars Sold</p>
<h4 class="fw-bolder text-success mb-3">{{ sold_cars }}</h4>
</div>
<span class="badge bg-success-subtle text-success fw-bold p-2 rounded-pill d-inline-flex align-self-start">
+5 units from last month
</span>
</div>
</div>
</div>
<div class="col-sm-6 col-md-4">
<div class="card h-100 shadow-sm border-0">
<div class="card-body d-flex flex-column justify-content-between p-4">
<div>
<p class="text-uppercase text-muted fw-bold small mb-1">Used Cars Sold</p>
<h4 class="fw-bolder text-info mb-3">{{ sold_used_cars }}</h4>
</div>
<span class="badge bg-success-subtle text-success fw-bold p-2 rounded-pill d-inline-flex align-self-start">
+5 units from last month
</span>
</div>
</div>
</div>
<div class="col-sm-6 col-md-4">
<div class="card h-100 shadow-sm border-0">
<div class="card-body d-flex flex-column justify-content-between p-4">
<div>
<p class="text-uppercase text-muted fw-bold small mb-1">New Cars Sold</p>
<h4 class="fw-bolder text-primary mb-3">{{ sold_new_cars }}</h4>
</div>
<span class="badge bg-success-subtle text-success fw-bold p-2 rounded-pill d-inline-flex align-self-start">
+5 units from last month
</span>
</div>
</div>
</div>
</div>
<div class="row g-4 mb-5">
<div class="col-md-6">
<div class="card h-100 shadow-sm border-0">
<div class="card-header bg-white border-bottom-0">
<h5 class="fw-bold mb-0 text-dark">Monthly Cars Sold</h5>
</div>
<div class="card-body d-flex align-items-center justify-content-center" style="height: 400px;">
<canvas id="CarsSoldByMonthChart"></canvas>
</div>
</div>
</div>
<div class="col-md-6">
<div class="card h-100 shadow-sm border-0">
<div class="card-header bg-white border-bottom-0">
<h5 class="fw-bold mb-0 text-dark">Sales by Make</h5>
</div>
<div class="card-body d-flex align-items-center justify-content-center" style="height: 400px;">
<canvas id="salesByBrandChart"></canvas>
</div>
</div>
</div>
</div>
<div class="row g-4 mb-5">
<div class="col-md-6">
<div class="card h-100 shadow-sm border-0">
<div class="card-header bg-white border-bottom-0">
<h5 class="fw-bold mb-0 text-dark">Top 5 Lead Sources</h5>
</div>
<div class="card-body d-flex align-items-center justify-content-center" style="height: 400px;">
<canvas id="leadSourcesChart"></canvas>
</div>
</div>
</div>
<div class="col-md-6">
<div class="card h-100 shadow-sm border-0">
<div class="card-header bg-white border-bottom-0">
<h5 class="fw-bold mb-0 text-dark">Appointments by Weekday</h5>
</div>
<div class="card-body d-flex align-items-center justify-content-center" style="height: 400px;">
<canvas id="appointmentsChart"></canvas>
</div>
</div>
</div>
</div>
<div class="row g-4">
<div class="col-12">
<div class="card h-100 shadow-sm border-0">
<div class="card-header bg-white border-bottom-0">
<h5 class="fw-bold mb-0 text-dark">Lead Conversion Funnel</h5>
</div>
<div class="card-body d-flex align-items-center justify-content-center" style="height: 400px;">
<canvas id="leadFunnelChart"></canvas>
</div>
</div>
</div>
</div>
</div>
<script src="https://cdn.jsdelivr.net/npm/chart.js"></script>
{% endblock content %}
{% block customJS%}
<script>
// Define a custom color palette that matches the Phoenix template
const primaryColor = '#7249b6'; // A vibrant purple
const secondaryColor = '#8193a6'; // A muted gray/blue
const successColor = '#00d074'; // A bright green
const infoColor = '#0085ff'; // A medium blue
const warningColor = '#ffc83b'; // A yellow
const dangerColor = '#e63757'; // A deep red
// Sales by Month Chart (Bar Chart)
const ctx1 = document.getElementById('CarsSoldByMonthChart').getContext('2d');
new Chart(ctx1, {
type: 'bar',
data: {
labels: ['Jan', 'Feb', 'Mar', 'Apr', 'May', 'Jun', 'Jul', 'Aug', 'Sep', 'Oct', 'Nov', 'Dec'],
datasets: [{
label: 'Total Cars Sold',
data: [2, 3, 10, 4, 30, 12, 8, 9, 20, 12, 15, 35],
backgroundColor: primaryColor,
borderColor: primaryColor,
borderWidth: 1
}]
},
options: {
responsive: true,
maintainAspectRatio: false,
plugins: {
legend: { display: false }
},
scales: {
y: {
beginAtZero: true,
grid: { color: 'rgba(0, 0, 0, 0.05)' },
ticks: { color: secondaryColor }
},
x: {
grid: { display: false },
ticks: { color: secondaryColor }
}
}
}
});
// Sales by Make (Pie Chart)
const ctx3 = document.getElementById('salesByBrandChart').getContext('2d');
new Chart(ctx3, {
type: 'pie',
data: {
labels: ['Toyota', 'Ford', 'Honda', 'BMW', 'Other'],
datasets: [{
label: 'Car Count by Make',
data: [45, 30, 25, 15, 10],
backgroundColor: [primaryColor, successColor, warningColor, infoColor, secondaryColor],
hoverOffset: 15,
}]
},
options: {
responsive: true,
maintainAspectRatio: false,
plugins: {
legend: {
position: 'right',
labels: { color: secondaryColor, font: { size: 14 } }
},
tooltip: {
backgroundColor: 'rgba(33, 37, 41, 0.9)',
titleColor: '#fff',
bodyColor: '#fff',
callbacks: {
label: function(context) {
const label = context.label || '';
const value = context.parsed || 0;
const total = context.dataset.data.reduce((a, b) => a + b, 0);
const percentage = ((value / total) * 100).toFixed(2);
return `${label}: ${value} cars (${percentage}%)`;
}
}
}
}
}
});
// Top 5 Lead Sources (Horizontal Bar Chart)
const ctx_leadSources = document.getElementById('leadSourcesChart').getContext('2d');
new Chart(ctx_leadSources, {
type: 'bar',
data: {
labels: ['Showroom', 'Referrals', 'WhatsApp', 'Facebook', 'TikTok'],
datasets: [{
label: 'Number of Leads',
data: [45, 35, 25, 20, 15],
backgroundColor: infoColor,
borderColor: infoColor,
borderWidth: 1
}]
},
options: {
indexAxis: 'y',
responsive: true,
maintainAspectRatio: false,
plugins: {
legend: { display: false },
title: { display: false },
tooltip: {
backgroundColor: 'rgba(33, 37, 41, 0.9)',
titleColor: '#fff',
bodyColor: '#fff',
}
},
scales: {
x: {
beginAtZero: true,
title: { display: true, text: 'Number of Leads', color: secondaryColor },
ticks: { color: secondaryColor },
grid: { color: 'rgba(0, 0, 0, 0.05)' }
},
y: {
grid: { display: false },
ticks: { color: secondaryColor }
}
}
}
});
// Appointments by Weekday (Bar Chart)
const ctx_appointments = document.getElementById('appointmentsChart').getContext('2d');
new Chart(ctx_appointments, {
type: 'bar',
data: {
labels: ['Mon', 'Tue', 'Wed', 'Thu', 'Fri', 'Sat', 'Sun'],
datasets: [{
label: 'Number of Appointments',
data: [10, 15, 20, 18, 25, 30, 12],
backgroundColor: successColor,
borderColor: successColor,
borderWidth: 1
}]
},
options: {
responsive: true,
maintainAspectRatio: false,
plugins: {
legend: { display: false },
title: { display: false },
tooltip: {
backgroundColor: 'rgba(33, 37, 41, 0.9)',
titleColor: '#fff',
bodyColor: '#fff',
}
},
scales: {
x: {
title: { display: true, text: 'Day of the Week', color: secondaryColor },
ticks: { color: secondaryColor },
grid: { display: false }
},
y: {
beginAtZero: true,
title: { display: true, text: 'Number of Appointments', color: secondaryColor },
ticks: { color: secondaryColor },
grid: { color: 'rgba(0, 0, 0, 0.05)' }
}
}
}
});
// Lead Conversion Funnel (Horizontal Bar Chart)
const ctx_funnel = document.getElementById('leadFunnelChart').getContext('2d');
const funnelData = {
labels: ['Initial Contact', 'Qualified Leads', 'Test Drives', 'Negotiation', 'Closed Deals'],
data: [250, 180, 120, 80, 45],
};
new Chart(ctx_funnel, {
type: 'bar',
data: {
labels: funnelData.labels,
datasets: [{
label: 'Number of Leads',
data: funnelData.data,
backgroundColor: [
primaryColor,
infoColor,
successColor,
warningColor,
dangerColor
],
borderColor: [
primaryColor,
infoColor,
successColor,
warningColor,
dangerColor
],
borderWidth: 1
}]
},
options: {
indexAxis: 'y',
responsive: true,
maintainAspectRatio: false,
plugins: {
legend: { display: false },
title: { display: false },
tooltip: {
backgroundColor: 'rgba(33, 37, 41, 0.9)',
titleColor: '#fff',
bodyColor: '#fff',
callbacks: {
label: function(context) {
const totalLeads = funnelData.data[0];
const currentLeads = context.parsed.x;
const percentage = ((currentLeads / totalLeads) * 100).toFixed(1);
return `Leads: ${currentLeads} (${percentage}%)`;
}
}
}
},
scales: {
x: {
beginAtZero: true,
display: false
},
y: {
grid: { display: false },
ticks: { color: secondaryColor }
}
}
}
});
</script>
{% endblock %}

View File

@ -3,10 +3,16 @@
{% block content %}
{% if request.user.is_authenticated %}
<div id="dashboard-content"
hx-get="{% if request.dealer %}
hx-get="{% if request.is_dealer %}
{% url 'dealer_dashboard' %}
{% elif request.is_manger %}
{% url 'manager_dashboard' %}
{% else %}
{% elif request.is_sales %}
{% url 'sales_dashboard' %}
{% elif request.is_inventory %}
{% url 'inventory_dashboard' %}
{% else %}
{% url 'accountant_dashboard' %}
{% endif %}"
hx-trigger="load"
hx-target="#dashboard-content"

View File

@ -88,13 +88,13 @@
}
.color-radio:checked + .color-display {
border: 2px solid #0d6efd;
box-shadow: 0 0 0 3px rgba(13, 110, 253, 0.25);
border: 3px solid rgb(44, 229, 44);
box-shadow: 0 0 10px rgba(44, 123, 229, 0.5);
}
.color-radio:focus + .color-display {
border-color: #86b7fe;
box-shadow: 0 0 0 3px rgba(13, 110, 253, 0.25);
border: 3px solid rgb(44, 229, 44);
box-shadow: 0 0 10px rgba(44, 123, 229, 0.5);
}
.color-display {

View File

@ -189,23 +189,12 @@
hx-on::before-request="on_before_request()"
hx-on::after-request="on_after_request()"></div>
<div class="w-100 list table-responsive">
<div class="form-check">
<input class="form-check-input ms-4" type="checkbox" id="select-all" />
<span class="ms-1 text-body-tertiary">{{ _("Select All") }}</span>
</div>
{% for car in cars %}
<div class="card border mb-3 py-0 px-0" id="project-list-table-body">
<div class="card-body">
<div class="row align-items-center">
<div class="col-auto">
<div class="form-check">
<input class="form-check-input car-checkbox"
type="checkbox"
name="car"
value="{{ car.pk }}"
id="car-{{ car.pk }}" />
</div>
</div>
<!-- Vehicle Image/Icon -->
<div class="col-auto">
<div class="avatar avatar-3xl">
@ -322,68 +311,5 @@
{% endblock %}
{% block customJS %}
<script>
links = document.querySelectorAll(".nav-link");
links.forEach((link) => {
link.addEventListener("click", () => {
links.forEach((link) => {
link.classList.remove("active");
});
link.classList.add("active");
});
});
function on_before_request() {
document.querySelector(".table").classList.toggle("on-before-request");
document.querySelector(".model-select").classList.add("on-after-request");
}
function on_after_request() {
document.querySelector(".table").classList.remove("on-before-request");
document.querySelector(".model-select").classList.remove("on-after-request");
}
function toggle_filter() {
document.querySelector(".filter").classList.toggle("d-none");
document.querySelector(".filter-icon").classList.toggle("fa-caret-down");
document.querySelector(".filter-icon").classList.toggle("fa-caret-up");
}
function filter_before_request() {
document.querySelector(".model-select").setAttribute("disabled", true);
document.querySelector(".year").setAttribute("disabled", true);
document.querySelector(".car_status").setAttribute("disabled", true);
}
function filter_after_request() {
document.querySelector(".model-select").removeAttribute("disabled");
document.querySelector(".year").removeAttribute("disabled");
document.querySelector(".car_status").removeAttribute("disabled");
}
document.getElementById("select-all").addEventListener("change", function () {
const checkboxes = document.querySelectorAll('#project-list-table-body input[type="checkbox"]');
if (this.checked) {
checkboxes.forEach((checkbox) => (checkbox.checked = true));
} else {
checkboxes.forEach((checkbox) => (checkbox.checked = false));
}
updateFormVisibility();
});
const cbox = document.querySelectorAll(".car-checkbox");
cbox.forEach((checkbox) => {
checkbox.addEventListener("change", function () {
updateFormVisibility();
});
});
function updateFormVisibility() {
const form = document.querySelector(".update-price-form");
const checkedCount = document.querySelectorAll(".car-checkbox:checked").length;
const submitButton = form.querySelector('button[type="submit"]');
if (checkedCount > 0) {
form.classList.remove("d-none");
submitButton.textContent = `Update Cost Price (${checkedCount})`;
} else {
form.classList.add("d-none");
}
}
</script>
{% endblock customJS %}

View File

@ -51,7 +51,7 @@
<section id="filters" class="mb-5 p-4 rounded border border-primary">
<h2 class="section-heading mb-4">{% trans 'Filters' %} <i class="fas fa-sliders-h ms-2"></i></h2>
<form method="GET" class="row g-3 align-items-end">
<div class="col-md-3">
<div class="col-md-2">
<label for="make-select" class="form-label">{% trans 'Make' %}</label>
<select id="make-select" name="make" class="form-select">
<option value="">{% trans 'All Makes' %}</option>
@ -60,7 +60,7 @@
{% endfor %}
</select>
</div>
<div class="col-md-3">
<div class="col-md-2">
<label for="model-select" class="form-label">{% trans 'Model' %}</label>
<select id="model-select" name="model" class="form-select">
<option value="">{% trans 'All Models' %}</option>
@ -69,7 +69,7 @@
{% endfor %}
</select>
</div>
<div class="col-md-3">
<div class="col-md-2">
<label for="serie-select" class="form-label">{% trans 'Serie' %}</label>
<select id="serie-select" name="serie" class="form-select">
<option value="">{% trans 'All Series' %}</option>
@ -87,36 +87,98 @@
{% endfor %}
</select>
</div>
<div class="col-md-1">
<div class="col-md-2">
<label for="stock_type-select" class="form-label">{% trans 'Stock Types' %}</label>
<select id="stock_type-select" name="stock_type" class="form-select">
<option value="">{% trans 'Stock Types' %}</option>
{% for stock_type in stock_types %}
<option value="{{ stock_type }}" {% if stock_type == selected_stock_type %}selected{% endif %}>{{ stock_type }}</option>
{% endfor %}
</select>
</div>
<div class="col-md-2">
<button type="submit" class="btn btn-primary w-100"><i class="fas fa-filter me-2"></i>{% trans 'Filter' %}</button>
</div>
</form>
</section>
<!---->
{% comment %} 'cars_sold': cars_sold,
'current_time': current_time,
'dealer': dealer,
'total_revenue_from_cars': total_revenue_from_cars,
'total_revenue_from_additonals':total_revenue_from_additonals,
'total_revenue_collected': total_revenue_collected,
'total_vat_on_cars':total_vat_on_cars,
'total_vat_from_additonals':total_vat_from_additonals,
'total_vat_collected':total_vat_collected,
'total_discount': total_discount,
'makes': makes,
'models': models_qs,
'series': series,
'years': years,
'selected_make': selected_make,
'selected_model': selected_model,
'selected_serie': selected_serie,
'selected_year': selected_year, {% endcomment %}
<!---->
<section id="summary" class="mb-5">
<h2 class="section-heading mb-4 border-start border-5 border-primary p-2">{% trans 'Report Summary' %}</h2>
<div class="row g-4">
<div class="col-md-6 col-lg-3">
<div class="card summary-card">
<div class="card-body">
<h5 class="card-title">{% trans 'Total Revenue' %}<span class="icon-saudi_riyal"></span></h5>
<p class="card-text">{{ total_revenue|floatformat:2 }} <span class="icon-saudi_riyal"></span></p>
<h5 class="card-title"><span>{% trans 'Total Revenue from Cars' %}<span class="icon-saudi_riyal"></span></span></h5>
<p class="card-text"><span>{{ total_revenue_from_cars|floatformat:2 }} <span class="icon-saudi_riyal"></span></span></p>
</div>
</div>
</div>
<div class="col-md-6 col-lg-3">
<div class="card summary-card">
<div class="card-body">
<h5 class="card-title">{% trans 'Total VAT Amount' %}<i class="fas fa-percent ms-2"></i></h5>
<p class="card-text">{{ 10000|floatformat:2 }} <span class="icon-saudi_riyal"></span></p>
<h5 class="card-title"><span>{% trans 'Total Revenue from Services' %}<span class="icon-saudi_riyal"></span></span></h5>
<p class="card-text"><span>{{ total_revenue_from_additonals|floatformat:2 }} <span class="icon-saudi_riyal"></span></span></p>
</div>
</div>
</div>
<div class="col-md-6 col-lg-3">
<div class="card summary-card">
<div class="card-body">
<h5 class="card-title"><span>{% trans 'Total Revenue' %}<span class="icon-saudi_riyal"></span></span></h5>
<p class="card-text"><span>{{total_revenue_collected |floatformat:2 }} <span class="icon-saudi_riyal"></span></span></p>
</div>
</div>
</div>
<div class="col-md-6 col-lg-3">
<div class="card summary-card">
<div class="card-body">
<h5 class="card-title">{% trans 'Total VAT from Cars' %}<i class="fas fa-percent ms-2"></i></h5>
<p class="card-text"><span>{{ total_vat_on_cars|floatformat:2 }} <span class="icon-saudi_riyal"></span></span></p>
</div>
</div>
</div>
<div class="col-md-6 col-lg-3">
<div class="card summary-card">
<div class="card-body">
<h5 class="card-title">{% trans 'Total VAT from Services' %}<i class="fas fa-percent ms-2"></i></h5>
<p class="card-text"><span>{{ total_vat_from_additonals|floatformat:2 }} <span class="icon-saudi_riyal"></span></span></p>
</div>
</div>
</div>
<div class="col-md-6 col-lg-3">
<div class="card summary-card">
<div class="card-body">
<h5 class="card-title">{% trans 'Total VAT' %}<i class="fas fa-percent ms-2"></i></h5>
<p class="card-text"><span>{{ total_vat_collected|floatformat:2 }} <span class="icon-saudi_riyal"></span></span></p>
</div>
</div>
</div>
<div class="col-md-6 col-lg-3">
<div class="card summary-card">
<div class="card-body">
<h5 class="card-title">{% trans 'Total Discount Amount' %}<i class="fas fa-tag ms-2"></i></h5>
<p class="card-text">{{ total_discount|floatformat:2 }} <span class="icon-saudi_riyal"></span></p>
<p class="card-text"><span>{{ total_discount|floatformat:2 }} <span class="icon-saudi_riyal"></span></span></p>
</div>
</div>
</div>
@ -124,13 +186,10 @@
<div class="card summary-card">
<div class="card-body">
<h5 class="card-title">{% trans 'Total Cars Sold' %}<i class="fas fa-car ms-2"></i></h5>
<p class="card-text">{{ cars_sold|length }}</p>
<p class="card-text">{{ total_cars_sold }}</p>
</div>
</div>
</div>
</div>
</section>
@ -142,7 +201,7 @@
<i class="bi bi-download me-2"></i>{% trans 'Download as CSV' %}
</a>
</div>
<div class="table-responsive">
<div class="table-responsive table-container">
<table class="table table-striped table-hover table-bordered table-sm">
<thead>
<tr>
@ -160,7 +219,10 @@
<th class="fs-9">{% trans 'Marked Price' %}</th>
<th class="fs-9">{% trans 'Discount Amount' %}</th>
<th class="fs-9">{% trans 'Selling Price' %}</th>
<th class="fs-9">{% trans 'Tax Amount' %}</th>
<th class="fs-9">{% trans 'VAT on Car' %}</th>
<th class="fs-9">{% trans 'Services Price' %}</th>
<th class="fs-9">{% trans 'VAT on Services' %}</th>
<th class="fs-9">{% trans 'Final Total' %}</th>
<th class="fs-9">{% trans 'Invoice Number' %}</th>
</tr>
</thead>
@ -178,12 +240,15 @@
<td class="fs-9">{{ car.stock_type|capfirst }}</td>
<td class="fs-9">{{ car.created_at|date }}</td>
<td class="fs-9">{{ car.invoice.date_paid|date|default_if_none:"-" }}</td>
<td class="fs-9">{{ car.cost_price }} <span class="icon-saudi_riyal"></span></td>
<td class="fs-9">{{ car.marked_price }} <span class="icon-saudi_riyal"></span></td>
<td class="fs-9">{{ car.total_discount }} <span class="icon-saudi_riyal"></span></td># TODO : check later
<td class="fs-9">{{ car.selling_price }} <span class="icon-saudi_riyal"></span></td>
<td class="fs-9">{{ car.vat_amount }} <span class="icon-saudi_riyal"></span></td># TODO : check later
<td class="fs-9">{{ car.invoice.invoice_number }}</td>
<td class="fs-9"><span>{{ car.cost_price }}<span class="icon-saudi_riyal"></span></span></td>
<td class="fs-9"><span>{{ car.marked_price }} <span class="icon-saudi_riyal"></span></span></td>
<td class="fs-9"><span>{{ car.discount}} <span class="icon-saudi_riyal"></span></span></td>
<td class="fs-9"><span>{{ car.final_price}} <span class="icon-saudi_riyal"></span></span></td>
<td class="fs-9"><span>{{ car.vat_amount|floatformat:2 }} <span class="icon-saudi_riyal"></span></span></td>
<td class="fs-9"><span>{{ car.get_additional_services.total|floatformat:2}} <span class="icon-saudi_riyal"></span></span></td>
<td class="fs-9"><span>{{ car.get_additional_services.services_vat|floatformat:2}}<span class="icon-saudi_riyal"></span></span></td>
<td class="fs-9"><span>{{ car.final_price_plus_services_plus_vat|floatformat:2 }}<span class="icon-saudi_riyal"></span></span></td>
<td class="fs-9"><span>{{ car.invoice.invoice_number }}</td>
</tr>
{% endfor %}
</tbody>

View File

@ -36,13 +36,13 @@
}
.color-radio:checked + .color-display {
border: 2px solid #0d6efd;
box-shadow: 0 0 0 3px rgba(13, 110, 253, 0.25);
border: 3px solid rgb(44, 229, 44);
box-shadow: 0 0 10px rgba(44, 123, 229, 0.5);
}
.color-radio:focus + .color-display {
border-color: #86b7fe;
box-shadow: 0 0 0 3px rgba(13, 110, 253, 0.25);
border: 3px solid rgb(44, 229, 44);
box-shadow: 0 0 10px rgba(44, 123, 229, 0.5);
}
.color-display {

View File

@ -17,8 +17,7 @@
<table class="table table-hover table-bordered">
<thead class="">
<tr>
<th style="min-width: 600px"
class="d-flex justify-content-between align-items-center">
<th class="d-flex justify-content-between align-items-center">
{% trans 'Item' %}
{% if po_model.is_draft %}
<button type="button"

View File

@ -124,6 +124,7 @@
data-bs-target="#confirmModal">
<span class="d-none d-sm-inline-block"><i class="fa-solid fa-check-double"></i> {% trans 'Mark As Approved' %}</span>
</button>
{% endif %}
{% if estimate.can_approve and not request.is_manager %}
<button class="btn btn-phoenix-warning" disabled>
@ -134,6 +135,9 @@
{% if perms.django_ledger.change_estimatemodel %}
<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>
<a href="{% url 'estimate_print' request.dealer.slug estimate.pk %}" 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' %}</span>
</a>
{% endif %}
{% if estimate.sale_orders.first %}
<!--if sale order exist-->
@ -283,7 +287,7 @@
<td class="align-middle ps-4 fw-semibold text-body-highlight" colspan="7">{% trans "Additional Services" %}</td>
<td class="align-middle text-start fw-semibold">
{% for service in data.additional_services.services %}
<small><span class="fw-semibold">+ {{ service.name }} - {{ service.price_|floatformat }}<span class="icon-saudi_riyal"></span></span></small>
<small><span class="fw-semibold">+ {{ service.0.name }} - {{ service.0.price_|floatformat }}<span class="icon-saudi_riyal"></span></span></small>
<br>
{% endfor %}
{% if estimate.is_draft %}

View File

@ -1,11 +1,10 @@
{% load i18n static custom_filters num2words_tags %}
<!DOCTYPE html>
<html lang="ar">
<html lang="ar" dir="rtl">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Invoice</title>
<!-- CSS -->
<title>Quotation</title>
<link href="{% static 'css/theme.min.css' %}"
type="text/css"
rel="stylesheet"
@ -15,70 +14,80 @@
rel="stylesheet"
id="user-style-default">
<link href="{% static 'css/custom.css' %}" type="text/css" rel="stylesheet">
<!-- Google Fonts - Roboto -->
<link href="https://fonts.googleapis.com/css2?family=Roboto:wght@300;400;500;700&display=swap"
rel="stylesheet">
<!-- Custom CSS -->
<style>
body {
font-family: 'Roboto', sans-serif;
margin: 0;
padding: 10mm;
padding: 0;
background-color: #f8f9fa;
}
.invoice-container {
width: 210mm;
min-height: 297mm;
padding: 10mm;
margin: auto;
margin: 10mm auto;
background: white;
border-radius: 5px;
box-shadow: 0 0 5px rgba(0, 0, 0, 0.1);
position: relative;
display: flex;
flex-direction: column;
}
.invoice-content {
flex-grow: 1;
}
.invoice-header {
text-align: center;
border-bottom: 2px solid #dee2e6;
padding-bottom: 10px;
margin-bottom: 20px;
}
.qr-code {
text-align: center;
margin-top: 10px;
}
.qr-code img {
width: 3cm;
height: 3cm;
border-radius: 0.3333333333rem;
}
.invoice-details, .invoice-table {
font-size: 12px;
font-size: 14px;
}
.invoice-table th {
background-color: #f8f9fa;
font-weight: 600;
}
.invoice-total {
text-align: right;
font-size: 13px;
font-size: 16px;
font-weight: 600;
margin-top: 10px;
}
.footer-note {
position: absolute;
bottom: 10mm;
left: 10mm;
right: 10mm;
font-size: 10px;
margin-top: auto;
padding-top: 10mm;
font-size: 13px;
display: flex;
justify-content: space-between;
align-items: center;
border-top: 2px solid #dee2e6;
}
.logo-img img {
width: 10mm;
height: 10mm;
}
</style>
</head>
<body>
@ -92,267 +101,262 @@
</div>
<div class="col-8"></div>
</div>
<div class="invoice-container" id="invoice-content">
<div class="invoice-header">
<h5 class="fs-5">
<span>Quotation</span>&nbsp;/&nbsp;<span>عرض سعر</span>
</h5>
</div>
<div class="invoice-details p-1">
<table class="table table-sm table-responsive border-gray-50">
<tr>
<td></td>
<td></td>
<td>
<div class="qr-code">
{% if dealer.logo %}
<img class="rounded-soft"
src="{{ dealer.logo.url|default:'' }}"
alt="Dealer Logo" />
{% endif %}
</div>
</td>
</tr>
<tr>
<td class="ps-1">
<strong>Customer Name</strong>
</td>
<td class="text-center">
{{ dealer.arabic_name }}
<br>
{{ dealer.name }}
</td>
<td class="text-end">
<strong>{{ dealer.arabic_name }}</strong>
</td>
</tr>
<tr>
<td class="ps-1">
<strong>Address</strong>
</td>
<td class="text-center">{{ dealer.address }}</td>
<td class="text-end">
<strong>العنوان</strong>
</td>
</tr>
<tr>
<td class="ps-1">
<strong>Phone</strong>
</td>
<td class="text-center">{{ dealer.phone_number }}</td>
<td class="text-end">
<strong>جوال</strong>
</td>
</tr>
<tr>
<td>
<strong>VAT Number</strong>
</td>
<td>{{ 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>Quotation&nbsp;Number</strong>
</td>
<td class="text-center">{{ estimate.estimate_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">{{ estimate.date_approved| date:"Y/m/d" }}</td>
<td class="text-end p-1">
<strong>التاريخ</strong>
</td>
</tr>
<tr>
<td class="ps-1">
<strong>Customer</strong>
</td>
<td class="text-center">{{ estimate.customer.customer_name }}</td>
<td class="text-end p-1">
<strong>العميل</strong>
</td>
</tr>
<tr>
<td class="ps-1">
<strong>VAT&nbsp;ID</strong>
</td>
<td class="text-center">{{ estimate.customer.vrn|default:"-" }}</td>
<td class="text-end p-1">
<strong>الرقم الضريبي</strong>
</td>
</tr>
<tr>
<td class="ps-1">
<strong>Email</strong>
</td>
<td class="text-center">{{ estimate.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">{{ estimate.get_terms_display }}</td>
<td class="text-end p-1">
<strong>طريقة&nbsp;الدفع</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>
<div class="invoice-container" id="estimate-content">
<div class="invoice-content">
<div class="invoice-header">
<h5 class="fs-5">
<span>Quotation</span>&nbsp;/&nbsp;<span>عرض سعر</span>
</h5>
</div>
<div class="invoice-details p-1">
<div class="d-flex justify-content-end align-items-end">
<div class="dealer-logo ">
{% if request.dealer.logo %}
<img class="rounded-soft" style="max-width:200px; max-height:200px;"
src="{{ request.dealer.logo.url|default:'' }}"
alt="Dealer Logo" />
{% endif %}
</div>
</div>
<table class="table table-sm table-responsive border-gray-50">
<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">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">Including VAT</span> / <span class="fs-10">شامل الضريبة</span>
</th>
<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>
</thead>
<tbody>
{% for item in data.cars %}
<tr>
<td class="ps-1 fs-10 align-content-center" colspan="3">{{ item.make }} - {{ item.model }} - {{ item.trim }}</td>
<td class="text-center fs-10 align-content-center">{{ item.year }}</td>
<td class="ps-1 fs-10 align-content-center">{{ item.vin }}</td>
<td class="text-center fs-10 align-content-center">{{ item.quantity|floatformat:-1 }}</td>
<td class="text-center fs-10 align-content-center">{{ item.unit_price|floatformat:2 }}</td>
<td class="text-center fs-10 align-content-center">{{ item.total_vat|floatformat:2 }}</td>
</tr>
{% endfor %}
<tr>
<td class="ps-1 fs-10 align-content-center" colspan="5"></td>
<td class="text-center fs-10 align-content-center">{{ data.quantity|floatformat:-1 }}</td>
<td class="text-center fs-10 align-content-center">{{ data.total_price_before_discount|floatformat:2 }}</td>
<td class="text-center fs-10 align-content-center">{{ data.grand_total|floatformat:2 }}</td>
<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>
</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.additionals %}
<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>Quotation&nbsp;Number</strong>
</td>
<td class="text-center">{{ estimate.estimate_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">{{ estimate.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">{{ estimate.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">{{ estimate.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">{{ estimate.get_terms_display }}</td>
<td class="text-end p-1">
<strong>طريقة&nbsp;الدفع</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-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">
<span class="fs-10">Including VAT</span> / <span class="fs-10">شامل الضريبة</span>
<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>
{% for item in data.additionals %}
<tr>
<td class="ps-1 text-start fs-10 align-content-center">{{ item.name }}</td>
<td class="ps-1 text-center fs-10 align-content-center">{{ item.price|floatformat }}</td>
<td class="ps-1 text-center fs-10 align-content-center">{{ item.price_|floatformat }}</td>
</tr>
{% endfor %}
<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>
{% 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">VAT</strong>
</td>
<td class="text-center">
<span class="fs-9">{{ data.total_vat_amount|floatformat }} <span class="icon-saudi_riyal"></span></span>
</td>
<td class="text-end">
<strong class="fs-9">ضريبة&nbsp;القيمة&nbsp;المضافة</strong>
</td>
</tr>
<tr>
<td class="text-start ps-1">
<strong class="fs-9">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">الإجمالي</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 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">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">الإجمالي</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>
<br>
<br>
<br>
<br>
<br>
<br>
<div class="footer-note">
<div class="logo-img text-center">
<img src="{% static 'images/logos/logo-d-pdf.png' %}" alt="Logo" />
<p class="fs-11 fw-bold">
<p class="fs-9 fw-bold">
<span>Haikal</span>&nbsp;|&nbsp;<span>هيكل</span>
</p>
</div>
<p class="fs-11">
<span class="fw-thin">Powered&nbsp;by&nbsp;</span><a class="text-decoration-none"
href="https://tenhal.sa"
style="color: #112e40"><span>TENHAL</span>&nbsp;|&nbsp;<span>تنحل</span></a>
<span class="fw-thin">Powered&nbsp;by&nbsp;</span>
<a class="text-decoration-none fs-9"
href="https://tenhal.sa"
style="color: #112e40;"><span>TENHAL</span>&nbsp;|&nbsp;<span>تنحل</span></a>
</p>
</div>
</div>
<script src="{% static 'vendors/bootstrap/bootstrap.min.js' %}"></script>
<script src="{% static 'js/html2pdf.bundle.min.js' %}"></script>
<script>
document.getElementById('download-pdf').addEventListener('click', function () {
html2pdf().from(document.getElementById('invoice-content')).set({
html2pdf().from(document.getElementById('estimate-content')).set({
margin: 0,
filename: "{{ invoice.invoice_number }}_{{ invoice.customer.customer_name }}_{{ invoice.date_in_review|date:'Y-m-d' }}.pdf",
filename: "{{ estimate.estimate_number }}_{{ estimate.customer.customer_name  }}_{{estimate.date_approved|date:'Y-m-d' }}.pdf",
image: { type: 'jpeg', quality: 0.98 },
html2canvas: { scale: 3 },
jsPDF: { unit: 'mm', format: 'a3', orientation: 'portrait' }
@ -360,4 +364,4 @@
});
</script>
</body>
</html>
</html>

View File

@ -358,7 +358,7 @@
<td class="align-middle ps-4 fw-semibold text-body-highlight" colspan="7">{% trans "Additional Services" %}</td>
<td class="align-middle text-start fw-bold">
{% for service in data.additional_services.services %}
<small><span class="fw-bold">+ {{ service.name }} - {{ service.price_|floatformat }}<span class="icon-saudi_riyal"></span></span></small>
<small><span class="fw-bold">+ {{ service.0.name }} - {{ service.0.price_|floatformat }}<span class="icon-saudi_riyal"></span></span></small>
<br>
{% endfor %}
</td>

View File

@ -1,11 +1,10 @@
{% load i18n static custom_filters num2words_tags %}
<!DOCTYPE html>
<html lang="ar">
<html lang="ar" dir="rtl">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Invoice</title>
<!-- CSS -->
<link href="{% static 'css/theme.min.css' %}"
type="text/css"
rel="stylesheet"
@ -15,75 +14,80 @@
rel="stylesheet"
id="user-style-default">
<link href="{% static 'css/custom.css' %}" type="text/css" rel="stylesheet">
<!-- Google Fonts - Roboto -->
<link href="https://fonts.googleapis.com/css2?family=Roboto:wght@300;400;500;700&display=swap"
rel="stylesheet">
<!-- Custom CSS -->
<style>
body {
font-family: 'Roboto', sans-serif;
margin: 0;
padding: 10mm;
padding: 0;
background-color: #f8f9fa;
}
.invoice-container {
width: 210mm;
min-height: 297mm;
padding: 10mm;
margin: auto;
margin: 10mm auto;
background: white;
border-radius: 5px;
box-shadow: 0 0 5px rgba(0, 0, 0, 0.1);
position: relative;
display: flex;
flex-direction: column;
}
.invoice-content {
flex-grow: 1;
}
.invoice-header {
text-align: center;
border-bottom: 2px solid #dee2e6;
padding-bottom: 10px;
margin-bottom: 20px;
}
.qr-code {
text-align: center;
margin-top: 10px;
}
.qr-code img {
width: 3cm;
height: 3cm;
border-radius: 0.3333333333rem;
}
.dealer-logo img {
width: 3cm;
height: 1cm;
position: relative;
}
.invoice-details, .invoice-table {
font-size: 12px;
font-size: 14px;
}
.invoice-table th {
background-color: #f8f9fa;
font-weight: 600;
}
.invoice-total {
text-align: right;
font-size: 13px;
font-size: 16px;
font-weight: 600;
margin-top: 10px;
}
.footer-note {
position: absolute;
bottom: 10mm;
left: 10mm;
right: 10mm;
font-size: 10px;
margin-top: auto;
padding-top: 10mm;
font-size: 13px;
display: flex;
justify-content: space-between;
align-items: center;
border-top: 2px solid #dee2e6; /* Add a top border to separate from content */
}
.logo-img img {
width: 10mm;
height: 10mm;
}
</style>
</head>
<body>
@ -97,271 +101,274 @@
</div>
<div class="col-8"></div>
</div>
<div class="invoice-container" id="invoice-content">
<div class="invoice-header">
<h5 class="fs-5">Tax&nbsp;Invoice&nbsp;&nbsp;/&nbsp;&nbsp;فاتورة&nbsp;ضريبية</h5>
</div>
<div class="invoice-details p-1">
<div class="d-flex justify-content-center align-items-center">
<div class="qr-code">
<img src="{% static 'qr_code/Marwan_qr.png' %}" alt="QR Code">
</div>
<div class="invoice-content">
<div class="invoice-header">
<h5 class="fs-5">
<span>Invoice</span>&nbsp;/&nbsp;<span>فاتورة</span>
</h5>
</div>
<div class="d-flex justify-content-end align-items-end">
<div class="dealer-logo ">
{% if dealer.logo %}
<img class="rounded-soft"
src="{{ dealer.logo.url|default:'' }}"
alt="Dealer Logo" />
{% endif %}
<div class="invoice-details p-1">
<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>
<table class="table table-sm table-bordered border-gray-50">
<tr>
<td class="ps-1">
<strong>Customer Name</strong>
</td>
<td class="text-center">
{{ dealer.arabic_name }}
<br>
{{ dealer.name }}
</td>
<td class="text-end">
<strong>اسم&nbsp;العميل</strong>
</td>
</tr>
<tr>
<td class="ps-1">
<strong>Address</strong>
</td>
<td class="text-center">{{ dealer.address }}</td>
<td class="text-end">
<strong>العنوان</strong>
</td>
</tr>
<tr>
<td class="ps-1">
<strong>Phone</strong>
</td>
<td class="text-center">{{ dealer.phone_number }}</td>
<td class="text-end">
<strong>جوال</strong>
</td>
</tr>
<tr>
<td class="ps-1">
<strong>VAT Number</strong>
</td>
<td class="text-center">{{ dealer.vrn }}</td>
<td class="text-end p-1">
<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>رقم&nbsp;الفاتورة</strong>
</td>
</tr>
<tr>
<td class="ps-1">
<strong>Date</strong>
</td>
<td class="text-center">{{ invoice.date_in_review| date:"Y/m/d" }}</td>
<td class="text-end p-1">
<strong>التاريخ</strong>
</td>
</tr>
<tr>
<td class="ps-1">
<strong>Customer</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>VAT&nbsp;ID</strong>
</td>
<td class="text-center">{{ invoice.customer.vrn|default:"-" }}</td>
<td class="text-end p-1">
<strong>الرقم&nbsp;الضريبي</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>البريد&nbsp;الالكتروني</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>طريقة&nbsp;الدفع</strong>
</td>
</tr>
</table>
</div>
<div class="d-flex justify-content-between">
<span class="fs-9 fw-thin">Car&nbsp;Details</span>
<span class="fs-9 fw-thin">تفاصيل&nbsp;السيارة</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">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">Including VAT</span> / <span class="fs-10">شامل الضريبة</span>
</th>
</tr>
</thead>
<tbody>
{% for item in data.cars %}
<tr>
<td class="ps-1 fs-10 align-content-center" colspan="3">{{ item.make }} - {{ item.model }} - {{ item.trim }}</td>
<td class="text-center fs-10 align-content-center">{{ item.year }}</td>
<td class="ps-1 fs-10 align-content-center">{{ item.vin }}</td>
<td class="text-center fs-10 align-content-center">{{ item.quantity|floatformat }}</td>
<td class="text-center fs-10 align-content-center">{{ item.unit_price|floatformat }}</td>
<td class="text-center fs-10 align-content-center">{{ item.total_vat|floatformat }}</td>
</tr>
{% endfor %}
<tr>
<td class="ps-1 fs-10 align-content-center" colspan="5"></td>
<td class="text-center fs-10 align-content-center">{{ data.quantity|floatformat }}</td>
<td class="text-center fs-10 align-content-center">{{ data.total_price_before_discount|floatformat }}</td>
<td class="text-center fs-10 align-content-center">{{ data.grand_total|floatformat }}</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.additionals %}
<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">
<span class="fs-10">Including VAT</span> / <span class="fs-10">شامل الضريبة</span>
<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>
{% for item in data.additionals %}
<tr>
<td class="ps-1 text-start fs-10 align-content-center">{{ item.name }}</td>
<td class="ps-1 text-center fs-10 align-content-center">{{ item.price|floatformat }}</td>
<td class="ps-1 text-center fs-10 align-content-center">{{ item.price_|floatformat }}</td>
</tr>
{% endfor %}
<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>
{% 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">VAT</strong>
</td>
<td class="text-center">
<span class="fs-9">{{ data.total_vat_amount|floatformat }} <span class="icon-saudi_riyal"></span></span>
</td>
<td class="text-end">
<strong class="fs-9">ضريبة&nbsp;القيمة&nbsp;المضافة</strong>
</td>
</tr>
<tr>
<td class="text-start ps-1">
<strong class="fs-9">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">الإجمالي</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 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>
<br>
<br>
<br>
<br>
<br>
<br>
<div class="footer-note">
<div class="logo-img text-center">
<img src="{% static 'images/logos/logo-d-pdf.png' %}" alt="Logo" />
<p class="fs-11 fw-bold">
<p class="fs-9 fw-bold">
<span>Haikal</span>&nbsp;|&nbsp;<span>هيكل</span>
</p>
</div>
<p class="fs-11">
<span class="fw-thin">Powered&nbsp;by&nbsp;</span><a class="text-decoration-none"
href="https://tenhal.sa"
style="color: #112e40"><span>TENHAL</span>&nbsp;|&nbsp;<span>تنحل</span></a>
<span class="fw-thin">Powered&nbsp;by&nbsp;</span>
<a class="text-decoration-none fs-9"
href="https://tenhal.sa"
style="color: #112e40;"><span>TENHAL</span>&nbsp;|&nbsp;<span>تنحل</span></a>
</p>
</div>
</div>
<script src="{% static 'vendors/bootstrap/bootstrap.min.js' %}"></script>
<script src="{% static 'js/html2pdf.bundle.min.js' %}"></script>
<script>
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_in_review|date:'Y-m-d' }}.pdf",
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: 'a4', orientation: 'portrait' }
jsPDF: { unit: 'mm', format: 'a3', orientation: 'portrait' }
}).save();
});
</script>
</body>
</html>
</html>

View File

@ -2,108 +2,107 @@
{% load static %}
{% load i18n %}
{% block title %}
{{ _("View Staff") }}
{{ user_.name }}
{% endblock title %}
{% block content %}
<div class="row my-5">
<div class="card rounded ">
<div class="card-header ">
<p class="mb-0">
<i class="fa-solid fa-user"></i> {{ _("User Details") }}
</p>
<div class="container py-4">
<div class="d-flex justify-content-between align-items-center mb-4">
<div class="d-flex align-items-center">
<a href="{% url 'user_list' request.dealer.slug %}" class="btn btn-phoenix-secondary btn-sm me-3">
<i class="fa-regular fa-circle-left me-2"></i>{% trans "Back to Staffs" %}
</a>
<h1 class="h5 fw-bold mb-0">{% trans "Staff Profile" %}</h1>
</div>
<div class="card-body">
<div class="row">
<div class="col-md-2">
{% if user_.logo %}
<img class="rounded-circle"
src="{{ user_.logo.url }}"
style="width: 100px;
height: 100px"
alt="User Image">
{% else %}
<img class="rounded-circle"
src="{% static 'img/default-user.png' %}"
style="width: 100px;
height: 100px"
alt="Default User Image">
{% endif %}
</div>
<div class="col-md-5">
<p>
<strong>{{ _("Name") }}:</strong> {{ user_.name }}
</p>
<p>
<strong>{{ _("Arabic Name") }}:</strong> {{ user_.arabic_name }}
</p>
<p>
<strong>{{ _("Email") }}:</strong> {{ user_.email }}
</p>
</div>
<div class="col-md-5">
<p>
<strong>{{ _("Phone Number") }}:</strong> {{ user_.phone_number }}
</p>
<div>
<span><strong>{{ _("Roles") }}:</strong></span>
{% for group in user_.groups %}
<span><strong>{{ group.name }}</strong></span>
{% if not forloop.last %}<span>&amp;</span>{% endif %}
{% endfor %}
<div>
<a href="{% url 'user_update' request.dealer.slug user_.slug %}" class="btn btn-phoenix-primary btn-sm me-2">
<i class="fa-solid fa-pen-to-square me-2"></i>{% trans "Edit" %}
</a>
<button class="btn btn-phoenix-danger btn-sm delete-btn" data-url="{% url 'user_delete' request.dealer.slug user_.slug %}"
data-message='{% trans "Are you sure you want to delete this user?" %}'
data-bs-toggle="modal" data-bs-target="#deleteModal">
<i class="fas fa-trash-alt me-2"></i>{% trans "Delete" %}
</button>
</div>
</div>
<div class="row g-3">
<div class="col-lg-4">
<div class="card h-100 border-0 shadow-sm rounded-3">
<div class="card-body text-center p-3">
<div class="position-relative d-inline-block mb-3">
{% if user_.logo %}
<img class="rounded-circle border border-4 border-body-tertiary"
src="{{ user_.logo.url }}" alt="{{ user_.name }}"
style="object-fit: cover; width: 100px; height: 100px;">
{% else %}
<div class="rounded-circle d-inline-flex align-items-center justify-content-center bg-primary text-white"
style="width: 100px; height: 100px; font-size: 2.5rem;">
{{ user_.name|first|upper }}
</div>
{% endif %}
</div>
<h2 class="h6 fw-bold mb-1">{{ user_.name }}</h2>
<p class="small mb-3"> <a href="mailto:{{ user_.email }}" class="text-secondary">{{ user_.email }}</a></p>
<div class="d-grid gap-1">
<a href="tel:{{ user_.phone_number }}" class="btn btn-outline-primary btn-sm">
<i class="fas fa-phone-alt me-2"></i>{{ user_.phone_number|default:"N/A" }}
</a>
</div>
</div>
</div>
</div>
<div class="table-responsive scrollbar mx-n1 px-1">
<div class="card-header "></div>
<h4 class="my-4">Groups</h4>
<a class="btn btn-sm btn-phoenix-primary mt-2 mb-4"
href="{% url 'user_groups' request.dealer.slug user_.slug %}"><i class="fa-solid fa-users"></i> Manage Groups</a>
<table class="table table-hover table-responsive-sm fs-9 mb-0">
<thead>
<tr>
<th>{% trans 'name'|capfirst %}</th>
</tr>
</thead>
<tbody>
{% for group in user_.groups %}
<tr>
<td>{{ group }}</td>
</tr>
{% empty %}
<tr>
<td>{% trans "No Group" %}</td>
</tr>
{% endfor %}
</tbody>
</table>
</div>
<div class="card-footer d-flex ">
<a class="btn btn-sm btn-phoenix-primary me-3"
href="{% url 'user_update' request.dealer.slug user_.slug %}">
{{ _("Edit") }}
<i class="fa-solid fa-pen-to-square"></i>
</a>
<button class="btn btn-phoenix-danger btn-sm delete-btn me-3"
data-url="{% url 'user_delete' request.dealer.slug user_.slug %}"
data-message='{{ _("Are you sure you want to delete this user?") }}'
data-bs-toggle="modal"
data-bs-target="#deleteModal">
{{ _("Delete") }}
<i class="fas fa-trash"></i>
</button>
<a class="btn btn-sm btn-phoenix-secondary me-3"
href="{% url 'user_list' request.dealer.slug %}">
{{ _("Back to Staffs List") }}
<i class="fa-regular fa-circle-left"></i>
</a>
<a class="btn btn-sm btn-phoenix-secondary"
href="{% url 'staff_password_reset' request.dealer.slug user_.pk %}">
{{ _("Reset Password") }}
<i class="fa-solid fa-key"></i>
</a>
<div class="col-lg-8">
<div class="card h-100 border-0 shadow-sm rounded-3">
<div class="card-body p-3">
<h4 class="mb-3 text-primary h6">{% trans "Personal Information" %}</h4>
<div class="row g-2 mb-4">
<div class="col-md-6">
<p class="small mb-0">{% trans "Arabic Name" %}</p>
<p class="fw-bold small mb-0">{{ user_.arabic_name|default:"N/A" }}</p>
</div>
<div class="col-md-6">
<p class="small mb-0">{% trans "Roles" %}</p>
<p class="fw-bold small mb-0">
{% for group in user_.groups %}
{{ group.name }} {% if not forloop.last %}&middot;{% endif %}
{% empty %}
<span class="text-muted">N/A</span>
{% endfor %}
</p>
</div>
</div>
<div class="d-flex justify-content-between align-items-center mb-3">
<h4 class="mb-0 text-primary h6">{% trans "Assigned Groups" %}</h4>
<a class="btn btn-sm btn-phoenix-primary" href="{% url 'user_groups' request.dealer.slug user_.slug %}">
<i class="fa-solid fa-users me-2"></i>{% trans "Manage Groups" %}
</a>
</div>
<div class="table-responsive scrollbar">
<table class="table table-hover table-striped fs--1 mb-0">
<thead>
<tr>
<th scope="col" class="text-nowrap">{% trans 'Group Name'|capfirst %}</th>
</tr>
</thead>
<tbody>
{% for group in user_.groups %}
<tr>
<td class="small">{{ group }}</td>
</tr>
{% empty %}
<tr>
<td class="text-secondary small">{% trans "This user is not assigned to any groups." %}</td>
</tr>
{% endfor %}
</tbody>
</table>
</div>
</div>
</div>
</div>
</div>
</div>
{% include 'modal/delete_modal.html' %}
{% endblock %}
{% endblock %}

View File

@ -5,32 +5,37 @@
{% trans "Group" %}
{% endblock title %}
{% block content %}
<div class="row">
<div class="row">
<div class="col-sm-9">
<div class="d-sm-flex justify-content-between">
<h3 class="mb-3"><i class="fa-solid fa-users"></i> {{ _("Manage Groups") }}</h3>
<div class="row justify-content-center mt-5 mb-3">
<div class="col-lg-8 col-md-10">
<div class="card shadow-sm border-0 rounded-3">
<div class="card-header bg-gray-200 py-3 border-0 rounded-top-3">
<h3 class="mb-0 fs-5 text-center">
{{ _("Manage Groups") }}<i class="fa-solid fa-users ms-2 text-primary"></i>
</h3>
</div>
<div class="card-body bg-light-subtle">
<form class="row g-3 mb-3" method="post" class="form" enctype="multipart/form-data" novalidate >
{% csrf_token %}
{{ redirect_field }}
{{ form|crispy }}
{% for error in form.errors %}<div class="text-danger">{{ error }}</div>{% endfor %}
<hr class="my-2">
<div class="d-grid gap-2 d-md-flex justify-content-md-center mt-3">
<button class="btn btn-lg btn-phoenix-primary md-me-2" type="submit"><i class="fa-solid fa-floppy-disk me-1"></i>{{ _("Save") }}</button>
<a href="{% url 'user_detail' request.dealer.slug staff.slug %}" class="btn btn-lg btn-phoenix-secondary"><i class="fa-solid fa-ban me-1"></i>{% trans "Cancel" %}</a>
</div>
</form>
</div>
</div>
</div>
</div>
<div class="row">
<div class="col-sm-9">
<form class="row g-3 mb-9" method="post" class="form" novalidate>
{% csrf_token %}
{{ redirect_field }}
{{ form|crispy }}
{% for error in form.errors %}
<div class="text-danger">{{ error }}</div>
{% endfor %}
<div class="d-flex mb-3">
<a href="{% url 'user_detail' request.dealer.slug staff.slug %}" class="btn btn-phoenix-primary me-2 px-6"><i class="fa-solid fa-ban"></i> {% trans "Cancel" %}</a>
<button class="btn btn-phoenix-primary" type="submit">
<i class="fa-solid fa-floppy-disk"></i>
{{ _("Save") }}
</button>
</div>
</form>
</div>
</div>
</div>
{% endblock %}

View File

@ -1,94 +1,100 @@
{% extends "base.html" %}
{% load i18n %}
{% load render_table from django_tables2 %}
{% block title %}
{% trans "Staffs" %}
{% endblock title %}
{% block content %}
{%if users or request.GET.q%}
<section class="">
<div class="row mt-4">
<div class="col-auto">
<div class="d-md-flex justify-content-between">
<div>
{% if request.user.userplan %}
<a href="{% url 'user_create' request.dealer.slug %}"
class="btn btn-sm btn-phoenix-primary me-4"><i class="fa-solid fa-user-tie me-1"></i> {% trans "Add New Staff" %}</a>
<a href="{% url 'group_list' request.dealer.slug %}"
class="btn btn-sm btn-phoenix-success"><i class="fa-solid fa-user-group me-1"></i> {% trans "Manage Groups & Permissions" %}</a>
{% else %}
{%if users or request.GET.q%}
<div class="container py-4">
<div class="d-flex align-items-center justify-content-between mb-4">
<h1 class="h3 mb-0">{% trans "Staffs" %}<i class="fa-solid fa-user-tie text-primary ms-2"></i></h1>
{% if request.user.userplan %}
<div>
<a href="{% url 'user_create' request.dealer.slug %}" class="btn btn-phoenix-primary me-2">
<i class="fa-solid fa-plus me-2"></i>{% trans "Add New Staff" %}
</a>
<a href="{% url 'group_list' request.dealer.slug %}" class="btn btn-phoenix-secondary">
<i class="fa-solid fa-user-group me-2"></i>{% trans "Manage Groups" %}
</a>
</div>
{% endif %}
</div>
{% url "pricing_page" request.dealer.slug as pricing_page_url %}
{% include "message-illustration.html" with value1="No Active Plan, please create your subscription plan." value2="Manage Subscription" url=pricing_page_url %}
{% endif %}
</div>
<div class="card shadow-sm border-0">
<div class="card-body p-0">
<div class="table-responsive scrollbar">
<table class="table table-hover table-striped mb-0">
<thead>
<tr class="bg-body-highlight">
<th>{% trans 'name'|capfirst %}</th>
<th>{% trans 'email'|capfirst %}</th>
<th>{% trans 'phone number'|capfirst %}</th>
<th>{% trans 'role'|capfirst %}</th>
<th>{% trans 'actions'|capfirst %}</th>
</tr>
</thead>
<tbody>
{% for user in users %}
<tr>
<td class="align-middle white-space-nowrap ps-2">
<div class="d-flex align-items-center">
<div class="avatar avatar-tiny me-2">
{% if user.logo %}
<img class="avatar-img rounded-circle"
src="{{ user.thumbnail.url }}"
onerror="this.src='/static/user-logo.jpg'"
alt="{{ user.name }}'s logo">
{% else %}
<div class="rounded-circle bg-light d-flex justify-content-center align-items-center" style="width: 2rem; height: 2rem; font-size: 0.8rem;">
{{ user.name|first|upper }}
</div>
{% endif %}
</div>
<a class="fw-bold text-decoration-none text-dark"
href="{% url 'user_detail' request.dealer.slug user.slug %}">
{{ user.name }}
</a>
</div>
</td>
<td class="align-middle white-space-nowrap">{{ user.email }}</td>
<td class="align-middle white-space-nowrap">{{ user.phone_number }}</td>
<td class="align-middle white-space-nowrap">
{% for group in user.groups %}
<span class="badge bg-primary me-1">{{ group.name|title }}</span>
{% endfor %}
</td>
<td class="align-middle white-space-nowrap">
<a class="btn btn-phoenix-success"
href="{% url 'user_detail' request.dealer.slug user.slug %}">
<i class="fa-solid fa-eye me-1"></i>{% trans 'View' %}
</a>
</td>
</tr>
{% endfor %}
</tbody>
</table>
</div>
</div>
<div class="table-responsive scrollbar mx-n1 px-1 mt-3">
<table class="table align-items-center table-flush table-hover">
<thead>
<tr class="bg-body-highlight">
<th>{% trans 'name'|capfirst %}</th>
<th>{% trans 'email'|capfirst %}</th>
<th>{% trans 'phone number'|capfirst %}</th>
<th>{% trans 'role'|capfirst %}</th>
<th>{% trans 'actions'|capfirst %}</th>
</tr>
</thead>
<tbody>
{% for user in users %}
<tr>
<td class="align-middle white-space-nowrap ps-1">
<div class="d-flex align-items-center">
<div class="avatar avatar-tiny me-2">
{% if user.logo %}
<img class="avatar-img rounded-circle"
src="{{user.thumbnail.url}}"
onerror="this.src='/static/user-logo.jpg'"
alt="Logo">
{% endif %}
</div>
<a class="fs-8 fw-bold"
href="{% url 'user_detail' request.dealer.slug user.slug %}">{{ user.arabic_name }}</a>
</div>
</td>
<td class="align-middle white-space-nowrap align-items-center">{{ user.email }}</td>
<td class="align-middle white-space-nowrap align-items-center justify-content-center">{{ user.phone_number }}</td>
<td>
{% for group in user.groups %}
<span class="badge badge-sm bg-primary text-center"><i class="fa-solid fa-scroll"></i> {% trans group.name|title %}</span>
{% endfor %}
</td>
<td class="align-middle white-space-nowrap">
<a class="btn btn-phoenix-success"
href="{% url 'user_detail' request.dealer.slug user.slug %}">
<i class="fa-solid fa-eye"></i>
{% trans 'view'|capfirst %}
</a>
</td>
</tr>
{% endfor %}
</tbody>
</table>
</div>
<div class="d-flex justify-content-center">
{% if is_paginated %}
{% if is_paginated %}
<div class="d-flex justify-content-center mt-3">
{% include 'partials/pagination.html' %}
{% endif %}
</div>
</div>
{% endif %}
</div>
</section>
{% else %}
{% if request.user.userplan %}
{% url "user_create" request.dealer.slug as create_staff_url %}
{% url "user_create" request.dealer.slug as create_staff_url %}
{% include "empty-illustration-page.html" with value="staff" url=create_staff_url %}
{% else %}
{% url "pricing_page" request.dealer.slug as pricing_page_url %}
{% include "message-illustration.html" with value1=_("No active plan, Please create a subscription plan.") value2=_("Buy Plan") url=pricing_page_url %}
{% endif %}
{% endif %}
{% endblock %}
{% endblock %}