Compare commits

...

4 Commits

32 changed files with 2982 additions and 1635 deletions

View File

@ -38,7 +38,7 @@ admin.site.register(models.DealerSettings)
# admin.site.register(models.SaleQuotationCar)
admin.site.register(models.SaleOrder)
admin.site.register(models.CustomGroup)
admin.site.register(models.CarFinance)
# admin.site.register(models.CarFinance)
admin.site.register(models.CarColors)
admin.site.register(models.CarRegistration)
admin.site.register(models.CustomCard)

View File

@ -32,7 +32,7 @@ from .models import (
Car,
VatRate,
CarTransfer,
CarFinance,
# CarFinance,
CustomCard,
CarRegistration,
CarColors,
@ -444,7 +444,7 @@ class CarFinanceForm(forms.ModelForm):
return cleaned_data
class Meta:
model = CarFinance
model = Car
fields = ["cost_price","marked_price"]

View File

@ -603,7 +603,6 @@ class AdditionalServices(models.Model, LocalizedNameMixin):
class Car(Base):
item_model = models.OneToOneField(
ItemModel,
models.DO_NOTHING,
@ -668,6 +667,32 @@ class Car(Base):
default=CarStockTypeChoices.NEW,
verbose_name=_("Stock Type"),
)
#
additional_services = models.ManyToManyField(
AdditionalServices, related_name="additionals", blank=True,null=True
)
cost_price = models.DecimalField(
max_digits=14, decimal_places=2, verbose_name=_("Cost Price"),default=Decimal("0.00")
)
selling_price = models.DecimalField(
max_digits=14,
decimal_places=2,
verbose_name=_("Selling Price"),
default=Decimal("0.00"),
)
marked_price = models.DecimalField(
max_digits=14,
decimal_places=2,
verbose_name=_("Marked Price"),
default=Decimal("0.00"),
)
discount_amount = models.DecimalField(
max_digits=14,
decimal_places=2,
verbose_name=_("Discount Amount"),
default=Decimal("0.00"),
)
#
remarks = models.TextField(blank=True, null=True, verbose_name=_("Remarks"))
mileage = models.IntegerField(blank=True, null=True, verbose_name=_("Mileage"))
receiving_date = models.DateTimeField(verbose_name=_("Receiving Date"))
@ -734,20 +759,16 @@ class Car(Base):
@property
def logo(self):
return getattr(self.id_car_make, "logo", "")
@property
def additional_services(self):
return self.finances.additional_services.all()
@property
def total_additional_services(self):
return sum([service.price_ for service in self.additional_services])
# @property
# def additional_services(self):
# return self.additional_services.all()
@property
def ready(self):
try:
return all(
[
self.colors,
self.finances,
self.finances.marked_price > 0,
self.marked_price > 0,
]
)
except Exception:
@ -838,10 +859,51 @@ class Car(Base):
car=self, exterior=exterior, interior=interior
)
self.save()
@property
def logo(self):
return self.id_car_make.logo.url if self.id_car_make.logo else None
#
@property
def get_additional_services_amount(self):
return sum([Decimal(x.price_) 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}
@property
def vat_amount(self):
vat = VatRate.objects.filter(dealer=self.dealer,is_active=True).first()
return Decimal(self.marked_price) * (vat.rate / 100)
@property
def total_vat(self):
return Decimal(self.marked_price) + Decimal(self.vat_amount)
# def get_discount_amount(self,estimate,user):
# try:
# instance = models.ExtraInfo.objects.get(
# dealer=self.dealer,
# content_object=estimate,
# related_object=user
# )
# if instance:
# return instance.data.get("discount",0)
# return 0
# except Exception:
# print("Error getting discount amount")
# return 0
# @property
# def total_discount(self):
# if self.discount_amount > 0:
# return self.marked_price - self.discount_amount
# return self.marked_price
# @property
# def total_vat(self):
# return round(self.total_discount + self.vat_amount + self.total_additionals, 2)
class CarTransfer(models.Model):
@ -880,7 +942,7 @@ class CarTransfer(models.Model):
@property
def total_price(self):
return self.quantity * self.car.finances.total_vat
return self.quantity * self.car.total_vat # TODO : check later
class Meta:
verbose_name = _("Car Transfer Log")
@ -924,105 +986,105 @@ class CarReservation(models.Model):
# Car Finance Model
class CarFinance(models.Model):
additional_services = models.ManyToManyField(
AdditionalServices, related_name="additional_finances", blank=True
)
car = models.OneToOneField(Car, on_delete=models.CASCADE, related_name="finances")
cost_price = models.DecimalField(
max_digits=14, decimal_places=2, verbose_name=_("Cost Price")
)
selling_price = models.DecimalField(
max_digits=14,
decimal_places=2,
verbose_name=_("Selling Price"),
default=Decimal("0.00"),
)
marked_price = models.DecimalField(
max_digits=14,
decimal_places=2,
verbose_name=_("Marked Price"),
default=Decimal("0.00"),
)
discount_amount = models.DecimalField(
max_digits=14,
decimal_places=2,
verbose_name=_("Discount Amount"),
default=Decimal("0.00"),
)
# is_sold = models.BooleanField(default=False)
# class CarFinance(models.Model):
# additional_services = models.ManyToManyField(
# AdditionalServices, related_name="additional_finances", blank=True
# )
# car = models.OneToOneField(Car, on_delete=models.CASCADE, related_name="finances")
# cost_price = models.DecimalField(
# max_digits=14, decimal_places=2, verbose_name=_("Cost Price")
# )
# selling_price = models.DecimalField(
# max_digits=14,
# decimal_places=2,
# verbose_name=_("Selling Price"),
# default=Decimal("0.00"),
# )
# marked_price = models.DecimalField(
# max_digits=14,
# decimal_places=2,
# verbose_name=_("Marked Price"),
# default=Decimal("0.00"),
# )
# discount_amount = models.DecimalField(
# max_digits=14,
# decimal_places=2,
# verbose_name=_("Discount Amount"),
# default=Decimal("0.00"),
# )
# # is_sold = models.BooleanField(default=False)
@property
def total(self):
return self.marked_price
# @property
# def total(self):
# return self.marked_price
@property
def total_additionals_no_vat(self):
return sum(x.price for x in self.additional_services.all())
# @property
# def total_additionals_no_vat(self):
# return sum(x.price for x in self.additional_services.all())
@property
def total_additionals(self):
return sum(x.price_ for x in self.additional_services.all())
# @property
# def total_additionals(self):
# return sum(x.price_ for x in self.additional_services.all())
@property
def total_discount(self):
if self.discount_amount > 0:
return self.marked_price - self.discount_amount
return self.marked_price
# @property
# def total_discount(self):
# if self.discount_amount > 0:
# return self.marked_price - self.discount_amount
# return self.marked_price
@property
def total_vat(self):
return round(self.total_discount + self.vat_amount + self.total_additionals, 2)
# @property
# def total_vat(self):
# return round(self.total_discount + self.vat_amount + self.total_additionals, 2)
@property
def vat_amount(self):
vat = VatRate.objects.filter(dealer=self.car.dealer, is_active=True).first()
if vat:
return (self.total_discount * Decimal(vat.rate)).quantize(Decimal("0.01"))
return Decimal("0.00")
# @property
# def vat_amount(self):
# vat = VatRate.objects.filter(dealer=self.car.dealer, is_active=True).first()
# if vat:
# return (self.total_discount * Decimal(vat.rate)).quantize(Decimal("0.01"))
# return Decimal("0.00")
@property
def revenue(self):
return self.marked_price - self.cost_price
# @property
# def revenue(self):
# return self.marked_price - self.cost_price
def to_dict(self):
return {
"cost_price": str(self.cost_price),
"selling_price": str(self.selling_price),
"marked_price": str(self.marked_price),
"discount_amount": str(self.discount_amount),
"total": str(self.total),
"total_discount": str(self.total_discount),
"total_vat": str(self.total_vat),
"vat_amount": str(self.vat_amount),
}
# def to_dict(self):
# return {
# "cost_price": str(self.cost_price),
# "selling_price": str(self.selling_price),
# "marked_price": str(self.marked_price),
# "discount_amount": str(self.discount_amount),
# "total": str(self.total),
# "total_discount": str(self.total_discount),
# "total_vat": str(self.total_vat),
# "vat_amount": str(self.vat_amount),
# }
def __str__(self):
return f"Car: {self.car}, Marked Price: {self.marked_price}"
# def __str__(self):
# return f"Car: {self.car}, Marked Price: {self.marked_price}"
# def save(self, *args, **kwargs):
# self.full_clean()
# try:
# price_after_discount = self.selling_price - self.discount_amount
# self.profit_margin = price_after_discount - self.cost_price
# self.vat_amount = settings.VAT_RATE
# except InvalidOperation as e:
# raise ValidationError(_("Invalid decimal operation: %s") % str(e))
# super().save(*args, **kwargs)
# # def save(self, *args, **kwargs):
# # self.full_clean()
# # try:
# # price_after_discount = self.selling_price - self.discount_amount
# # self.profit_margin = price_after_discount - self.cost_price
# # self.vat_amount = settings.VAT_RATE
# # except InvalidOperation as e:
# # raise ValidationError(_("Invalid decimal operation: %s") % str(e))
# # super().save(*args, **kwargs)
class Meta:
verbose_name = _("Car Financial Details")
verbose_name_plural = _("Car Financial Details")
indexes = [
models.Index(fields=["car"], name="car_finance_car_idx"),
models.Index(fields=["cost_price"], name="car_finance_cost_price_idx"),
models.Index(
fields=["selling_price"], name="car_finance_selling_price_idx"
),
models.Index(fields=["marked_price"], name="car_finance_marked_price_idx"),
models.Index(fields=["discount_amount"], name="car_finance_discount_idx"),
]
# class Meta:
# verbose_name = _("Car Financial Details")
# verbose_name_plural = _("Car Financial Details")
# indexes = [
# models.Index(fields=["car"], name="car_finance_car_idx"),
# models.Index(fields=["cost_price"], name="car_finance_cost_price_idx"),
# models.Index(
# fields=["selling_price"], name="car_finance_selling_price_idx"
# ),
# models.Index(fields=["marked_price"], name="car_finance_marked_price_idx"),
# models.Index(fields=["discount_amount"], name="car_finance_discount_idx"),
# ]
class ExteriorColors(models.Model, LocalizedNameMixin):
@ -2875,7 +2937,7 @@ class SaleOrder(models.Model):
@property
def price(self):
return self.car.finances.marked_price
return self.car.marked_price
@property
def items(self):

View File

@ -270,6 +270,9 @@ def create_item_model(sender, instance, created, **kwargs):
)
instance.item_model = inventory
inventory.save()
else:
instance.item_model.default_amount = instance.marked_price
# inventory = entity.create_item_inventory(
# name=instance.vin,
# uom_model=uom,
@ -284,108 +287,108 @@ def create_item_model(sender, instance, created, **kwargs):
# # update price - CarFinance
@receiver(post_save, sender=models.CarFinance)
def update_item_model_cost(sender, instance, created, **kwargs):
"""
Signal handler for updating an inventory item's cost and additional information
when a CarFinance instance is saved. This function updates the corresponding
inventory item of the car dealer's entity associated with the car's VIN by
modifying its default amount and updating additional data fields.
# @receiver(post_save, sender=models.CarFinance)
# def update_item_model_cost(sender, instance, created, **kwargs):
# """
# Signal handler for updating an inventory item's cost and additional information
# when a CarFinance instance is saved. This function updates the corresponding
# inventory item of the car dealer's entity associated with the car's VIN by
# modifying its default amount and updating additional data fields.
:param sender: The model class that triggered the signal.
:param instance: The instance of the CarFinance that was saved.
:param created: A boolean indicating whether the model instance was newly created.
:param kwargs: Additional keyword arguments passed during the signal invocation.
:return: None
"""
# if created and not instance.is_sold:
# if created:
# entity = instance.car.dealer.entity
# coa = entity.get_default_coa()
# inventory_account = (
# entity.get_all_accounts()
# .filter(name=f"Inventory:{instance.car.id_car_make.name}")
# .first()
# )
# if not inventory_account:
# inventory_account = create_make_accounts(
# entity,
# coa,
# [instance.car.id_car_make],
# "Inventory",
# roles.ASSET_CA_INVENTORY,
# "debit",
# )
# :param sender: The model class that triggered the signal.
# :param instance: The instance of the CarFinance that was saved.
# :param created: A boolean indicating whether the model instance was newly created.
# :param kwargs: Additional keyword arguments passed during the signal invocation.
# :return: None
# """
# # if created and not instance.is_sold:
# # if created:
# # entity = instance.car.dealer.entity
# # coa = entity.get_default_coa()
# # inventory_account = (
# # entity.get_all_accounts()
# # .filter(name=f"Inventory:{instance.car.id_car_make.name}")
# # .first()
# # )
# # if not inventory_account:
# # inventory_account = create_make_accounts(
# # entity,
# # coa,
# # [instance.car.id_car_make],
# # "Inventory",
# # roles.ASSET_CA_INVENTORY,
# # "debit",
# # )
# cogs = (
# entity.get_all_accounts()
# .filter(name=f"Cogs:{instance.car.id_car_make.name}")
# .first()
# )
# if not cogs:
# cogs = create_make_accounts(
# entity, coa, [instance.car.id_car_make], "Cogs", roles.COGS, "debit"
# )
# revenue = (
# entity.get_all_accounts()
# .filter(name=f"Revenue:{instance.car.id_car_make.name}")
# .first()
# )
# if not revenue:
# revenue = create_make_accounts(
# entity,
# coa,
# [instance.car.id_car_make],
# "Revenue",
# roles.ASSET_CA_RECEIVABLES,
# "credit",
# )
# # cogs = (
# # entity.get_all_accounts()
# # .filter(name=f"Cogs:{instance.car.id_car_make.name}")
# # .first()
# # )
# # if not cogs:
# # cogs = create_make_accounts(
# # entity, coa, [instance.car.id_car_make], "Cogs", roles.COGS, "debit"
# # )
# # revenue = (
# # entity.get_all_accounts()
# # .filter(name=f"Revenue:{instance.car.id_car_make.name}")
# # .first()
# # )
# # if not revenue:
# # revenue = create_make_accounts(
# # entity,
# # coa,
# # [instance.car.id_car_make],
# # "Revenue",
# # roles.ASSET_CA_RECEIVABLES,
# # "credit",
# # )
# cash_account = (
# # entity.get_all_accounts()
# # .filter(name="Cash", role=roles.ASSET_CA_CASH)
# # .first()
# entity.get_all_accounts()
# .filter(role=roles.ASSET_CA_CASH, role_default=True)
# .first()
# )
# # cash_account = (
# # # entity.get_all_accounts()
# # # .filter(name="Cash", role=roles.ASSET_CA_CASH)
# # # .first()
# # entity.get_all_accounts()
# # .filter(role=roles.ASSET_CA_CASH, role_default=True)
# # .first()
# # )
# ledger = LedgerModel.objects.create(
# entity=entity, name=f"Inventory Purchase - {instance.car}"
# )
# je = JournalEntryModel.objects.create(
# ledger=ledger,
# description=f"Acquired {instance.car} for inventory",
# )
# TransactionModel.objects.create(
# journal_entry=je,
# account=inventory_account,
# amount=Decimal(instance.cost_price),
# tx_type="debit",
# description="",
# )
# # ledger = LedgerModel.objects.create(
# # entity=entity, name=f"Inventory Purchase - {instance.car}"
# # )
# # je = JournalEntryModel.objects.create(
# # ledger=ledger,
# # description=f"Acquired {instance.car} for inventory",
# # )
# # TransactionModel.objects.create(
# # journal_entry=je,
# # account=inventory_account,
# # amount=Decimal(instance.cost_price),
# # tx_type="debit",
# # description="",
# # )
# TransactionModel.objects.create(
# journal_entry=je,
# account=cash_account,
# amount=Decimal(instance.cost_price),
# tx_type="credit",
# description="",
# )
# # TransactionModel.objects.create(
# # journal_entry=je,
# # account=cash_account,
# # amount=Decimal(instance.cost_price),
# # tx_type="credit",
# # description="",
# # )
instance.car.item_model.default_amount = instance.marked_price
# if not isinstance(instance.car.item_model.additional_info, dict):
# instance.car.item_model.additional_info = {}
# instance.car.item_model.additional_info.update({"car_finance": instance.to_dict()})
# instance.car.item_model.additional_info.update(
# {
# "additional_services": [
# service.to_dict() for service in instance.additional_services.all()
# ]
# }
# )
instance.car.item_model.save()
print(f"Inventory item updated with CarFinance data for Car: {instance.car}")
# instance.car.item_model.default_amount = instance.marked_price
# # if not isinstance(instance.car.item_model.additional_info, dict):
# # instance.car.item_model.additional_info = {}
# # instance.car.item_model.additional_info.update({"car_finance": instance.to_dict()})
# # instance.car.item_model.additional_info.update(
# # {
# # "additional_services": [
# # service.to_dict() for service in instance.additional_services.all()
# # ]
# # }
# # )
# instance.car.item_model.save()
# print(f"Inventory item updated with CarFinance data for Car: {instance.car}")
# @receiver(pre_save, sender=models.SaleQuotation)
@ -803,144 +806,144 @@ def create_dealer_settings(sender, instance, created, **kwargs):
# )
def save_journal(car_finance, ledger, vendor):
"""
Saves a journal entry pertaining to a car finance transaction for a specific ledger and vendor.
# def save_journal(car_finance, ledger, vendor):
# """
# Saves a journal entry pertaining to a car finance transaction for a specific ledger and vendor.
This function ensures that relevant accounts are updated to record financial transactions. It handles
debiting of the inventory account and crediting of the vendor account to maintain accurate bookkeeping.
Additionally, it creates vendor accounts dynamically if required and ties the created journal entry to
the ledger passed as a parameter. All transactions adhere to the ledger's entity-specific Chart of
Accounts (COA) configuration.
# This function ensures that relevant accounts are updated to record financial transactions. It handles
# debiting of the inventory account and crediting of the vendor account to maintain accurate bookkeeping.
# Additionally, it creates vendor accounts dynamically if required and ties the created journal entry to
# the ledger passed as a parameter. All transactions adhere to the ledger's entity-specific Chart of
# Accounts (COA) configuration.
:param car_finance: Instance of the car finance object containing details about the financed car
and its associated costs.
:type car_finance: `CarFinance` object
:param ledger: Ledger instance to which the journal entry is tied. This ledger must provide
entity-specific details, including its COA and related accounts.
:type ledger: `Ledger` object
:param vendor: Vendor instance representing the supplier or vendor related to the car finance
transaction. This vendor is used to derive or create the vendor account in COA.
:type vendor: `Vendor` object
# :param car_finance: Instance of the car finance object containing details about the financed car
# and its associated costs.
# :type car_finance: `CarFinance` object
# :param ledger: Ledger instance to which the journal entry is tied. This ledger must provide
# entity-specific details, including its COA and related accounts.
# :type ledger: `Ledger` object
# :param vendor: Vendor instance representing the supplier or vendor related to the car finance
# transaction. This vendor is used to derive or create the vendor account in COA.
# :type vendor: `Vendor` object
:return: None
"""
entity = ledger.entity
coa = entity.get_default_coa()
journal = JournalEntryModel.objects.create(
posted=False,
description=f"Finances of Car:{car_finance.car.vin} for Vendor:{car_finance.car.vendor.name}",
ledger=ledger,
locked=False,
origin="Payment",
)
ledger.additional_info["je_number"] = journal.je_number
ledger.save()
# :return: None
# """
# entity = ledger.entity
# coa = entity.get_default_coa()
# journal = JournalEntryModel.objects.create(
# posted=False,
# description=f"Finances of Car:{car_finance.car.vin} for Vendor:{car_finance.car.vendor.name}",
# ledger=ledger,
# locked=False,
# origin="Payment",
# )
# ledger.additional_info["je_number"] = journal.je_number
# ledger.save()
inventory_account = (
entity.get_default_coa_accounts().filter(role=roles.ASSET_CA_INVENTORY).first()
)
vendor_account = entity.get_default_coa_accounts().filter(name=vendor.name).first()
# inventory_account = (
# entity.get_default_coa_accounts().filter(role=roles.ASSET_CA_INVENTORY).first()
# )
# vendor_account = entity.get_default_coa_accounts().filter(name=vendor.name).first()
if not vendor_account:
last_account = (
entity.get_all_accounts()
.filter(role=roles.LIABILITY_CL_ACC_PAYABLE)
.order_by("-created")
.first()
)
if len(last_account.code) == 4:
code = f"{int(last_account.code)}{1:03d}"
elif len(last_account.code) > 4:
code = f"{int(last_account.code) + 1}"
# if not vendor_account:
# last_account = (
# entity.get_all_accounts()
# .filter(role=roles.LIABILITY_CL_ACC_PAYABLE)
# .order_by("-created")
# .first()
# )
# if len(last_account.code) == 4:
# code = f"{int(last_account.code)}{1:03d}"
# elif len(last_account.code) > 4:
# code = f"{int(last_account.code) + 1}"
vendor_account = entity.create_account(
name=vendor.name,
code=code,
role=roles.LIABILITY_CL_ACC_PAYABLE,
coa_model=coa,
balance_type="credit",
active=True,
)
additional_services_account = (
entity.get_default_coa_accounts()
.filter(name="Additional Services", role=roles.COGS)
.first()
)
# vendor_account = entity.create_account(
# name=vendor.name,
# code=code,
# role=roles.LIABILITY_CL_ACC_PAYABLE,
# coa_model=coa,
# balance_type="credit",
# active=True,
# )
# additional_services_account = (
# entity.get_default_coa_accounts()
# .filter(name="Additional Services", role=roles.COGS)
# .first()
# )
# Debit Inventory Account
TransactionModel.objects.create(
journal_entry=journal,
account=inventory_account,
amount=car_finance.cost_price,
tx_type="debit",
)
# # Debit Inventory Account
# TransactionModel.objects.create(
# journal_entry=journal,
# account=inventory_account,
# amount=car_finance.cost_price,
# tx_type="debit",
# )
# Credit Vendor Account
TransactionModel.objects.create(
journal_entry=journal,
account=vendor_account,
amount=car_finance.cost_price,
tx_type="credit",
)
# # Credit Vendor Account
# TransactionModel.objects.create(
# journal_entry=journal,
# account=vendor_account,
# amount=car_finance.cost_price,
# tx_type="credit",
# )
@receiver(post_save, sender=models.CarFinance)
def update_finance_cost(sender, instance, created, **kwargs):
"""
Signal to handle `post_save` functionality for the `CarFinance` model. This function
creates or updates financial records related to a car's finance details in the ledger
associated with the car's vendor and dealer. For newly created instances, a ledger is
created or retrieved, and the `save_journal` function is executed to log the financial
transactions.
# @receiver(post_save, sender=models.CarFinance)
# def update_finance_cost(sender, instance, created, **kwargs):
# """
# Signal to handle `post_save` functionality for the `CarFinance` model. This function
# creates or updates financial records related to a car's finance details in the ledger
# associated with the car's vendor and dealer. For newly created instances, a ledger is
# created or retrieved, and the `save_journal` function is executed to log the financial
# transactions.
This function also has commented-out logic to handle updates for already created
instances, including journal updates or adjustments to existing financial transactions.
# This function also has commented-out logic to handle updates for already created
# instances, including journal updates or adjustments to existing financial transactions.
:param sender: Model class that triggered the signal
:type sender: Model
:param instance: Instance of the `CarFinance` model passed to the signal
:type instance: CarFinance
:param created: Boolean value indicating if the instance was created (`True`) or updated (`False`)
:type created: bool
:param kwargs: Arbitrary keyword arguments passed to the signal
:type kwargs: dict
:return: None
"""
if created:
entity = instance.car.dealer.entity
vendor = instance.car.vendor
vin = instance.car.vin if instance.car.vin else ""
make = instance.car.id_car_make.name if instance.car.id_car_make else ""
model = instance.car.id_car_model.name if instance.car.id_car_model else ""
year = instance.car.year
vendor_name = vendor.name if vendor else ""
# :param sender: Model class that triggered the signal
# :type sender: Model
# :param instance: Instance of the `CarFinance` model passed to the signal
# :type instance: CarFinance
# :param created: Boolean value indicating if the instance was created (`True`) or updated (`False`)
# :type created: bool
# :param kwargs: Arbitrary keyword arguments passed to the signal
# :type kwargs: dict
# :return: None
# """
# if created:
# entity = instance.car.dealer.entity
# vendor = instance.car.vendor
# vin = instance.car.vin if instance.car.vin else ""
# make = instance.car.id_car_make.name if instance.car.id_car_make else ""
# model = instance.car.id_car_model.name if instance.car.id_car_model else ""
# year = instance.car.year
# vendor_name = vendor.name if vendor else ""
name = f"{vin}-{make}-{model}-{year}-{vendor_name}"
ledger, _ = LedgerModel.objects.get_or_create(name=name, entity=entity)
save_journal(instance, ledger, vendor)
# name = f"{vin}-{make}-{model}-{year}-{vendor_name}"
# ledger, _ = LedgerModel.objects.get_or_create(name=name, entity=entity)
# save_journal(instance, ledger, vendor)
# if not created:
# if ledger.additional_info.get("je_number"):
# journal = JournalEntryModel.objects.filter(je_number=ledger.additional_info.get("je_number")).first()
# journal.description = f"Finances of Car:{instance.car.vin} for Vendor:{instance.car.vendor.vendor_name}"
# journal.save()
# debit = journal.get_transaction_queryset().filter(tx_type='debit').first()
# credit = journal.get_transaction_queryset().filter(tx_type='credit').first()
# if debit and credit:
# if journal.is_locked():
# journal.mark_as_unlocked()
# journal.save()
# debit.amount = instance.cost_price
# credit.amount = instance.cost_price
# debit.save()
# credit.save()
# else:
# save_journal(instance,ledger,vendor,journal=journal)
# else:
# save_journal(instance,ledger,vendor)
# else:
# save_journal(instance,ledger,vendor)
# # if not created:
# # if ledger.additional_info.get("je_number"):
# # journal = JournalEntryModel.objects.filter(je_number=ledger.additional_info.get("je_number")).first()
# # journal.description = f"Finances of Car:{instance.car.vin} for Vendor:{instance.car.vendor.vendor_name}"
# # journal.save()
# # debit = journal.get_transaction_queryset().filter(tx_type='debit').first()
# # credit = journal.get_transaction_queryset().filter(tx_type='credit').first()
# # if debit and credit:
# # if journal.is_locked():
# # journal.mark_as_unlocked()
# # journal.save()
# # debit.amount = instance.cost_price
# # credit.amount = instance.cost_price
# # debit.save()
# # credit.save()
# # else:
# # save_journal(instance,ledger,vendor,journal=journal)
# # else:
# # save_journal(instance,ledger,vendor)
# # else:
# # save_journal(instance,ledger,vendor)
@receiver(post_save, sender=PurchaseOrderModel)

