Dashboards and Sale reports: Sale reports finances update and dashboards cards logic #180
@ -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(
|
||||
|
||||
@ -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,
|
||||
|
||||
@ -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")
|
||||
}
|
||||
|
||||
|
||||
|
||||
@ -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
|
||||
|
||||
218
templates/dashboards/accountant_dashboard.html
Normal file
218
templates/dashboards/accountant_dashboard.html
Normal 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 %}
|
||||
349
templates/dashboards/dealer_dashboard.html
Normal file
349
templates/dashboards/dealer_dashboard.html
Normal 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 %}
|
||||
300
templates/dashboards/inventory_dashboard.html
Normal file
300
templates/dashboards/inventory_dashboard.html
Normal 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 %}
|
||||
@ -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 %}
|
||||
367
templates/dashboards/manager_dashboard.html
Normal file
367
templates/dashboards/manager_dashboard.html
Normal 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 %}
|
||||
356
templates/dashboards/sales_dashboard.html
Normal file
356
templates/dashboards/sales_dashboard.html
Normal 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 %}
|
||||
@ -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"
|
||||
|
||||
@ -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 {
|
||||
|
||||
@ -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 %}
|
||||
|
||||
|
||||
@ -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>
|
||||
|
||||
@ -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 {
|
||||
|
||||
@ -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"
|
||||
|
||||
@ -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 %}
|
||||
|
||||
@ -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> / <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 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 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>طريقة الدفع</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> / <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 Services</span>
|
||||
<span class="fs-9 fw-thin">الخدمات الإضافية</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 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>طريقة الدفع</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 / النوع</th>
|
||||
<th class="text-center fs-10 align-content-center">Price / القيمة</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">ضريبة القيمة المضافة</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 }} <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">كتابةً: </span><span class="fs-9">{{ data.grand_total|num_to_words }} <span class="icon-saudi_riyal"></span></span>
|
||||
</td>
|
||||
</tr>
|
||||
</table>
|
||||
<div class="d-flex justify-content-between">
|
||||
<span class="fs-9 fw-thin">Additional Services</span>
|
||||
<span class="fs-9 fw-thin">الخدمات الإضافية</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 / النوع</th>
|
||||
<th class="text-center fs-10 align-content-center">Price / القيمة</th>
|
||||
<th class="text-center fs-10 align-content-center">VAT / ضريبة الخدمة</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">إجمالي ضريبة القيمة المضافة</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 }} <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">كتابةً: </span><span class="fs-9">{{ data.grand_total|num_to_words }} <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> | <span>هيكل</span>
|
||||
</p>
|
||||
</div>
|
||||
<p class="fs-11">
|
||||
<span class="fw-thin">Powered by </span><a class="text-decoration-none"
|
||||
href="https://tenhal.sa"
|
||||
style="color: #112e40"><span>TENHAL</span> | <span>تنحل</span></a>
|
||||
<span class="fw-thin">Powered by </span>
|
||||
<a class="text-decoration-none fs-9"
|
||||
href="https://tenhal.sa"
|
||||
style="color: #112e40;"><span>TENHAL</span> | <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>
|
||||
@ -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>
|
||||
|
||||
@ -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 Invoice / فاتورة ضريبية</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> / <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 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>اسم العميل</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 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_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 ID</strong>
|
||||
</td>
|
||||
<td class="text-center">{{ invoice.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">{{ invoice.customer.email|default:"N/A" }}</td>
|
||||
<td class="text-end p-1">
|
||||
<strong>البريد الالكتروني</strong>
|
||||
</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td class="ps-1">
|
||||
<strong>Terms</strong>
|
||||
</td>
|
||||
<td class="text-center">{{ invoice.get_terms_display }}</td>
|
||||
<td class="text-end p-1">
|
||||
<strong>طريقة الدفع</strong>
|
||||
</td>
|
||||
</tr>
|
||||
</table>
|
||||
</div>
|
||||
<div class="d-flex justify-content-between">
|
||||
<span class="fs-9 fw-thin">Car Details</span>
|
||||
<span class="fs-9 fw-thin">تفاصيل السيارة</span>
|
||||
</div>
|
||||
<div class="invoice-table p-1">
|
||||
<table class="table table-sm table-bordered m-1">
|
||||
<thead>
|
||||
<tr>
|
||||
<th class="text-wrap text-center align-content-center">
|
||||
<span class="fs-10">Make</span> / <span class="fs-10">الصانع</span>
|
||||
</th>
|
||||
<th class="text-wrap text-center align-content-center">
|
||||
<span class="fs-10">Model</span> / <span class="fs-10">الموديل</span>
|
||||
</th>
|
||||
<th class="text-wrap text-center align-content-center">
|
||||
<span class="fs-10">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 Services</span>
|
||||
<span class="fs-9 fw-thin">الخدمات الإضافية</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 / النوع</th>
|
||||
<th class="text-center fs-10 align-content-center">Price / القيمة</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">ضريبة القيمة المضافة</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 }} <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">كتابةً: </span><span class="fs-9">{{ data.grand_total|num_to_words }} <span class="icon-saudi_riyal"></span></span>
|
||||
</td>
|
||||
</tr>
|
||||
</table>
|
||||
<div class="d-flex justify-content-between">
|
||||
<span class="fs-9 fw-thin">Additional Services</span>
|
||||
<span class="fs-9 fw-thin">الخدمات الإضافية</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 / النوع</th>
|
||||
<th class="text-center fs-10 align-content-center">Price / السعر</th>
|
||||
<th class="text-center fs-10 align-content-center">Service VAT / ضريبة الخدمة</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">إجمالي ضريبة القيمة المضافة</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 }} <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">كتابةً: </span><span class="fs-9">{{ data.grand_total|num_to_words }} <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> | <span>هيكل</span>
|
||||
</p>
|
||||
</div>
|
||||
<p class="fs-11">
|
||||
<span class="fw-thin">Powered by </span><a class="text-decoration-none"
|
||||
href="https://tenhal.sa"
|
||||
style="color: #112e40"><span>TENHAL</span> | <span>تنحل</span></a>
|
||||
<span class="fw-thin">Powered by </span>
|
||||
<a class="text-decoration-none fs-9"
|
||||
href="https://tenhal.sa"
|
||||
style="color: #112e40;"><span>TENHAL</span> | <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>
|
||||
@ -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>&</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 %}·{% 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 %}
|
||||
@ -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 %}
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
@ -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 %}
|
||||
Loading…
x
Reference in New Issue
Block a user