View File

@ -40,12 +40,15 @@ urlpatterns = [
views.assign_car_makes,
name="assign_car_makes",
),
path(
"dashboards/manager/",
views.ManagerDashboard.as_view(),
name="manager_dashboard",
),
#dashboards
path("dashboards/dealer/", views.DealerDashboard.as_view(),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"),
@ -345,13 +348,8 @@ urlpatterns = [
name="car_delete",
),
path(
"<slug:dealer_slug>/cars/<slug:slug>/finance/create/",
views.CarFinanceCreateView.as_view(),
name="car_finance_create",
),
path(
"<slug:dealer_slug>/cars/finance/<int:pk>/update/",
views.CarFinanceUpdateView.as_view(),
"<slug:dealer_slug>/cars/<slug:slug>/finance/update/",
views.CarFinanceUpdateView,
name="car_finance_update",
),
path(
@ -1091,6 +1089,10 @@ urlpatterns = [
views.MonthlyIncomeStatementView.as_view(),
name="entity-ic-date",
),
# Chart of Accounts...
path('<slug:dealer_slug>/chart-of-accounts/<slug:entity_slug>/list/',
views.ChartOfAccountModelListView.as_view(),
name='coa-list'),
# CASH FLOW STATEMENTS...
# Entities...
path(

View File

@ -779,7 +779,6 @@ class CarTransfer:
)
self.bill.additional_info = {}
self.bill.additional_info.update({"car_info": self.car.to_dict()})
self.bill.additional_info.update({"car_finance": self.car.finances.to_dict()})
self.bill.mark_as_review()
self.bill.mark_as_approved(
@ -791,14 +790,14 @@ class CarTransfer:
self.car.dealer = self.to_dealer
self.car.vendor = self.vendor
self.car.receiving_date = datetime.datetime.now()
self.car.finances.additional_services.clear()
self.car.additional_services.clear()
if hasattr(self.car, "custom_cards"):
self.car.custom_cards.delete()
self.car.finances.cost_price = self.transfer.total_price
self.car.finances.marked_price = 0
self.car.finances.discount_amount = 0
self.car.finances.save()
self.car.cost_price = self.transfer.total_price
self.car.marked_price = 0
self.car.discount_amount = 0
self.car.save()
self.car.location.owner = self.to_dealer
self.car.location.showroom = self.to_dealer
self.car.location.description = ""
@ -920,7 +919,7 @@ class CarTransfer:
# )
# bill.additional_info.update({"car_info": car.to_dict()})
# bill.additional_info.update({"car_finance": car.finances.to_dict()})
# bill.additional_info.update({"car_finance": car.to_dict()})
# bill.mark_as_review()
# bill.mark_as_approved(to_dealer.entity.slug, to_dealer.entity.admin)
@ -929,14 +928,14 @@ class CarTransfer:
# car.dealer = to_dealer
# car.vendor = vendor
# car.receiving_date = datetime.datetime.now()
# car.finances.additional_services.clear()
# car.additional_services.clear()
# if hasattr(car, "custom_cards"):
# car.custom_cards.delete()
# car.finances.cost_price = transfer.total_price
# car.finances.selling_price = 0
# car.finances.discount_amount = 0
# car.finances.save()
# car.cost_price = transfer.total_price
# car.selling_price = 0
# car.discount_amount = 0
# car.save()
# car.location.owner = to_dealer
# car.location.showroom = to_dealer
# car.location.description = ""
@ -980,6 +979,153 @@ def to_dict(obj):
return obj_dict
class CarFinanceCalculator1:
"""
Class responsible for calculating car financing details.
This class provides methods and attributes required for calculating various
aspects related to car financing, such as VAT calculation, pricing, discounts,
and additional services. It processes data about cars, computes totals (e.g.,
price, VAT, discounts), and aggregates the financial data for reporting or
further processing.
:ivar model: The data model passed to the calculator for retrieving transaction data.
:type model: Any
:ivar vat_rate: The current active VAT rate retrieved from the database.
:type vat_rate: Decimal
:ivar item_transactions: A collection of item transactions retrieved from the model.
:type item_transactions: list
:ivar additional_services: A list of additional services with details (e.g., name, price, taxable status).
:type additional_services: list
"""
VAT_OBJ_NAME = "vat_rate"
CAR_FINANCE_KEY = "car_finance"
CAR_INFO_KEY = "car_info"
ADDITIONAL_SERVICES_KEY = "additional_services"
def __init__(self, model):
if isinstance(model, InvoiceModel):
self.dealer = models.Dealer.objects.get(entity=model.ce_model.entity)
self.extra_info = models.ExtraInfo.objects.get(
dealer=self.dealer,
content_type=ContentType.objects.get_for_model(model.ce_model),
object_id=model.ce_model.pk,
)
elif isinstance(model, EstimateModel):
self.dealer = models.Dealer.objects.get(entity=model.entity)
self.extra_info = models.ExtraInfo.objects.get(
dealer=self.dealer,
content_type=ContentType.objects.get_for_model(model),
object_id=model.pk,
)
self.model = model
self.vat_rate = self._get_vat_rate()
self.item_transactions = self._get_item_transactions()
# self.additional_services = self._get_additional_services()
def _get_vat_rate(self):
vat = models.VatRate.objects.filter(dealer=self.dealer,is_active=True).first()
if not vat:
raise ObjectDoesNotExist("No active VAT rate found")
return vat.rate
def _get_additional_services(self):
return [x for item in self.item_transactions
for x in item.item_model.car.additional_services
]
def _get_item_transactions(self):
return self.model.get_itemtxs_data()[0].all()
def get_items(self):
return self._get_item_transactions()
@staticmethod
def _get_quantity(item):
return item.ce_quantity or item.quantity
# def _get_nested_value(self, item, *keys):
# current = item.item_model.additional_info
# for key in keys:
# current = current.get(key, {})
# return current
def _get_car_data(self, item):
quantity = self._get_quantity(item)
car = item.item_model.car
unit_price = Decimal(car.marked_price)
discount = self.extra_info.data.get("discount",0)
sell_price = unit_price - Decimal(discount)
return {
"item_number": item.item_model.item_number,
"vin": car.vin, #car_info.get("vin"),
"make": car.id_car_make ,#car_info.get("make"),
"model": car.id_car_model ,#car_info.get("model"),
"year": car.year ,# car_info.get("year"),
"logo": car.logo, # getattr(car.id_car_make, "logo", ""),
"trim": car.id_car_trim ,# car_info.get("trim"),
"mileage": car.mileage ,# car_info.get("mileage"),
"cost_price": car.cost_price,
"selling_price": car.selling_price,
"marked_price": car.marked_price,
"discount": car.discount_amount,
"quantity": quantity,
"unit_price": unit_price,
"sell_price": sell_price,
"total": unit_price,
"total_vat": sell_price * self.vat_rate,
"total_discount": discount,
"final_price": sell_price + (sell_price * self.vat_rate),
"total_additionals": car.total_additional_services,
"grand_total": sell_price + (sell_price * self.vat_rate) + car.total_additional_services,
"additional_services": car.additional_services,# self._get_nested_value(
#item, self.ADDITIONAL_SERVICES_KEY
#),
}
def calculate_totals(self):
total_price = sum(
Decimal(item.item_model.car.marked_price)
for item in self.item_transactions
)
total_additionals = sum(
Decimal(item.price_) for item in self._get_additional_services())
total_discount = self.extra_info.data.get("discount",0)
total_price_discounted = total_price
if total_discount:
total_price_discounted = total_price - Decimal(total_discount)
print(total_price_discounted)
total_vat_amount = total_price_discounted * self.vat_rate
return {
"total_price_discounted":total_price_discounted,
"total_price_before_discount":total_price,
"total_price": total_price_discounted,
"total_vat_amount": total_vat_amount,
"total_discount": Decimal(total_discount),
"total_additionals": total_additionals,
"grand_total":total_price_discounted + total_vat_amount + total_additionals,
}
def get_finance_data(self):
totals = self.calculate_totals()
return {
"cars": [self._get_car_data(item) for item in self.item_transactions],
"quantity": sum(
self._get_quantity(item) for item in self.item_transactions
),
"total_price": round(totals["total_price"], 2),
"total_price_discounted": round(totals["total_price_discounted"], 2),
"total_price_before_discount": round(totals["total_price_before_discount"], 2),
"total_vat": round(totals["total_vat_amount"] + totals["total_price"], 2),
"total_vat_amount": round(totals["total_vat_amount"], 2),
"total_discount": round(totals["total_discount"], 2),
"total_additionals": round(totals["total_additionals"], 2),
"grand_total": round(totals["grand_total"], 2),
"additionals": self._get_additional_services(),
"vat": round(self.vat_rate, 2),
}
class CarFinanceCalculator:
"""
Class responsible for calculating car financing details.
@ -1054,7 +1200,7 @@ class CarFinanceCalculator:
def _get_car_data(self, item):
quantity = self._get_quantity(item)
car = item.item_model.car
unit_price = Decimal(car.finances.marked_price)
unit_price = Decimal(car.marked_price)
discount = self.extra_info.data.get("discount",0)
sell_price = unit_price - Decimal(discount)
return {
@ -1066,10 +1212,10 @@ class CarFinanceCalculator:
"logo": car.logo, # getattr(car.id_car_make, "logo", ""),
"trim": car.id_car_trim ,# car_info.get("trim"),
"mileage": car.mileage ,# car_info.get("mileage"),
"cost_price": car.finances.cost_price,
"selling_price": car.finances.selling_price,
"marked_price": car.finances.marked_price,
"discount": car.finances.discount_amount,
"cost_price": car.cost_price,
"selling_price": car.selling_price,
"marked_price": car.marked_price,
"discount": car.discount_amount,
"quantity": quantity,
"unit_price": unit_price,
"sell_price": sell_price,
@ -1086,7 +1232,7 @@ class CarFinanceCalculator:
def calculate_totals(self):
total_price = sum(
Decimal(item.item_model.car.finances.marked_price)
Decimal(item.item_model.car.marked_price)
for item in self.item_transactions
)
total_additionals = sum(
@ -1112,7 +1258,7 @@ class CarFinanceCalculator:
def get_finance_data(self):
totals = self.calculate_totals()
return {
"cars": [self._get_car_data(item) for item in self.item_transactions],
"car": [self._get_car_data(item) for item in self.item_transactions],
"quantity": sum(
self._get_quantity(item) for item in self.item_transactions
),
@ -1127,6 +1273,53 @@ class CarFinanceCalculator:
"additionals": self._get_additional_services(),
"vat": round(self.vat_rate, 2),
}
def get_finance_data(estimate,dealer):
vat = models.VatRate.objects.filter(dealer=dealer,is_active=True).first()
item = estimate.get_itemtxs_data()[0].first()
car = item.item_model.car
if isinstance(estimate,InvoiceModel) and hasattr(estimate, "ce_model"):
estimate = estimate.ce_model
extra_info = models.ExtraInfo.objects.get(
dealer=dealer,
content_type=ContentType.objects.get_for_model(EstimateModel),
object_id=estimate.pk,
)
discount = extra_info.data.get("discount", 0)
discount = Decimal(discount)
vat_amount = car.marked_price * vat.rate
additional_services = car.get_additional_services()
return {
"car": car,
"discounted_price": (Decimal(car.marked_price) - discount) 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")
}
# totals = self.calculate_totals()
# return {
# "car": [self._get_car_data(item) for item in self.item_transactions],
# "quantity": sum(
# self._get_quantity(item) for item in self.item_transactions
# ),
# "total_price": round(totals["total_price"], 2),
# "total_price_discounted": round(totals["total_price_discounted"], 2),
# "total_price_before_discount": round(totals["total_price_before_discount"], 2),
# "total_vat": round(totals["total_vat_amount"] + totals["total_price"], 2),
# "total_vat_amount": round(totals["total_vat_amount"], 2),
# "total_discount": round(totals["total_discount"], 2),
# "total_additionals": round(totals["total_additionals"], 2),
# "grand_total": round(totals["grand_total"], 2),
# "additionals": self._get_additional_services(),
# "vat": round(self.vat_rate, 2),
# }
# class CarFinanceCalculator:
# """
# Class responsible for calculating car financing details.
@ -1360,9 +1553,9 @@ def _post_sale_and_cogs(invoice, dealer):
2) COGS / Inventory journal
"""
entity = invoice.ledger.entity
calc = CarFinanceCalculator(invoice)
data = calc.get_finance_data()
# calc = CarFinanceCalculator(invoice)
data = get_finance_data(invoice,dealer)
car = data.get("car")
cash_acc = entity.get_all_accounts().filter(role_default=True, role=roles.ASSET_CA_CASH).first()
ar_acc = entity.get_all_accounts().filter(role_default=True, role=roles.ASSET_CA_RECEIVABLES).first()
vat_acc = entity.get_all_accounts().filter(role_default=True, role=roles.LIABILITY_CL_TAXES_PAYABLE).first()
@ -1371,105 +1564,105 @@ def _post_sale_and_cogs(invoice, dealer):
cogs_acc = entity.get_all_accounts().filter(role_default=True, role=roles.COGS).first()
inv_acc = entity.get_all_accounts().filter(role_default=True, role=roles.ASSET_CA_INVENTORY).first()
for car_data in data['cars']:
car = invoice.get_itemtxs_data()[0].filter(
item_model__car__vin=car_data['vin']
).first().item_model.car
qty = Decimal(car_data['quantity'])
# for car_data in data['cars']:
# car = invoice.get_itemtxs_data()[0].filter(
# item_model__car__vin=car_data['vin']
# ).first().item_model.car
# qty = Decimal(car_data['quantity'])
net_car_price = Decimal(car_data['total']) - Decimal(car_data['total_discount'])
net_additionals_price = Decimal(data['total_additionals'])
vat_amount = Decimal(data['total_vat_amount']) * qty
grand_total = net_car_price + net_additionals_price + vat_amount
cost_total = Decimal(car_data['cost_price']) * qty
net_car_price = Decimal(data['discounted_price'])
net_additionals_price = Decimal(data['additional_services']['total'])
vat_amount = Decimal(data['vat_amount'])
grand_total = net_car_price + net_additionals_price + vat_amount
cost_total = Decimal(car.cost_price)
# ------------------------------------------------------------------
# 2A. Journal: Cash / A-R / VAT / Sales
# ------------------------------------------------------------------
# ------------------------------------------------------------------
# 2A. Journal: Cash / A-R / VAT / Sales
# ------------------------------------------------------------------
je_sale = JournalEntryModel.objects.create(
ledger=invoice.ledger,
description=f"Sale {car.vin}",
origin=f"Invoice {invoice.invoice_number}",
locked=False,
posted=False
)
# Dr Cash (what the customer paid)
je_sale = JournalEntryModel.objects.create(
ledger=invoice.ledger,
description=f"Sale {car.vin}",
origin=f"Invoice {invoice.invoice_number}",
locked=False,
posted=False
)
# Dr Cash (what the customer paid)
TransactionModel.objects.create(
journal_entry=je_sale,
account=cash_acc,
amount=grand_total,
tx_type='debit'
)
# # Cr A/R (clear the receivable)
# TransactionModel.objects.create(
# journal_entry=je_sale,
# account=ar_acc,
# amount=grand_total,
# tx_type='credit'
# )
# Cr VAT Payable
TransactionModel.objects.create(
journal_entry=je_sale,
account=vat_acc,
amount=vat_amount,
tx_type='credit'
)
# Cr Sales Car
TransactionModel.objects.create(
journal_entry=je_sale,
account=car_rev,
amount=net_car_price,
tx_type='credit'
)
if net_additionals_price > 0:
# Cr Sales Additional Services
TransactionModel.objects.create(
journal_entry=je_sale,
account=cash_acc,
amount=grand_total,
tx_type='debit'
)
# # Cr A/R (clear the receivable)
# TransactionModel.objects.create(
# journal_entry=je_sale,
# account=ar_acc,
# amount=grand_total,
# tx_type='credit'
# )
# Cr VAT Payable
TransactionModel.objects.create(
journal_entry=je_sale,
account=vat_acc,
amount=vat_amount,
account=add_rev,
amount=net_additionals_price,
tx_type='credit'
)
# Cr Sales Car
TransactionModel.objects.create(
journal_entry=je_sale,
account=car_rev,
amount=net_car_price,
tx_type='credit'
)
# ------------------------------------------------------------------
# 2B. Journal: COGS / Inventory reduction
# ------------------------------------------------------------------
je_cogs = JournalEntryModel.objects.create(
ledger=invoice.ledger,
description=f"COGS {car.vin}",
origin=f"Invoice {invoice.invoice_number}",
locked=False,
posted=False
)
if net_additionals_price > 0:
# Cr Sales Additional Services
TransactionModel.objects.create(
journal_entry=je_sale,
account=add_rev,
amount=net_additionals_price,
tx_type='credit'
)
# Dr COGS
TransactionModel.objects.create(
journal_entry=je_cogs,
account=cogs_acc,
amount=cost_total,
tx_type='debit'
)
# ------------------------------------------------------------------
# 2B. Journal: COGS / Inventory reduction
# ------------------------------------------------------------------
je_cogs = JournalEntryModel.objects.create(
ledger=invoice.ledger,
description=f"COGS {car.vin}",
origin=f"Invoice {invoice.invoice_number}",
locked=False,
posted=False
)
# Dr COGS
TransactionModel.objects.create(
journal_entry=je_cogs,
account=cogs_acc,
amount=cost_total,
tx_type='debit'
)
# Cr Inventory
TransactionModel.objects.create(
journal_entry=je_cogs,
account=inv_acc,
amount=cost_total,
tx_type='credit'
)
# ------------------------------------------------------------------
# 2C. Update car state flags inside the same transaction
# ------------------------------------------------------------------
entity.get_items_inventory().filter(name=car.vin).update(for_inventory=False)
# car.item_model.for_inventory = False
# car.item_model.save(update_fields=['for_inventory'])
car.finances.selling_price = grand_total
car.finances.is_sold = True
car.finances.save()
# Cr Inventory
TransactionModel.objects.create(
journal_entry=je_cogs,
account=inv_acc,
amount=cost_total,
tx_type='credit'
)
# ------------------------------------------------------------------
# 2C. Update car state flags inside the same transaction
# ------------------------------------------------------------------
entity.get_items_inventory().filter(name=car.vin).update(for_inventory=False)
# car.item_model.for_inventory = False
# car.item_model.save(update_fields=['for_inventory'])
car.selling_price = grand_total
# car.is_sold = True
car.save()
# def handle_account_process(invoice, amount, finance_data):
# """
# Processes accounting transactions based on an invoice, financial data,

View File

@ -105,6 +105,9 @@ from django_ledger.forms.bank_account import (
BankAccountCreateForm,
BankAccountUpdateForm,
)
from django_ledger.views.chart_of_accounts import (
ChartOfAccountModelListView as ChartOfAccountModelListViewBase
)
from django_ledger.views.bill import (
BillModelCreateView,
BillModelDetailView,
@ -126,6 +129,7 @@ from django_ledger.forms.invoice import (
from django_ledger.forms.item import (
InventoryItemCreateForm,
)
from django_ledger.forms.purchase_order import (
PurchaseOrderModelCreateForm,
BasePurchaseOrderModelUpdateForm,
@ -195,6 +199,7 @@ from .services import (
from .utils import (
CarFinanceCalculator,
get_car_finance_data,
get_finance_data,
get_item_transactions,
handle_payment,
reserve_car,
@ -386,9 +391,7 @@ class TestView(TemplateView):
template_name = "inventory/cars_list_api.html"
class ManagerDashboard(LoginRequiredMixin, TemplateView):
class DealerDashboard(LoginRequiredMixin, TemplateView):
"""
ManagerDashboard class is a view handling the dashboard for a manager.
@ -402,7 +405,7 @@ class ManagerDashboard(LoginRequiredMixin, TemplateView):
:type template_name: str
"""
template_name = "dashboards/manager.html"
template_name = "dashboards/dealer_dashbaord.html"
# def dispatch(self, request, *args, **kwargs):
# if not request.user.is_authenticated:
@ -484,6 +487,105 @@ class ManagerDashboard(LoginRequiredMixin, TemplateView):
return context
class ManagerDashboard(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/manager_dashboard.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 = 0 # models.CarFinance.objects.filter(car__dealer=dealer).aggregate( #TODO:update_finance
# 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
class SalesDashboard(LoginRequiredMixin, TemplateView):
"""
@ -503,7 +605,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)
@ -1459,124 +1706,21 @@ class CarDetailView(LoginRequiredMixin, PermissionRequiredMixin, DetailView):
permission_required = ["inventory.view_car"]
class CarFinanceCreateView(LoginRequiredMixin, PermissionRequiredMixin,SuccessMessageMixin, CreateView):
"""
Handles the creation of car finance records within the inventory system.
def CarFinanceUpdateView(request,dealer_slug,slug):
car = get_object_or_404(models.Car, slug=slug)
dealer = get_object_or_404(models.Dealer, slug=dealer_slug)
This view provides functionality to create car finance records tied to a
specific car in the inventory. It enforces that the user is logged in and
has the required permissions to add a car finance record. It also customizes
form behavior and context data to associate the finance record with the
corresponding car and populate additional services based on the user's type.
:ivar model: The database model associated with the view.
:type model: models.CarFinance
:ivar form_class: The form class used to create car finance records.
:type form_class: forms.CarFinanceForm
:ivar template_name: The template used to render the car finance creation page.
:type template_name: str
:ivar permission_required: The list of permissions required to access this view.
:type permission_required: list
"""
model = models.CarFinance
form_class = forms.CarFinanceForm
template_name = "inventory/car_finance_form.html"
permission_required = ["inventory.add_carfinance"]
def dispatch(self, request, *args, **kwargs):
self.car = get_object_or_404(models.Car, slug=self.kwargs["slug"])
return super().dispatch(request, *args, **kwargs)
def form_valid(self, form):
form.instance.car = self.car
messages.success(self.request, _("Car finance details saved successfully"))
return super().form_valid(form)
def get_success_url(self):
return reverse(
"car_detail",
kwargs={"dealer_slug": self.request.dealer.slug, "slug": self.car.slug},
)
def get_context_data(self, **kwargs):
context = super().get_context_data(**kwargs)
context["car"] = self.car
return context
# def get_form(self, form_class=None):
# form = super().get_form(form_class)
# dealer = get_user_type(self.request)
# form.fields[
# "additional_finances"
# ].queryset = models.AdditionalServices.objects.filter(dealer=dealer)
# return form
class CarFinanceUpdateView(
LoginRequiredMixin, PermissionRequiredMixin, SuccessMessageMixin, UpdateView
):
"""
Manages the update of car finance details.
This class-based view provides functionality for updating car finance
information in the system. It enforces login and specific permissions for
access, and it leverages mixins to provide success messages upon completing
updates. It is designed to interact with car finance data, update form
handling, and enforce related business logic.
:ivar model: The model associated with the view.
:type model: models.CarFinance
:ivar form_class: The form class used by the view.
:type form_class: forms.CarFinanceForm
:ivar template_name: The template used for rendering the form.
:type template_name: str
:ivar success_message: The success message displayed after an update.
:type success_message: str
:ivar permission_required: List of permissions required to access the view.
:type permission_required: list
"""
model = models.CarFinance
form_class = forms.CarFinanceForm
template_name = "inventory/car_finance_form.html"
success_message = _("Car finance details updated successfully")
permission_required = ["inventory.change_carfinance"]
def get_success_url(self):
return reverse(
"car_detail",
kwargs={
"dealer_slug": self.request.dealer.slug,
"slug": self.object.car.slug,
},
)
def get_form_kwargs(self):
kwargs = super().get_form_kwargs()
kwargs["instance"] = self.get_object()
return kwargs
def get_context_data(self , **kwargs):
context = super().get_context_data(**kwargs)
context["car"] = self.object.car
return context
# def get_initial(self):
# initial = super().get_initial()
# instance = self.get_object()
# dealer = get_user_type(self.request)
# selected_items = instance.additional_services.filter(dealer=dealer)
# initial["additional_finances"] = selected_items
# return initial
# def get_form(self, form_class=None):
# form = super().get_form(form_class)
# dealer = get_user_type(self.request)
# form.fields[
# "additional_finances"
# ].queryset = models.AdditionalServices.objects.filter(dealer=dealer)
# return form
if request.method == "POST":
form = forms.CarFinanceForm(request.POST, instance=car)
if form.is_valid():
form.save()
return redirect("car_detail", dealer_slug=dealer.slug, slug=car.slug)
else:
messages.error(request, "Please correct the errors below.")
else:
form = forms.CarFinanceForm(instance=car)
return render(request, "inventory/car_finance_form.html", {"car": car, "dealer": dealer, "form": form})
class CarUpdateView(
LoginRequiredMixin, PermissionRequiredMixin, SuccessMessageMixin, UpdateView
@ -3064,7 +3208,7 @@ def GroupPermissionView(request, dealer_slug, pk):
# Define ALL permissions you want to manage
MODEL_LIST = [
("inventory", "car"),
("inventory", "carfinance"),
# ("inventory", "carfinance"),
("inventory", "carlocation"),
("inventory", "customcard"),
("inventory", "cartransfer"),
@ -3955,7 +4099,7 @@ class BankAccountListView(LoginRequiredMixin, PermissionRequiredMixin, ListView)
def get_queryset(self):
query = self.request.GET.get("q")
dealer = get_user_type(self.request)
dealer = self.request.dealer
bank_accounts = BankAccountModel.objects.filter(entity_model=dealer.entity)
return apply_search_filters(bank_accounts, query)
@ -4659,8 +4803,7 @@ def create_estimate(request, dealer_slug, slug=None):
hash=item.get("item_id"),
# finances__is_sold=False,
colors__isnull=False,
finances__isnull=False,
finances__marked_price__gt=1,
marked_price__gt=1,
status="available",
).all()
@ -4670,9 +4813,9 @@ def create_estimate(request, dealer_slug, slug=None):
{
"item_number": i.item_model.item_number,
"quantity": 1,
"unit_cost": round(float(i.finances.marked_price)),
"unit_revenue": round(float(i.finances.marked_price)),
"total_amount": round(float(i.finances.total_vat)),
"unit_cost": round(float(i.marked_price)),
"unit_revenue": round(float(i.marked_price)),
"total_amount": round(float(i.total_vat)),# TODO : check later
}
)
@ -4690,10 +4833,10 @@ def create_estimate(request, dealer_slug, slug=None):
# instance = models.Car.objects.get(vin=item.name)
# estimate_itemtxs = {
# item.item_number: {
# "unit_cost": instance.finances.cost_price,
# "unit_revenue": instance.finances.marked_price,
# "unit_cost": instance.cost_price,
# "unit_revenue": instance.marked_price,
# "quantity": Decimal(quantities),
# "total_amount": instance.finances.total_vat * int(quantities),
# "total_amount": instance.total_vat * int(quantities),# TODO : check later
# }
# }
@ -4773,8 +4916,7 @@ def create_estimate(request, dealer_slug, slug=None):
models.Car.objects.filter(
dealer=dealer,
colors__isnull=False,
finances__isnull=False,
finances__marked_price__gt=1,
marked_price__gt=1,
status="available",
)
.annotate(
@ -4849,17 +4991,18 @@ class EstimateDetailView(LoginRequiredMixin, PermissionRequiredMixin, DetailView
dealer = get_object_or_404(models.Dealer, slug=self.kwargs["dealer_slug"])
estimate = kwargs.get("object")
if estimate.get_itemtxs_data():
calculator = CarFinanceCalculator(estimate)
finance_data = calculator.get_finance_data()
# 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
kwargs["invoice"] = invoice_obj
try:
car_finances = estimate.get_itemtxs_data()[0].first().item_model.car.finances
selected_items = car_finances.additional_services.filter(dealer=dealer)
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)
form.fields["additional_finances"].queryset = form.fields["additional_finances"].queryset.filter(dealer=dealer) # TODO : check later
form.initial["additional_finances"] = selected_items
kwargs["additionals_form"] = form
except Exception as e:
@ -4938,8 +5081,8 @@ def create_sale_order(request, dealer_slug, pk):
# else:
# form.fields["opportunity"].widget = HiddenInput()
calculator = CarFinanceCalculator(estimate)
finance_data = calculator.get_finance_data()
# calculator = CarFinanceCalculator(estimate)
finance_data = get_finance_data(estimate,dealer)
return render(
request,
"sales/estimates/sale_order_form.html",
@ -4957,15 +5100,16 @@ def update_estimate_discount(request, dealer_slug, pk):
content_type=ContentType.objects.get_for_model(EstimateModel),
object_id=estimate.pk,
)
# calculator = CarFinanceCalculator(estimate)
# finance_data = calculator.get_finance_data()
discount_amount = request.POST.get("discount_amount", 0)
calculator = CarFinanceCalculator(estimate)
finance_data = calculator.get_finance_data()
if Decimal(discount_amount) >= finance_data.get('cars')[0]['marked_price']:
finance_data = get_finance_data(estimate,dealer)
car = finance_data.get('car')
if Decimal(discount_amount) >= car.marked_price:
messages.error(request, _("Discount amount cannot be greater than marked price"))
return redirect("estimate_detail", dealer_slug=dealer_slug, pk=pk)
print(finance_data.get('cars')[0]['marked_price'] * Decimal('0.5'))
if Decimal(discount_amount) > finance_data.get('cars')[0]['marked_price'] * Decimal('0.5'):
if Decimal(discount_amount) > car.marked_price * Decimal('0.5'):
messages.warning(request, _("Discount amount is greater than 50% of the marked price, proceed with caution."))
else:
messages.success(request, _("Discount updated successfully"))
@ -4983,10 +5127,10 @@ def update_estimate_additionals(request, dealer_slug, pk):
if form.is_valid():
estimate = get_object_or_404(EstimateModel, pk=pk)
car = estimate.get_itemtxs_data()[0].first().item_model.car
car.finances.additional_services.set(
car.additional_services.set(
form.cleaned_data["additional_finances"]
)
car.finances.save()
car.save()
messages.success(request, "Additional Finances updated successfully")
return redirect("estimate_detail", dealer_slug=dealer_slug, pk=pk)
@ -5005,10 +5149,12 @@ class SaleOrderDetail(LoginRequiredMixin, PermissionRequiredMixin, DetailView):
def get_context_data(self, **kwargs):
saleorder = kwargs.get("object")
dealer = self.request.dealer
estimate = saleorder.estimate
if estimate.get_itemtxs_data():
calculator = CarFinanceCalculator(estimate)
finance_data = calculator.get_finance_data()
# calculator = CarFinanceCalculator(estimate)
# finance_data = calculator.get_finance_data()
finance_data = get_finance_data(estimate,dealer)
kwargs["data"] = finance_data
return super().get_context_data(**kwargs)
@ -5106,9 +5252,10 @@ class EstimatePreviewView(LoginRequiredMixin, PermissionRequiredMixin, DetailVie
def get_context_data(self, **kwargs):
estimate = kwargs.get("object")
if estimate.get_itemtxs_data():
# data = get_financial_values(estimate)
calculator = CarFinanceCalculator(estimate)
kwargs["data"] = calculator.get_finance_data()
# calculator = CarFinanceCalculator(estimate)
kwargs["data"] = get_finance_data(estimate,self.request.dealer)
return super().get_context_data(**kwargs)
@ -5288,8 +5435,9 @@ class InvoiceDetailView(LoginRequiredMixin, PermissionRequiredMixin, DetailView)
invoice = kwargs.get("object")
if invoice.get_itemtxs_data():
calculator = CarFinanceCalculator(invoice)
finance_data = calculator.get_finance_data()
# calculator = CarFinanceCalculator(invoice)
# finance_data = calculator.get_finance_data()
finance_data = get_finance_data(invoice,self.request.dealer)
kwargs["data"] = finance_data
kwargs["payments"] = JournalEntryModel.objects.filter(
ledger=invoice.ledger
@ -5515,16 +5663,17 @@ def invoice_create(request, dealer_slug, pk):
ledger.save()
invoice.save()
calculator = CarFinanceCalculator(estimate)
finance_data = calculator.get_finance_data()
# calculator = CarFinanceCalculator(estimate)
# finance_data = calculator.get_finance_data()
finance_data = get_finance_data(estimate,dealer)
car = finance_data.get("car")
invoice_itemtxs = {
i.get("item_number"): {
"unit_cost": i.get("grand_total"),
car.item_model.item_number: {
"unit_cost": finance_data.get("grand_total"),
"quantity": 1,
"total_amount": i.get("grand_total"),
"total_amount": finance_data.get("grand_total"),
}
for i in finance_data.get("cars")
}
invoice_itemtxs = invoice.migrate_itemtxs(
@ -5592,8 +5741,8 @@ class InvoicePreviewView(LoginRequiredMixin, PermissionRequiredMixin, DetailView
dealer = get_object_or_404(models.Dealer, slug=self.kwargs["dealer_slug"])
invoice = kwargs.get("object")
if invoice.get_itemtxs_data():
calculator = CarFinanceCalculator(invoice)
finance_data = calculator.get_finance_data()
# calculator = CarFinanceCalculator(invoice)
finance_data = get_finance_data(invoice,dealer)
kwargs["data"] = finance_data
kwargs["dealer"] = dealer
return super().get_context_data(**kwargs)
@ -6938,11 +7087,11 @@ class OpportunityCreateView(
if self.request.is_dealer:
form.fields["lead"].queryset = models.Lead.objects.filter(
dealer=dealer
)
)
elif self.request.is_staff:
form.fields["lead"].queryset = models.Lead.objects.filter(
dealer=dealer, staff=self.request.staff
)
)
return form
def get_success_url(self):
@ -9499,7 +9648,7 @@ def pricing_page(request, dealer_slug):
else:
messages.info(request,_("You already have an plan!!"))
return redirect('home',dealer_slug=dealer_slug)
@login_required
@ -10559,7 +10708,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,
@ -10687,13 +10836,13 @@ def upload_cars(request, dealer_slug, pk=None):
vendor=vendor,
receiving_date=receiving_date,
)
if po_item:
models.CarFinance.objects.create(
car=car,
cost_price=po_item.item.unit_cost,
marked_price=0,
selling_price=0,
)
# if po_item: #TODO:update
# models.CarFinance.objects.create(
# car=car,
# cost_price=po_item.item.unit_cost,
# marked_price=0,
# selling_price=0,
# )
car.add_colors(exterior=exterior, interior=interior)
cars_created += 1
logger.debug(
@ -10765,11 +10914,8 @@ def bulk_update_car_price(request):
else:
for car_pk in cars:
car = models.Car.objects.get(pk=car_pk)
if not hasattr(car, "finances"):
models.CarFinance.objects.create(car=car, cost_price=Decimal(price))
else:
car.finances.cost_price = Decimal(price)
car.finances.save()
car.cost_price = Decimal(price)
car.save()
messages.success(request, "Price updated successfully")
response = HttpResponse()
@ -10979,11 +11125,11 @@ def car_sale_report_csv_export(request,dealer_slug):
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 '',
car.finances.cost_price,
car.finances.marked_price,
car.finances.discount_amount,
car.finances.selling_price,
car.finances.vat_amount,
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
])
@ -11232,4 +11378,8 @@ def ticket_update(request, ticket_id):
return render(request, 'support/ticket_update.html', {
'ticket': ticket,
'form': form
})
})
class ChartOfAccountModelListView(ChartOfAccountModelListViewBase):
template_name = 'chart_of_accounts/coa_list.html'

View File

@ -0,0 +1,38 @@
{% extends 'base.html' %}
{% load i18n %}
{% load static %}
{% load icon from django_ledger %}
{% block content %}
<div class="card mb-4">
<div class="card-body">
<div class="row">
<div class="col-12">
<div class="d-flex justify-content-between align-items-center mb-4">
<h1 class="display-4 mb-0">Chart of Accounts</h1>
<a href="{{ entity_model.get_coa_create_url }}" class="btn btn-success btn-lg">
{% icon 'carbon:add-alt' 24 %} Add New
</a>
</div>
{% if not inactive %}
<a class="btn btn-warning mb-4" href="{{ entity_model.get_coa_list_inactive_url }}">
{% trans 'Show Inactive' %}
</a>
{% else %}
<a class="btn btn-warning mb-4" href="{{ entity_model.get_coa_list_url }}">
{% trans 'Show Active' %}
</a>
{% endif %}
</div>
</div>
<div class="row row-cols-1 row-cols-md-2 g-4">
{% for coa_model in coa_list %}
<div class="col">
{% include 'chart_of_accounts/includes/coa_card.html' with coa_model=coa_model %}
</div>
{% endfor %}
</div>
</div>
</div>
{% endblock %}

View File

@ -0,0 +1,116 @@
{% load django_ledger %}
{% load i18n %}
{% now "Y" as current_year %}
<div class="card shadow-sm border-0 mb-4">
<div class="card-header {% if coa_model.is_default %}bg-success text-white{% elif not coa_model.is_active %}bg-danger text-white{% endif %} py-3">
<div class="d-flex align-items-center">
<div class="me-3">
<i class="fas fa-building fa-2x"></i>
</div>
<div class="fw-bold fs-5">
{{ coa_model.name }}
{% if coa_model.is_default %}
<span class="badge bg-light text-dark ms-2">{% trans 'DEFAULT' %}</span>
{% endif %}
</div>
</div>
</div>
<div class="card-body">
<div class="row g-3">
<div class="col-md-6">
<div class="d-flex align-items-center mb-2">
<span class="fw-bold me-2">{% trans 'Entity Default' %}:</span>
{% if coa_model.is_default %}
<span class="badge bg-success"><i class="fas fa-check-circle"></i></span>
{% else %}
<span class="badge bg-danger"><i class="fas fa-times-circle"></i></span>
{% endif %}
</div>
<div class="d-flex align-items-center mb-2">
<span class="fw-bold me-2">{% trans 'Is Active' %}:</span>
{% if coa_model.is_active %}
<span class="badge bg-success"><i class="fas fa-check-circle"></i></span>
{% else %}
<span class="badge bg-danger"><i class="fas fa-times-circle"></i></span>
{% endif %}
</div>
<div class="mb-2">
<span class="fw-bold">CoA ID:</span>
<span class="text-muted ms-2">{{ coa_model.slug }}</span>
</div>
</div>
<div class="col-md-6">
<div class="mb-2">
<span class="fw-bold"><i class="fas fa-list-alt me-1"></i> {% trans 'Total Accounts' %}:</span>
<span class="ms-2">{{ coa_model.accountmodel_total__count }}</span>
</div>
<div class="mb-2">
<span class="fw-bold text-info"><i class="fas fa-check-circle me-1"></i> {% trans 'Active Accounts' %}:</span>
<span class="ms-2">{{ coa_model.accountmodel_active__count }}</span>
</div>
<div class="mb-2">
<span class="fw-bold text-danger"><i class="fas fa-lock me-1"></i> {% trans 'Locked Accounts' %}:</span>
<span class="ms-2">{{ coa_model.accountmodel_locked__count }}</span>
</div>
</div>
</div>
<hr class="my-3">
<div class="row">
<div class="col-md-6">
<small class="text-muted">
<i class="far fa-calendar-plus me-1"></i>
<span class="fw-bold">{% trans 'Created' %}:</span>
{{ coa_model.created|date }}
</small>
</div>
<div class="col-md-6">
<small class="text-muted">
<i class="far fa-clock me-1"></i>
<span class="fw-bold">{% trans 'Updated' %}:</span>
{{ coa_model.created|timesince }} {% trans 'ago' %}
</small>
</div>
</div>
</div>
<div class="card-footer bg-transparent border-top-0">
<div class="d-flex flex-wrap gap-2">
<a href="{{ coa_model.get_update_url }}" class="btn btn-sm btn-outline-warning fw-bold">
<i class="fas fa-edit me-1"></i> {% trans 'Update' %}
</a>
<a href="{{ coa_model.get_account_list_url }}" class="btn btn-sm btn-outline-success fw-bold">
<i class="fas fa-book me-1"></i> {% trans 'Accounts' %}
</a>
<a href="{{ coa_model.get_create_coa_account_url }}" class="btn btn-sm btn-outline-info fw-bold">
<i class="fas fa-plus-circle me-1"></i> {% trans 'Add Account' %}
</a>
{% if coa_model.can_mark_as_default %}
<a href="{{ coa_model.mark_as_default_url }}" class="btn btn-sm btn-outline-danger fw-bold">
<i class="fas fa-star me-1"></i> {% trans 'Mark as Default' %}
</a>
{% endif %}
{% if coa_model.can_deactivate %}
<a href="{{ coa_model.mark_as_inactive_url }}" class="btn btn-sm btn-outline-warning fw-bold">
<i class="fas fa-toggle-off me-1"></i> {% trans 'Mark as Inactive' %}
</a>
{% elif coa_model.can_activate %}
<a href="{{ coa_model.mark_as_active_url }}" class="btn btn-sm btn-outline-success fw-bold">
<i class="fas fa-toggle-on me-1"></i> {% trans 'Mark as Active' %}
</a>
{% endif %}
</div>
</div>
</div>

View File

@ -70,9 +70,9 @@
</h3>
{% endif %}
<div class="d-flex align-items-center mb-4">
{% if opportunity.car.finances %}
{% if opportunity.car.marked_price %}
<h5 class="mb-0 me-4">
{{ opportunity.car.finances.total }} <span class="fw-light"><span class="icon-saudi_riyal"></span></span>
{{ opportunity.car.total }} <span class="fw-light"><span class="icon-saudi_riyal"></span></span># TODO : check later
</h5>
{% endif %}
</div>

View File

@ -75,7 +75,7 @@
height: 12px;
width: 12px"></span>{{ opportunity.get_stage_display }}
</p>
<p class="ms-auto fs-9 text-body-emphasis fw-semibold mb-0 deals-revenue">{{ opportunity.car.finances.total }}</p>
<p class="ms-auto fs-9 text-body-emphasis fw-semibold mb-0 deals-revenue">{{ opportunity.car.total }}</p># TODO : check later
</div>
<div class="deals-company-agent d-flex flex-between-center">
<div class="d-flex align-items-center">
@ -107,7 +107,7 @@
<td class="d-none d-sm-block pe-sm-2">:</td>
<td class="py-1">
<p class="ps-6 ps-sm-0 fw-semibold fs-9 mb-0 mb-0 pb-3 pb-sm-0 text-body-emphasis">
{{ opportunity.car.finances.total }}
{{ opportunity.car.total }}# TODO : check later
</p>
</td>
</tr>

View File

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

View File

@ -0,0 +1,243 @@
{% 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 text-dark mb-0">Business Health 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">
-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">Total VAT Collected</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">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>
<div class="row g-4">
<div class="col-lg-8">
<div class="card h-100 shadow-sm border-0">
<div class="card-header bg-white border-bottom-0">
<h5 class="fw-bold mb-0 text-dark">Monthly Revenue & Profit</h5>
</div>
<div class="card-body" style="height: 400px;">
<canvas id="revenueProfitChart"></canvas>
</div>
</div>
</div>
<div class="col-lg-4">
<div class="card h-100 shadow-sm border-0">
<div class="card-header bg-white border-bottom-0">
<h5 class="fw-bold mb-0 text-dark">Monthly Cars Sold</h5>
</div>
<div class="card-body d-flex align-items-center justify-content-center" style="height: 400px;">
<canvas id="CarsSoldByMonthChart"></canvas>
</div>
</div>
</div>
</div>
</div>
<script src="https://cdn.jsdelivr.net/npm/chart.js"></script>
{% endblock content %}
{% block customJS %}
<script>
// Define a color palette that aligns with the Phoenix template
const primaryColor = '#7249b6'; // A vibrant purple
const secondaryColor = '#8193a6'; // A muted gray/blue
const successColor = '#00d074'; // A bright green
const dangerColor = '#e63757'; // A deep red
// Monthly Cars Sold (Bar Chart)
const ctx1 = document.getElementById('CarsSoldByMonthChart').getContext('2d');
new Chart(ctx1, {
type: 'bar',
data: {
labels: ['Jan', 'Feb', 'Mar', 'Apr', 'May', 'Jun', 'Jul', 'Aug', 'Sep', 'Oct', 'Nov', 'Dec'],
datasets: [{
label: 'Total Cars Sold',
data: [2, 3, 10, 4, 30, 12, 8, 9, 20, 12, 15, 35],
backgroundColor: primaryColor,
borderColor: primaryColor,
borderWidth: 1
}]
},
options: {
responsive: true,
maintainAspectRatio: false,
plugins: {
legend: { display: false }
},
scales: {
y: {
beginAtZero: true,
grid: { color: 'rgba(0, 0, 0, 0.05)' },
ticks: { color: secondaryColor }
},
x: {
grid: { display: false },
ticks: { color: secondaryColor }
}
}
}
});
// Monthly Revenue & Profit (Line Chart)
const ctx2 = document.getElementById('revenueProfitChart').getContext('2d');
new Chart(ctx2, {
type: 'line',
data: {
labels: ['Jan', 'Feb', 'Mar', 'Apr', 'May', 'Jun', 'Jul', 'Aug', 'Sep', 'Oct', 'Nov', 'Dec'],
datasets: [
{
label: 'Monthly Revenue',
data: [120000, 150000, 130000, 180000, 200000, 175000, 190000, 220000, 210000, 250000, 240000, 280000],
borderColor: primaryColor,
backgroundColor: 'rgba(114, 73, 182, 0.1)', // Using primaryColor with transparency
tension: 0.4,
fill: true,
pointBackgroundColor: primaryColor,
pointRadius: 5,
pointHoverRadius: 8
},
{
label: 'Monthly Net Profit',
data: [25000, 35000, 28000, 40000, 45000, 38000, 42000, 50000, 48000, 55000, 52000, 60000],
borderColor: successColor,
backgroundColor: 'rgba(0, 208, 116, 0.1)', // Using successColor with transparency
tension: 0.4,
fill: true,
pointBackgroundColor: successColor,
pointRadius: 5,
pointHoverRadius: 8
}
]
},
options: {
responsive: true,
maintainAspectRatio: false,
plugins: {
legend: {
display: true,
labels: { color: '#495057', boxWidth: 20 }
},
tooltip: {
backgroundColor: 'rgba(33, 37, 41, 0.9)',
titleColor: 'white',
bodyColor: 'white',
padding: 10,
callbacks: {
label: function(context) {
let label = context.dataset.label || '';
if (label) {
label += ': ';
}
if (context.parsed.y !== null) {
label += new Intl.NumberFormat('en-US', { style: 'currency', currency: 'USD' }).format(context.parsed.y);
}
return label;
}
}
}
},
scales: {
x: {
grid: { color: 'rgba(0, 0, 0, 0.05)' },
ticks: { color: secondaryColor },
border: { color: secondaryColor }
},
y: {
grid: { color: 'rgba(0, 0, 0, 0.05)' },
ticks: { color: secondaryColor },
border: { color: secondaryColor }
}
}
}
});
</script>
{% endblock %}

View File

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

View File

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

View File

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

View File

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

View File

@ -217,7 +217,7 @@
<li class="collapsed-nav-item-title d-none">{% trans 'Financials' %}</li>
{% if perms.django_ledger.view_accountmodel %}
<li class="nav-item">
<a class="nav-link" href="{% url 'account_list' request.dealer.slug %}">
<a class="nav-link" href="{% url 'coa-list' request.dealer.slug request.entity.slug %}">
<div class="d-flex align-items-center">
<span class="nav-link-icon"><span class="fas fa-book-open"></span></span><span class="nav-link-text">{% trans 'Chart of Accounts'|capfirst %}</span>
</div>
@ -295,7 +295,7 @@
</div>
{% endif %}
<!---->
{% if perms.django_ledger.can_view_reports %}
<div class="nav-item-wrapper">
<a class="nav-link dropdown-indicator label-1" href="#nv-reports" role="button" data-bs-toggle="collapse" aria-expanded="false" aria-controls="nv-reports">
@ -304,40 +304,40 @@
<span class="nav-link-icon"><i class="fa-solid fa-book-open"></i></span><span class="nav-link-text">{% trans 'Reports' %}</span>
</div>
</a>
<div class="parent-wrapper label-1">
<ul class="nav collapse parent" data-bs-parent="#navbarVerticalCollapse" id="nv-reports">
<li class="collapsed-nav-item-title d-none">{% trans 'Financials' %}</li>
{% if perms.django_ledger.view_accountmodel %}
<li class="nav-item">
<a class="nav-link" href="{% url 'entity-cf' request.dealer.slug request.dealer.entity.slug %}">
<div class="d-flex align-items-center">
<span class="nav-link-icon"><span class="fas fa-solid fa-sack-dollar"></span></span><span class="nav-link-text">{% trans 'Cash Flow'|capfirst %}</span>
</div>
</a>
</li>
<li class="nav-item">
<a class="nav-link" href="{% url 'entity-ic' request.dealer.slug request.dealer.entity.slug %}">
<div class="d-flex align-items-center">
<span class="nav-link-icon"><span class="fa-solid fa-sheet-plastic"></span></span><span class="nav-link-text">{% trans 'Income Statement'|capfirst %}</span>
</div>
</a>
</a>
</li>
<li class="nav-item">
<a class="nav-link" href="{% url 'entity-bs' request.dealer.slug request.dealer.entity.slug %}">
<div class="d-flex align-items-center">
<span class="nav-link-icon"><span class="fas fa-solid fa-scale-balanced"></span></span><span class="nav-link-text">{% trans 'Balance Sheet'|capfirst %}</span>
</div>
</a>
</a>
</li>
<li class="nav-item">
<!--car purchase report-->
<a class="nav-link" href="{% url 'po-report' request.dealer.slug %}">
@ -346,7 +346,7 @@
</div>
</a>
</li>
<li class="nav-item">
<!--car sale report-->
<a class="nav-link" href="{% url 'car-sale-report' request.dealer.slug %}">
@ -356,14 +356,14 @@
</a>
</li>
{% endif %}
</ul>
</div>
</div>
{% endif %}
<!---->
</li>
</ul>
{# --- Support & Contact Section (New) --- #}

View File

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

View File

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

View File

@ -20,11 +20,11 @@
<div class="alert alert-outline-warning d-flex align-items-center"
role="alert">
<i class="fa-solid fa-circle-info fs-6"></i>
{% if not car.finances and not car.colors %}
{% if not car.marked_price and not car.colors %}
<p class="mb-0 flex-1">
{{ _("This car information is not complete , please add colors and finances both before making it ready for sale .") }}
</p>
{% elif car.finances and not car.colors %}
{% elif car.marked_price and not car.colors %}
<p class="mb-0 flex-1">
{{ _("This car information is not complete , please add colors before making it ready for sale .") }}
</p>
@ -205,7 +205,7 @@
<tr>
<th>{% trans 'Location'|capfirst %}</th>
<td>
{% if car.finances and not car.get_transfer %}
{% if car.marked_price and not car.get_transfer %}
{% if car.location %}
{% if car.location.is_owner_showroom %}
{% trans 'Our Showroom' %}
@ -257,16 +257,16 @@
<div class="card-body">
<div class="table-responsive scrollbar mb-3">
<table class="table table-sm fs-9 mb-0 overflow-hidden">
{% if car.finances %}
{% if car.marked_price %}
<tr>
{% if perms.inventory.view_carfinance %}
<th>{% trans "Cost Price"|capfirst %}</th>
<td>{{ car.finances.cost_price|floatformat:2 }}</td>
<td>{{ car.cost_price|floatformat:2 }}</td>
{% endif %}
</tr>
<tr>
<th>{% trans "Marked Price"|capfirst %}</th>
<td>{{ car.finances.marked_price|floatformat:2 }}</td>
<td>{{ car.marked_price|floatformat:2 }}</td>
</tr>
{% comment %} <tr>
<th>{% trans "Selling Price"|capfirst %}</th>
@ -300,7 +300,7 @@
<tr>
<td colspan="2">
{% if not car.get_transfer %}
<a href="{% url 'car_finance_update' request.dealer.slug car.finances.pk %}"
<a href="{% url 'car_finance_update' request.dealer.slug car.slug %}"
class="btn btn-phoenix-warning btn-sm mb-3">{% trans "Edit" %}</a>
{% else %}
<span class="badge bg-danger">{% trans "Cannot Edit, Car in Transfer." %}</span>
@ -311,7 +311,7 @@
{% else %}
<p>{% trans "No finance details available." %}</p>
{% if perms.inventory.add_carfinance %}
<a href="{% url 'car_finance_create' request.dealer.slug car.slug %}"
<a href="{% url 'car_finance_update' request.dealer.slug car.slug %}"
class="btn btn-phoenix-success btn-sm mb-3">{% trans "Add" %}</a>
{% endif %}
{% endif %}

View File

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

View File

@ -98,10 +98,10 @@
<td>{{ transfer.car.vin }}</td>
<td>{{ transfer.car }}</td>
<td class="text-center">
{{ transfer.car.finances.selling_price }}&nbsp;<span class="icon-saudi_riyal"></span>
{{ transfer.car.selling_price }}&nbsp;<span class="icon-saudi_riyal"></span>
</td>
<td class="text-center">
{{ transfer.car.finances.vat_amount }}&nbsp;<span class="icon-saudi_riyal"></span>
{{ transfer.car.vat_amount }}&nbsp;<span class="icon-saudi_riyal"></span># TODO : check later
</td>
<td class="text-center">
{{ transfer.total_price }}&nbsp;<span class="icon-saudi_riyal"></span>

View File

@ -296,7 +296,7 @@
<tr>
<td>{{ transfer.car }}</td>
<td class="text-center">{{ transfer.quantity }}</td>
<td class="text-center">{{ transfer.car.finances.selling_price }}</td>
<td class="text-center">{{ transfer.car.selling_price }}</td>
<td class="text-center">{{ transfer.total_price }}</td>
</tr>
</tbody>
@ -345,12 +345,12 @@
});
document.getElementById('confirmAccept').addEventListener('click', function () {
// Handle the accept action here
// Handle the accept action here
$('#acceptModal').modal('hide');
});
document.getElementById('confirmReject').addEventListener('click', function () {
// Handle the reject action here
// Handle the reject action here
$('#rejectModal').modal('hide');
});
</script>

View File

@ -178,11 +178,11 @@
<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.finances.cost_price }} <span class="icon-saudi_riyal"></span></td>
<td class="fs-9">{{ car.finances.marked_price }} <span class="icon-saudi_riyal"></span></td>
<td class="fs-9">{{ car.finances.total_discount }} <span class="icon-saudi_riyal"></span></td>
<td class="fs-9">{{ car.finances.selling_price }} <span class="icon-saudi_riyal"></span></td>
<td class="fs-9">{{ car.finances.vat_amount }} <span class="icon-saudi_riyal"></span></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>
</tr>
{% endfor %}

View File

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

View File

@ -104,7 +104,7 @@
{% endif %}
</div>
</div>
<div class="d-flex align-items-center gap-2">
<div class="d-flex align-items-center gap-2" hx-boost="false">
{% if estimate.status == 'draft' %}
{% if perms.django_ledger.change_estimatemodel %}
<button id="mark_as_sent_estimate"
@ -112,7 +112,7 @@
onclick="setFormAction('review')"
data-bs-toggle="modal"
data-bs-target="#confirmModal">
<span class="d-none d-sm-inline-block"><i class="fa-solid fa-check-double"></i> {% trans 'Mark As Review' %}</span>
<i class="fa-solid fa-check-double me-1"></i><span class="d-none d-sm-inline-block"> {% trans 'Mark As Review' %}</span>
</button>
{% endif %}
{% elif estimate.status == 'in_review' %}
@ -122,45 +122,46 @@
class="btn btn-phoenix-secondary"
data-bs-toggle="modal"
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>
<i class="fa-solid fa-check-double me-1"></i> <span class="d-none d-sm-inline-block">{% trans 'Mark As Approved' %}</span>
</button>
{% endif %}
{% if estimate.can_approve and not request.is_manager %}
<button class="btn btn-phoenix-warning" disabled>
<i class="fas fa-hourglass-start me-2"></i><span class="text-warning">{% trans 'Waiting for Manager Approval' %}</span>
<i class="fas fa-hourglass-start me-1"></i><span class="text-warning d-none d-sm-inline-block ">{% trans 'Waiting for Manager Approval' %}</span>
</button>
{% endif %}
{% elif estimate.status == 'approved' %}
{% 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>
class="btn btn-phoenix-primary me-2"><span class="fa-regular fa-paper-plane me-1"></span><span class="d-none d-sm-inline-block">{% trans 'Send Quotation' %}</span></a>
{% endif %}
{% if estimate.sale_orders.first %}
<!--if sale order exist-->
{% if perms.django_ledger.add_invoicemodel %}
<a href="{% url 'invoice_create' request.dealer.slug estimate.pk %}"
class="btn btn-phoenix-primary"><span class="d-none d-sm-inline-block"><i class="fa-solid fa-receipt"></i> {% trans 'Create Invoice' %}</span></a>
class="btn btn-phoenix-primary"><i class="fa-solid fa-receipt me-1"></i><span class="d-none d-sm-inline-block me-1"> {% trans 'Create Invoice' %}</span></a>
{% endif %}
{% if perms.inventory.view_saleorder %}
<a href="{% url 'order_detail' request.dealer.slug estimate.sale_orders.first.pk %}"
class="btn btn-phoenix-primary"><span class="d-none d-sm-inline-block">{{ _("Preview Sale Order") }}</span></a>
class="btn btn-phoenix-primary"><i class="fas fa-shopping-cart me-1"></i><span class="d-none d-sm-inline-block">{{ _("Preview Sale Order") }}</span></a>
{% endif %}
{% else %}
{% if perms.inventory.add_saleorder %}
<a href="{% url 'create_sale_order' request.dealer.slug estimate.pk %}"
class="btn btn-phoenix-primary"><span class="d-none d-sm-inline-block"><i class="fa-solid fa-file-import"></i> {% trans 'Create Sale Order' %}</span></a>
class="btn btn-phoenix-primary"><i class="fa-solid fa-file-import me-1"></i><span class="d-none d-sm-inline-block"> {% trans 'Create Sale Order' %}</span></a>
{% endif %}
{% endif %}
{% elif estimate.status == 'completed' %}
{% if perms.inventory.view_saleorder %}
<a href="{% url 'order_detail' request.dealer.slug estimate.sale_orders.first.pk %}"
class="btn btn-phoenix-primary"><span class="d-none d-sm-inline-block">{{ _("Preview Sale Order") }}</span></a>
class="btn btn-phoenix-primary"><i class="fas fa-shopping-cart me-1"></i><span class="d-none d-sm-inline-block">{{ _("Preview Sale Order") }}</span></a>
{% endif %}
{% if perms.django_ledger.view_invoicemodel %}
<a href="{% url 'invoice_detail' request.dealer.slug request.entity.slug estimate.invoicemodel_set.first.pk %}"
class="btn btn-phoenix-primary btn-sm"
type="button"><i class="fa-solid fa-receipt"></i>
{{ _("View Invoice") }}</a>
type="button"><i class="fa-solid fa-receipt me-1"></i>
<span class="d-none d-sm-inline-block">{{ _("View Invoice") }}</span>
</a>
{% endif %}
{% endif %}
{% if estimate.can_cancel %}
@ -168,7 +169,7 @@
<button class="btn btn-phoenix-danger"
data-bs-toggle="modal"
data-bs-target="#CancelModal">
<i class="fa-solid fa-ban"></i> {% trans "Cancel" %}
<i class="fa-solid fa-ban me-1"></i> <span class="d-none d-sm-inline-block">{% trans "Cancel" %}</span>
</button>
{% endif %}
{% endif %}
@ -178,31 +179,31 @@
<div class="row">
<div class="col mb-2">
<h6>
<i class="fa-solid fa-hashtag"></i> {% trans 'Quotation Number' %}:
<i class="fa-solid fa-hashtag me-1"></i> {% trans 'Quotation Number' %}:
</h6>
<p class="fs-9 text-body-secondary fw-semibold mb-0">{{ estimate.estimate_number }}</p>
</div>
<div class="col mb-2">
<h6>
<i class="fa-solid fa-calendar-days"></i> {% trans 'Quotation Date' %}:
<i class="fa-solid fa-calendar-days me-1"></i> {% trans 'Quotation Date' %}:
</h6>
<p class="fs-9 text-body-secondary fw-semibold mb-0">{{ estimate.created }}</p>
</div>
<div class="col mb-2">
<h6>
<i class="fa-solid fa-user"></i> {% trans 'Customer' %}:
<i class="fa-solid fa-user me-1"></i> {% trans 'Customer' %}:
</h6>
<p class="fs-9 text-body-secondary fw-semibold mb-0">{{ estimate.customer.customer_name }}</p>
</div>
<div class="col mb-2">
<h6>
<i class="fa-solid fa-envelope"></i> {% trans 'Email' %}:
<i class="fa-solid fa-envelope me-1"></i> {% trans 'Email' %}:
</h6>
<p class="fs-9 text-body-secondary fw-semibold mb-0">{{ estimate.customer.email }}</p>
</div>
<div class="col">
<h6>
<i class="fa-solid fa-list"></i> {% trans "Quotation Status" %}:
<i class="fa-solid fa-list me-1"></i> {% trans "Quotation Status" %}:
</h6>
<div class="fs-9 text-body-secondary fw-semibold mb-0">
{% if estimate.status == 'draft' %}
@ -237,21 +238,18 @@
<th scope="col" style="min-width: 100px;">{% trans "Total" %}</th>
</tr>
</thead>
<tbody>
{% for item in data.cars %}
<tr>
<td class="align-middle">
<img src="{{item.logo}}" width="40" height="40" class="ps-2"></img>
<img src="{{data.car.logo}}" width="40" height="40" class="ps-2"></img>
</td>
<td class="align-middle">{{ item.make }}</td>
<td class="align-middle">{{ item.model }}</td>
<td class="align-middle">{{ item.year }}</td>
<td class="align-middle">{{ item.vin }}</td>
<td class="align-middle">{{ item.quantity }}</td>
<td class="align-middle ps-5">{{ item.unit_price }}</td>
<td class="align-middle text-body-tertiary fw-semibold">{{ item.total }}</td>
<td class="align-middle">{{ data.car.id_car_make }}</td>
<td class="align-middle">{{ data.car.id_car_model }}</td>
<td class="align-middle">{{ data.car.year }}</td>
<td class="align-middle">{{ data.car.vin }}</td>
<td class="align-middle">1</td>
<td class="align-middle ps-5">{{ data.car.marked_price }}</td>
<td class="align-middle text-body-tertiary fw-semibold">{{ data.car.marked_price }}</td>
</tr>
{% endfor %}
<tr class="bg-body-secondary total-sum">
<td class="align-middle ps-4 fw-semibold text-body-highlight" colspan="7">{% trans "Discount Amount" %}</td>
@ -264,7 +262,7 @@
<input type="number"
class="form-control"
name="discount_amount"
value="{{ data.total_discount }}"
value="{{ data.discount_amount }}"
step="0.01"
style="width: 1px">
<span class="input-group-text"><span class="icon-saudi_riyal"></span></span>
@ -272,20 +270,20 @@
</div>
</form>
{% else %}
{{ data.total_discount }} <span class="icon-saudi_riyal"></span>
{{ data.discount_amount }} <span class="icon-saudi_riyal"></span>
{% endif %}
</td>
</tr>
<tr class="bg-body-secondary total-sum">
<td class="align-middle ps-4 fw-semibold text-body-highlight" colspan="7">{% trans "Vat" %} ({{ data.vat }})</td>
<td class="align-middle ps-4 fw-semibold text-body-highlight" colspan="7">{% trans "Vat" %} ({{ data.vat_rate }})</td>
<td class="align-middle text-start fw-semibold">
<span id="">+ {{ data.total_vat_amount }}<span class="icon-saudi_riyal"></span></span>
<span id="">+ {{ data.vat_amount }}<span class="icon-saudi_riyal"></span></span>
</td>
</tr>
<tr class="bg-body-secondary total-sum">
<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.additionals %}
{% 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>
<br>
{% endfor %}

View File

@ -111,7 +111,7 @@
<td>{{ car.year }} - {{ car.id_car_make.name }} - {{ car.id_car_model.name }} - {{ car.id_car_trim.name }}</td>
<td>{{ car.colors.first.exterior.name }}</td>
<td>{{ car.colors.first.interior.name }}</td>
<td>{{ car.finances.selling_price }}</td>
<td>{{ car.selling_price }}</td>
<td>{{ car.get_specifications }}</td>
</tr>
{% endfor %}

View File

@ -330,34 +330,34 @@
</tr>
</thead>
<tbody>
{% for item in data.cars %}
<tr>
<td class="align-middle"></td>
<td class="align-middle">{{ item.make }}</td>
<td class="align-middle">{{ item.model }}</td>
<td class="align-middle">{{ item.year }}</td>
<td class="align-middle">{{ item.vin }}</td>
<td class="align-middle">{{ item.quantity|floatformat:-1 }}</td>
<td class="align-middle ps-5">{{ item.total }}</td>
<td class="align-middle text-body-tertiary fw-semibold">{{ item.total }}</td>
<td class="align-middle">
<img src="{{data.car.logo}}" width="40" height="40" class="ps-2"></img>
</td>
<td class="align-middle">{{ data.car.id_car_make }}</td>
<td class="align-middle">{{ data.car.id_car_model }}</td>
<td class="align-middle">{{ data.car.year }}</td>
<td class="align-middle">{{ data.car.vin }}</td>
<td class="align-middle">1</td>
<td class="align-middle ps-5">{{ data.car.marked_price }}</td>
<td class="align-middle text-body-tertiary fw-semibold">{{ data.car.marked_price }}</td>
</tr>
{% endfor %}
<tr class="bg-body-secondary total-sum">
<td class="align-middle ps-4 fw-semibold text-body-highlight" colspan="7">{% trans "Discount Amount" %}</td>
<td class="align-middle text-start fw-semibold">
<span id="grand-total">- {{ data.total_discount|floatformat }}<span class="icon-saudi_riyal"></span></span>
<span id="grand-total">- {{ data.discount_amount|floatformat }}<span class="icon-saudi_riyal"></span></span>
</td>
</tr>
<tr class="bg-body-secondary total-sum">
<td class="align-middle ps-4 fw-semibold text-body-highlight" colspan="7">{% trans "VAT" %} ({{ data.vat }}%)</td>
<td class="align-middle ps-4 fw-semibold text-body-highlight" colspan="7">{% trans "VAT" %} ({{ data.vat_rate }}%)</td>
<td class="align-middle text-start fw-semibold">
<span id="grand-total">+ {{ data.total_vat_amount|floatformat }}<span class="icon-saudi_riyal"></span></span>
<span id="grand-total">+ {{ data.vat_amount|floatformat }}<span class="icon-saudi_riyal"></span></span>
</td>
</tr>
<tr class="bg-body-secondary total-sum">
<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.additionals %}
{% 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>
<br>
{% endfor %}

View File

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

View File

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

View File

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