Compare commits
7 Commits
dcd30b0bb2
...
cf378f1a1e
| Author | SHA1 | Date | |
|---|---|---|---|
| cf378f1a1e | |||
| cb396f44e2 | |||
| 4a6ee8aad6 | |||
| 6e344926b9 | |||
| 15394da2f6 | |||
| 5d47a5b4cc | |||
| f8e3ae67a2 |
@ -146,6 +146,7 @@ class StaffForm(forms.ModelForm):
|
|||||||
)
|
)
|
||||||
|
|
||||||
class Meta:
|
class Meta:
|
||||||
|
|
||||||
model = Staff
|
model = Staff
|
||||||
fields = ["first_name","last_name", "arabic_name", "phone_number", "address", "logo", "group"]
|
fields = ["first_name","last_name", "arabic_name", "phone_number", "address", "logo", "group"]
|
||||||
|
|
||||||
|
|||||||
22
inventory/hooks.py
Normal file
22
inventory/hooks.py
Normal file
@ -0,0 +1,22 @@
|
|||||||
|
import logging
|
||||||
|
from inventory.models import Dealer
|
||||||
|
from .utils import get_accounts_data,create_account
|
||||||
|
|
||||||
|
logger = logging.getLogger(__name__)
|
||||||
|
|
||||||
|
def check_create_coa_accounts(task):
|
||||||
|
logger.info("Checking if all accounts are created")
|
||||||
|
instance_id = task.args[0]
|
||||||
|
instance = Dealer.objects.get(pk=instance_id)
|
||||||
|
entity = instance.entity
|
||||||
|
coa = entity.get_default_coa()
|
||||||
|
|
||||||
|
for account_data in get_accounts_data():
|
||||||
|
if entity.get_all_accounts().filter(code=account_data["code"]).exists():
|
||||||
|
logger.info(f"Default account already exists: {account_data['code']}")
|
||||||
|
continue
|
||||||
|
logger.info(f"Default account does not exist: {account_data['code']}")
|
||||||
|
create_account(entity, coa, account_data)
|
||||||
|
|
||||||
|
def print_results(task):
|
||||||
|
print(task.kwargs.get("dealer"))
|
||||||
9
inventory/management/commands/run1.py
Normal file
9
inventory/management/commands/run1.py
Normal file
@ -0,0 +1,9 @@
|
|||||||
|
from django.core.management.base import BaseCommand
|
||||||
|
from django_q.tasks import async_task, result
|
||||||
|
|
||||||
|
|
||||||
|
class Command(BaseCommand):
|
||||||
|
def handle(self, *args, **kwargs):
|
||||||
|
from inventory.models import Dealer
|
||||||
|
instance = Dealer.objects.first()
|
||||||
|
async_task(name="test_task_test",func="inventory.tasks.test_task",dealer=instance,hook="inventory.hooks.print_results")
|
||||||
@ -2,6 +2,7 @@ import uuid
|
|||||||
from datetime import datetime
|
from datetime import datetime
|
||||||
from django.conf import settings
|
from django.conf import settings
|
||||||
from django.contrib.auth.models import Permission
|
from django.contrib.auth.models import Permission
|
||||||
|
from inventory.validators import SaudiPhoneNumberValidator
|
||||||
from decimal import Decimal
|
from decimal import Decimal
|
||||||
from django.urls import reverse
|
from django.urls import reverse
|
||||||
from django.utils.text import slugify
|
from django.utils.text import slugify
|
||||||
@ -1305,7 +1306,7 @@ class Dealer(models.Model, LocalizedNameMixin):
|
|||||||
)
|
)
|
||||||
arabic_name = models.CharField(max_length=255, verbose_name=_("Arabic Name"))
|
arabic_name = models.CharField(max_length=255, verbose_name=_("Arabic Name"))
|
||||||
name = models.CharField(max_length=255, verbose_name=_("English Name"))
|
name = models.CharField(max_length=255, verbose_name=_("English Name"))
|
||||||
phone_number = PhoneNumberField(region="SA", verbose_name=_("Phone Number"))
|
phone_number = models.CharField(max_length=255, verbose_name=_("Phone Number"),validators=[SaudiPhoneNumberValidator])
|
||||||
address = models.CharField(
|
address = models.CharField(
|
||||||
max_length=200, blank=True, null=True, verbose_name=_("Address")
|
max_length=200, blank=True, null=True, verbose_name=_("Address")
|
||||||
)
|
)
|
||||||
@ -1431,7 +1432,7 @@ class Staff(models.Model):
|
|||||||
last_name = models.CharField(max_length=255, verbose_name=_("Last Name"))
|
last_name = models.CharField(max_length=255, verbose_name=_("Last Name"))
|
||||||
|
|
||||||
arabic_name = models.CharField(max_length=255, verbose_name=_("Arabic Name"))
|
arabic_name = models.CharField(max_length=255, verbose_name=_("Arabic Name"))
|
||||||
phone_number = PhoneNumberField(region="SA", verbose_name=_("Phone Number"))
|
phone_number = models.CharField(max_length=255, verbose_name=_("Phone Number"),validators=[SaudiPhoneNumberValidator])
|
||||||
staff_type = models.CharField(
|
staff_type = models.CharField(
|
||||||
choices=StaffTypes.choices, max_length=255, verbose_name=_("Staff Type")
|
choices=StaffTypes.choices, max_length=255, verbose_name=_("Staff Type")
|
||||||
)
|
)
|
||||||
@ -1825,7 +1826,7 @@ class Organization(models.Model, LocalizedNameMixin):
|
|||||||
)
|
)
|
||||||
vrn = models.CharField(max_length=15, verbose_name=_("VAT Registration Number"))
|
vrn = models.CharField(max_length=15, verbose_name=_("VAT Registration Number"))
|
||||||
email = models.EmailField(verbose_name=_("Email"))
|
email = models.EmailField(verbose_name=_("Email"))
|
||||||
phone_number = PhoneNumberField(region="SA", verbose_name=_("Phone Number"))
|
phone_number = models.CharField(max_length=255, verbose_name=_("Phone Number"),validators=[SaudiPhoneNumberValidator])
|
||||||
address = models.CharField(
|
address = models.CharField(
|
||||||
max_length=200, blank=True, null=True, verbose_name=_("Address")
|
max_length=200, blank=True, null=True, verbose_name=_("Address")
|
||||||
)
|
)
|
||||||
@ -1959,7 +1960,7 @@ class Representative(models.Model, LocalizedNameMixin):
|
|||||||
id_number = models.CharField(
|
id_number = models.CharField(
|
||||||
max_length=10, unique=True, verbose_name=_("ID Number")
|
max_length=10, unique=True, verbose_name=_("ID Number")
|
||||||
)
|
)
|
||||||
phone_number = PhoneNumberField(region="SA", verbose_name=_("Phone Number"))
|
phone_number = models.CharField(max_length=255, verbose_name=_("Phone Number"),validators=[SaudiPhoneNumberValidator])
|
||||||
email = models.EmailField(max_length=255, verbose_name=_("Email Address"))
|
email = models.EmailField(max_length=255, verbose_name=_("Email Address"))
|
||||||
address = models.CharField(
|
address = models.CharField(
|
||||||
max_length=200, blank=True, null=True, verbose_name=_("Address")
|
max_length=200, blank=True, null=True, verbose_name=_("Address")
|
||||||
@ -1979,7 +1980,7 @@ class Lead(models.Model):
|
|||||||
first_name = models.CharField(max_length=50, verbose_name=_("First Name"))
|
first_name = models.CharField(max_length=50, verbose_name=_("First Name"))
|
||||||
last_name = models.CharField(max_length=50, verbose_name=_("Last Name"))
|
last_name = models.CharField(max_length=50, verbose_name=_("Last Name"))
|
||||||
email = models.EmailField(verbose_name=_("Email"))
|
email = models.EmailField(verbose_name=_("Email"))
|
||||||
phone_number = PhoneNumberField(region="SA", verbose_name=_("Phone Number"))
|
phone_number = models.CharField(max_length=255, verbose_name=_("Phone Number"),validators=[SaudiPhoneNumberValidator])
|
||||||
address = models.CharField(
|
address = models.CharField(
|
||||||
max_length=200, blank=True, null=True, verbose_name=_("Address")
|
max_length=200, blank=True, null=True, verbose_name=_("Address")
|
||||||
)
|
)
|
||||||
@ -2667,7 +2668,7 @@ class Vendor(models.Model, LocalizedNameMixin):
|
|||||||
arabic_name = models.CharField(max_length=255, verbose_name=_("Arabic Name"))
|
arabic_name = models.CharField(max_length=255, verbose_name=_("Arabic Name"))
|
||||||
name = models.CharField(max_length=255, verbose_name=_("English Name"))
|
name = models.CharField(max_length=255, verbose_name=_("English Name"))
|
||||||
contact_person = models.CharField(max_length=100, verbose_name=_("Contact Person"))
|
contact_person = models.CharField(max_length=100, verbose_name=_("Contact Person"))
|
||||||
phone_number = PhoneNumberField(region="SA", verbose_name=_("Phone Number"))
|
phone_number = models.CharField(max_length=255, verbose_name=_("Phone Number"),validators=[SaudiPhoneNumberValidator])
|
||||||
email = models.EmailField(max_length=255, verbose_name=_("Email Address"))
|
email = models.EmailField(max_length=255, verbose_name=_("Email Address"))
|
||||||
address = models.CharField(max_length=200, verbose_name=_("Address"))
|
address = models.CharField(max_length=200, verbose_name=_("Address"))
|
||||||
logo = models.ImageField(
|
logo = models.ImageField(
|
||||||
@ -3219,6 +3220,7 @@ class CustomGroup(models.Model):
|
|||||||
"activity",
|
"activity",
|
||||||
"payment",
|
"payment",
|
||||||
"vendor",
|
"vendor",
|
||||||
|
|
||||||
],
|
],
|
||||||
other_perms=[
|
other_perms=[
|
||||||
"view_car",
|
"view_car",
|
||||||
@ -3229,7 +3231,8 @@ class CustomGroup(models.Model):
|
|||||||
"view_saleorder",
|
"view_saleorder",
|
||||||
"view_leads",
|
"view_leads",
|
||||||
"view_opportunity",
|
"view_opportunity",
|
||||||
"view_customer",
|
'view_customer'
|
||||||
|
|
||||||
],
|
],
|
||||||
)
|
)
|
||||||
self.set_permissions(
|
self.set_permissions(
|
||||||
|
|||||||
@ -178,7 +178,8 @@ def create_ledger_entity(sender, instance, created, **kwargs):
|
|||||||
entity.create_uom(name=u[1], unit_abbr=u[0])
|
entity.create_uom(name=u[1], unit_abbr=u[0])
|
||||||
|
|
||||||
# Create COA accounts, background task
|
# Create COA accounts, background task
|
||||||
async_task(create_coa_accounts, instance)
|
async_task(name="create_coa_accounts",func=create_coa_accounts,dealer_pk=instance.pk,hook="inventory.hooks.check_create_coa_accounts")
|
||||||
|
# async_task('inventory.tasks.check_create_coa_accounts', instance, schedule_type='O', schedule_time=timedelta(seconds=20))
|
||||||
|
|
||||||
# create_settings(instance.pk)
|
# create_settings(instance.pk)
|
||||||
# create_accounts_for_make(instance.pk)
|
# create_accounts_for_make(instance.pk)
|
||||||
|
|||||||
1583
inventory/tasks.py
1583
inventory/tasks.py
File diff suppressed because it is too large
Load Diff
@ -1,284 +1,284 @@
|
|||||||
import json
|
# import json
|
||||||
from . import models as m
|
# from . import models as m
|
||||||
from django.urls import reverse
|
# from django.urls import reverse
|
||||||
from django.test import Client, TestCase
|
# from django.test import Client, TestCase
|
||||||
from django.contrib.auth import get_user_model
|
# from django.contrib.auth import get_user_model
|
||||||
from django_ledger.io.io_core import get_localdate
|
# from django_ledger.io.io_core import get_localdate
|
||||||
|
|
||||||
from django.core.exceptions import ObjectDoesNotExist
|
# from django.core.exceptions import ObjectDoesNotExist
|
||||||
from decimal import Decimal
|
# from decimal import Decimal
|
||||||
from unittest.mock import MagicMock
|
# from unittest.mock import MagicMock
|
||||||
from inventory.models import VatRate
|
# from inventory.models import VatRate
|
||||||
from inventory.utils import CarFinanceCalculator
|
# from inventory.utils import CarFinanceCalculator
|
||||||
|
|
||||||
|
|
||||||
User = get_user_model()
|
# User = get_user_model()
|
||||||
|
|
||||||
|
|
||||||
# Create your tests here.
|
# # Create your tests here.
|
||||||
class ModelTest(TestCase):
|
# class ModelTest(TestCase):
|
||||||
"""
|
# """
|
||||||
Test case class designed to test models in the application. The tests
|
# Test case class designed to test models in the application. The tests
|
||||||
verify model creation, related object creation, and appropriate
|
# verify model creation, related object creation, and appropriate
|
||||||
functionality for specific business logic, ensuring correctness and
|
# functionality for specific business logic, ensuring correctness and
|
||||||
reliability of the defined models.
|
# reliability of the defined models.
|
||||||
|
|
||||||
This class tests the following:
|
# This class tests the following:
|
||||||
- Dealer model, including associated user and entity creation.
|
# - Dealer model, including associated user and entity creation.
|
||||||
- Car model, ensuring product creation and validations.
|
# - Car model, ensuring product creation and validations.
|
||||||
- Testing of car finances, including finance totals and relationships.
|
# - Testing of car finances, including finance totals and relationships.
|
||||||
- Creation of additional services, ensuring the generation of corresponding
|
# - Creation of additional services, ensuring the generation of corresponding
|
||||||
item services.
|
# item services.
|
||||||
|
|
||||||
:ivar vat: Vat rate created for testing purposes.
|
# :ivar vat: Vat rate created for testing purposes.
|
||||||
:type vat: VatRate instance
|
# :type vat: VatRate instance
|
||||||
:ivar dealer: Dealer instance created for testing.
|
# :ivar dealer: Dealer instance created for testing.
|
||||||
:type dealer: Dealer instance
|
# :type dealer: Dealer instance
|
||||||
:ivar car_make: Car make created for testing purposes.
|
# :ivar car_make: Car make created for testing purposes.
|
||||||
:type car_make: CarMake instance
|
# :type car_make: CarMake instance
|
||||||
:ivar car_model: Car model created for testing purposes.
|
# :ivar car_model: Car model created for testing purposes.
|
||||||
:type car_model: CarModel instance
|
# :type car_model: CarModel instance
|
||||||
:ivar car_serie: Car series created for testing purposes.
|
# :ivar car_serie: Car series created for testing purposes.
|
||||||
:type car_serie: CarSerie instance
|
# :type car_serie: CarSerie instance
|
||||||
:ivar trim: Car trim created for testing purposes.
|
# :ivar trim: Car trim created for testing purposes.
|
||||||
:type trim: CarTrim instance
|
# :type trim: CarTrim instance
|
||||||
:ivar car: Car object created for testing.
|
# :ivar car: Car object created for testing.
|
||||||
:type car: Car instance
|
# :type car: Car instance
|
||||||
:ivar car_finances: Car finance object for the car under test.
|
# :ivar car_finances: Car finance object for the car under test.
|
||||||
:type car_finances: CarFinance instance
|
# :type car_finances: CarFinance instance
|
||||||
"""
|
# """
|
||||||
|
|
||||||
def setUp(self):
|
# def setUp(self):
|
||||||
email = "RkzgO@example.com"
|
# email = "RkzgO@example.com"
|
||||||
name = "John Doe"
|
# name = "John Doe"
|
||||||
password = "password"
|
# password = "password"
|
||||||
crn = "123456789"
|
# crn = "123456789"
|
||||||
vrn = "123456789"
|
# vrn = "123456789"
|
||||||
phone = "123456789"
|
# phone = "123456789"
|
||||||
address = "123 Main St"
|
# address = "123 Main St"
|
||||||
arabic_name = "الاسم بالعربية"
|
# arabic_name = "الاسم بالعربية"
|
||||||
|
|
||||||
self.vat = m.VatRate.objects.create(rate=0.15)
|
# self.vat = m.VatRate.objects.create(rate=0.15)
|
||||||
|
|
||||||
user = User.objects.create(username=email, email=email)
|
# user = User.objects.create(username=email, email=email)
|
||||||
user.set_password(password)
|
# user.set_password(password)
|
||||||
user.save()
|
# user.save()
|
||||||
|
|
||||||
self.dealer = m.Dealer.objects.create(
|
# self.dealer = m.Dealer.objects.create(
|
||||||
user=user,
|
# user=user,
|
||||||
name=name,
|
# name=name,
|
||||||
arabic_name=arabic_name,
|
# arabic_name=arabic_name,
|
||||||
crn=crn,
|
# crn=crn,
|
||||||
vrn=vrn,
|
# vrn=vrn,
|
||||||
phone_number=phone,
|
# phone_number=phone,
|
||||||
address=address,
|
# address=address,
|
||||||
)
|
# )
|
||||||
|
|
||||||
self.car_make = m.CarMake.objects.create(name="Make")
|
# self.car_make = m.CarMake.objects.create(name="Make")
|
||||||
self.car_model = m.CarModel.objects.create(
|
# self.car_model = m.CarModel.objects.create(
|
||||||
name="Model", id_car_make=self.car_make
|
# name="Model", id_car_make=self.car_make
|
||||||
)
|
# )
|
||||||
self.car_serie = m.CarSerie.objects.create(
|
# self.car_serie = m.CarSerie.objects.create(
|
||||||
name="Serie", id_car_model=self.car_model
|
# name="Serie", id_car_model=self.car_model
|
||||||
)
|
# )
|
||||||
self.trim = m.CarTrim.objects.create(name="Trim", id_car_serie=self.car_serie)
|
# self.trim = m.CarTrim.objects.create(name="Trim", id_car_serie=self.car_serie)
|
||||||
self.car = m.Car.objects.create(
|
# self.car = m.Car.objects.create(
|
||||||
vin="123456789",
|
# vin="123456789",
|
||||||
dealer=self.dealer,
|
# dealer=self.dealer,
|
||||||
id_car_make=self.car_make,
|
# id_car_make=self.car_make,
|
||||||
id_car_model=self.car_model,
|
# id_car_model=self.car_model,
|
||||||
id_car_serie=self.car_serie,
|
# id_car_serie=self.car_serie,
|
||||||
year=2020,
|
# year=2020,
|
||||||
id_car_trim=self.trim,
|
# id_car_trim=self.trim,
|
||||||
receiving_date=get_localdate(),
|
# receiving_date=get_localdate(),
|
||||||
)
|
# )
|
||||||
|
|
||||||
self.car_finances = m.CarFinance.objects.create(
|
# self.car_finances = m.CarFinance.objects.create(
|
||||||
car=self.car, selling_price=1000, cost_price=500, discount_amount=200
|
# car=self.car, selling_price=1000, cost_price=500, discount_amount=200
|
||||||
)
|
# )
|
||||||
|
|
||||||
def test_dealer_creation_creates_user_and_entity(self):
|
# def test_dealer_creation_creates_user_and_entity(self):
|
||||||
dealer = self.dealer
|
# dealer = self.dealer
|
||||||
|
|
||||||
self.assertEqual(User.objects.count(), 1)
|
# self.assertEqual(User.objects.count(), 1)
|
||||||
self.assertEqual(m.Dealer.objects.count(), 1)
|
# self.assertEqual(m.Dealer.objects.count(), 1)
|
||||||
self.assertEqual(User.objects.first().username, "RkzgO@example.com")
|
# self.assertEqual(User.objects.first().username, "RkzgO@example.com")
|
||||||
self.assertEqual(User.objects.first().email, "RkzgO@example.com")
|
# self.assertEqual(User.objects.first().email, "RkzgO@example.com")
|
||||||
self.assertTrue(User.objects.first().check_password("password"))
|
# self.assertTrue(User.objects.first().check_password("password"))
|
||||||
self.assertEqual(dealer.user, User.objects.first())
|
# self.assertEqual(dealer.user, User.objects.first())
|
||||||
self.assertEqual(dealer.name, "John Doe")
|
# self.assertEqual(dealer.name, "John Doe")
|
||||||
self.assertEqual(dealer.arabic_name, "الاسم بالعربية")
|
# self.assertEqual(dealer.arabic_name, "الاسم بالعربية")
|
||||||
self.assertEqual(dealer.crn, "123456789")
|
# self.assertEqual(dealer.crn, "123456789")
|
||||||
self.assertEqual(dealer.vrn, "123456789")
|
# self.assertEqual(dealer.vrn, "123456789")
|
||||||
self.assertEqual(dealer.phone_number, "123456789")
|
# self.assertEqual(dealer.phone_number, "123456789")
|
||||||
self.assertEqual(dealer.address, "123 Main St")
|
# self.assertEqual(dealer.address, "123 Main St")
|
||||||
|
|
||||||
self.assertIsNotNone(dealer.entity)
|
# self.assertIsNotNone(dealer.entity)
|
||||||
self.assertEqual(dealer.entity.name, dealer.name)
|
# self.assertEqual(dealer.entity.name, dealer.name)
|
||||||
|
|
||||||
self.assertEqual(dealer.entity.get_all_accounts().count(), 57)
|
# self.assertEqual(dealer.entity.get_all_accounts().count(), 57)
|
||||||
self.assertEqual(dealer.entity.get_uom_all().count(), 16)
|
# self.assertEqual(dealer.entity.get_uom_all().count(), 16)
|
||||||
|
|
||||||
def test_car_creation_creates_product(self):
|
# def test_car_creation_creates_product(self):
|
||||||
dealer = self.dealer
|
# dealer = self.dealer
|
||||||
|
|
||||||
self.assertEqual(m.Car.objects.count(), 1)
|
# self.assertEqual(m.Car.objects.count(), 1)
|
||||||
self.assertEqual(self.car.vin, "123456789")
|
# self.assertEqual(self.car.vin, "123456789")
|
||||||
self.assertEqual(self.car.dealer, dealer)
|
# self.assertEqual(self.car.dealer, dealer)
|
||||||
self.assertEqual(self.car.id_car_make, self.car_make)
|
# self.assertEqual(self.car.id_car_make, self.car_make)
|
||||||
self.assertEqual(self.car.id_car_model, self.car_model)
|
# self.assertEqual(self.car.id_car_model, self.car_model)
|
||||||
self.assertEqual(self.car.id_car_serie, self.car_serie)
|
# self.assertEqual(self.car.id_car_serie, self.car_serie)
|
||||||
self.assertEqual(self.car.year, 2020)
|
# self.assertEqual(self.car.year, 2020)
|
||||||
self.assertEqual(self.car.id_car_trim, self.trim)
|
# self.assertEqual(self.car.id_car_trim, self.trim)
|
||||||
|
|
||||||
product = dealer.entity.get_items_all().filter(name=self.car.vin).first()
|
# product = dealer.entity.get_items_all().filter(name=self.car.vin).first()
|
||||||
self.assertEqual(product.name, self.car.vin)
|
# self.assertEqual(product.name, self.car.vin)
|
||||||
|
|
||||||
def test_car_finances_creation(self):
|
# def test_car_finances_creation(self):
|
||||||
self.assertEqual(m.CarFinance.objects.count(), 1)
|
# self.assertEqual(m.CarFinance.objects.count(), 1)
|
||||||
self.assertEqual(self.car_finances.car, self.car)
|
# self.assertEqual(self.car_finances.car, self.car)
|
||||||
self.assertEqual(self.car_finances.selling_price, 1000)
|
# self.assertEqual(self.car_finances.selling_price, 1000)
|
||||||
self.assertEqual(self.car_finances.cost_price, 500)
|
# self.assertEqual(self.car_finances.cost_price, 500)
|
||||||
self.assertEqual(self.car_finances.discount_amount, 200)
|
# self.assertEqual(self.car_finances.discount_amount, 200)
|
||||||
|
|
||||||
def test_car_finance_total(self):
|
# def test_car_finance_total(self):
|
||||||
self.assertEqual(m.CarFinance.objects.count(), 1)
|
# self.assertEqual(m.CarFinance.objects.count(), 1)
|
||||||
self.assertEqual(self.car_finances.total, 1000)
|
# self.assertEqual(self.car_finances.total, 1000)
|
||||||
self.assertEqual(self.car_finances.total_discount, 800)
|
# self.assertEqual(self.car_finances.total_discount, 800)
|
||||||
self.assertEqual(self.car_finances.total_vat, 920)
|
# self.assertEqual(self.car_finances.total_vat, 920)
|
||||||
|
|
||||||
def test_car_additional_services_create_item_service(self):
|
# def test_car_additional_services_create_item_service(self):
|
||||||
m.AdditionalServices.objects.create(
|
# m.AdditionalServices.objects.create(
|
||||||
name="Service",
|
# name="Service",
|
||||||
price=100,
|
# price=100,
|
||||||
description="Description",
|
# description="Description",
|
||||||
dealer=self.dealer,
|
# dealer=self.dealer,
|
||||||
taxable=True,
|
# taxable=True,
|
||||||
uom=m.UnitOfMeasure.PIECE,
|
# uom=m.UnitOfMeasure.PIECE,
|
||||||
)
|
# )
|
||||||
|
|
||||||
self.assertEqual(
|
# self.assertEqual(
|
||||||
m.ItemModel.objects.filter(
|
# m.ItemModel.objects.filter(
|
||||||
name="Service",
|
# name="Service",
|
||||||
default_amount=100,
|
# default_amount=100,
|
||||||
is_product_or_service=True,
|
# is_product_or_service=True,
|
||||||
item_role="service",
|
# item_role="service",
|
||||||
).count(),
|
# ).count(),
|
||||||
1,
|
# 1,
|
||||||
)
|
# )
|
||||||
|
|
||||||
|
|
||||||
class AuthenticationTest(TestCase):
|
# class AuthenticationTest(TestCase):
|
||||||
"""
|
# """
|
||||||
Represents a set of test cases for user authentication and signup validation within
|
# Represents a set of test cases for user authentication and signup validation within
|
||||||
a web application. These tests ensure the correctness of authentication functionalities
|
# a web application. These tests ensure the correctness of authentication functionalities
|
||||||
such as login and signing up with appropriate JSON data handling.
|
# such as login and signing up with appropriate JSON data handling.
|
||||||
|
|
||||||
:ivar client: Django test client used to simulate HTTP requests within the test framework.
|
# :ivar client: Django test client used to simulate HTTP requests within the test framework.
|
||||||
:type client: Client
|
# :type client: Client
|
||||||
:ivar url: URL for account signup endpoint used in the test cases.
|
# :ivar url: URL for account signup endpoint used in the test cases.
|
||||||
:type url: str
|
# :type url: str
|
||||||
"""
|
# """
|
||||||
|
|
||||||
def setUp(self):
|
# def setUp(self):
|
||||||
self.client = Client()
|
# self.client = Client()
|
||||||
self.url = reverse("account_signup")
|
# self.url = reverse("account_signup")
|
||||||
|
|
||||||
def test_login(self):
|
# def test_login(self):
|
||||||
url = reverse("account_login")
|
# url = reverse("account_login")
|
||||||
response = self.client.post(
|
# response = self.client.post(
|
||||||
url, {"email": "RkzgO@example.com", "password": "password"}
|
# url, {"email": "RkzgO@example.com", "password": "password"}
|
||||||
)
|
# )
|
||||||
self.assertEqual(response.status_code, 200)
|
# self.assertEqual(response.status_code, 200)
|
||||||
|
|
||||||
def test_valid_data(self):
|
# def test_valid_data(self):
|
||||||
# Create valid JSON data
|
# # Create valid JSON data
|
||||||
data = {
|
# data = {
|
||||||
"wizardValidationForm1": {
|
# "wizardValidationForm1": {
|
||||||
"email": "test@example.com",
|
# "email": "test@example.com",
|
||||||
"password": "password123",
|
# "password": "password123",
|
||||||
"confirm_password": "password123",
|
# "confirm_password": "password123",
|
||||||
},
|
# },
|
||||||
"wizardValidationForm2": {
|
# "wizardValidationForm2": {
|
||||||
"name": "John Doe",
|
# "name": "John Doe",
|
||||||
"arabic_name": "جون دو",
|
# "arabic_name": "جون دو",
|
||||||
"phone_number": "1234567890",
|
# "phone_number": "1234567890",
|
||||||
},
|
# },
|
||||||
"wizardValidationForm3": {
|
# "wizardValidationForm3": {
|
||||||
"crn": "123456",
|
# "crn": "123456",
|
||||||
"vrn": "789012",
|
# "vrn": "789012",
|
||||||
"address": "123 Main St",
|
# "address": "123 Main St",
|
||||||
},
|
# },
|
||||||
}
|
# }
|
||||||
|
|
||||||
# Send a POST request with the JSON data
|
# # Send a POST request with the JSON data
|
||||||
response = self.client.post(
|
# response = self.client.post(
|
||||||
self.url, data=json.dumps(data), content_type="application/json"
|
# self.url, data=json.dumps(data), content_type="application/json"
|
||||||
)
|
# )
|
||||||
|
|
||||||
# Check the response
|
# # Check the response
|
||||||
self.assertEqual(response.status_code, 200)
|
# self.assertEqual(response.status_code, 200)
|
||||||
self.assertEqual(response.json(), {"message": "User created successfully."})
|
# self.assertEqual(response.json(), {"message": "User created successfully."})
|
||||||
|
|
||||||
def test_passwords_do_not_match(self):
|
# def test_passwords_do_not_match(self):
|
||||||
# Create JSON data with mismatched passwords
|
# # Create JSON data with mismatched passwords
|
||||||
data = {
|
# data = {
|
||||||
"wizardValidationForm1": {
|
# "wizardValidationForm1": {
|
||||||
"email": "test@example.com",
|
# "email": "test@example.com",
|
||||||
"password": "password123",
|
# "password": "password123",
|
||||||
"confirm_password": "differentpassword",
|
# "confirm_password": "differentpassword",
|
||||||
},
|
# },
|
||||||
"wizardValidationForm2": {
|
# "wizardValidationForm2": {
|
||||||
"name": "John Doe",
|
# "name": "John Doe",
|
||||||
"arabic_name": "جون دو",
|
# "arabic_name": "جون دو",
|
||||||
"phone_number": "1234567890",
|
# "phone_number": "1234567890",
|
||||||
},
|
# },
|
||||||
"wizardValidationForm3": {
|
# "wizardValidationForm3": {
|
||||||
"crn": "123456",
|
# "crn": "123456",
|
||||||
"vrn": "789012",
|
# "vrn": "789012",
|
||||||
"address": "123 Main St",
|
# "address": "123 Main St",
|
||||||
},
|
# },
|
||||||
}
|
# }
|
||||||
|
|
||||||
# Send a POST request with the JSON data
|
# # Send a POST request with the JSON data
|
||||||
response = self.client.post(
|
# response = self.client.post(
|
||||||
self.url, data=json.dumps(data), content_type="application/json"
|
# self.url, data=json.dumps(data), content_type="application/json"
|
||||||
)
|
# )
|
||||||
|
|
||||||
# Check the response
|
# # Check the response
|
||||||
self.assertEqual(response.status_code, 400)
|
# self.assertEqual(response.status_code, 400)
|
||||||
self.assertEqual(response.json(), {"error": "Passwords do not match."})
|
# self.assertEqual(response.json(), {"error": "Passwords do not match."})
|
||||||
|
|
||||||
def test_missing_required_fields(self):
|
# def test_missing_required_fields(self):
|
||||||
# Create JSON data with missing required fields
|
# # Create JSON data with missing required fields
|
||||||
data = {
|
# data = {
|
||||||
"wizardValidationForm1": {
|
# "wizardValidationForm1": {
|
||||||
"email": "test@example.com",
|
# "email": "test@example.com",
|
||||||
"password": "password123",
|
# "password": "password123",
|
||||||
# Missing "confirm_password"
|
# # Missing "confirm_password"
|
||||||
},
|
# },
|
||||||
"wizardValidationForm2": {
|
# "wizardValidationForm2": {
|
||||||
"name": "John Doe",
|
# "name": "John Doe",
|
||||||
"arabic_name": "جون دو",
|
# "arabic_name": "جون دو",
|
||||||
"phone_number": "1234567890",
|
# "phone_number": "1234567890",
|
||||||
},
|
# },
|
||||||
"wizardValidationForm3": {
|
# "wizardValidationForm3": {
|
||||||
"crn": "123456",
|
# "crn": "123456",
|
||||||
"vrn": "789012",
|
# "vrn": "789012",
|
||||||
"address": "123 Main St",
|
# "address": "123 Main St",
|
||||||
},
|
# },
|
||||||
}
|
# }
|
||||||
|
|
||||||
# Send a POST request with the JSON data
|
# # Send a POST request with the JSON data
|
||||||
response = self.client.post(
|
# response = self.client.post(
|
||||||
self.url, data=json.dumps(data), content_type="application/json"
|
# self.url, data=json.dumps(data), content_type="application/json"
|
||||||
)
|
# )
|
||||||
|
|
||||||
# Check the response
|
# # Check the response
|
||||||
self.assertEqual(response.status_code, 400)
|
# self.assertEqual(response.status_code, 400)
|
||||||
self.assertIn(
|
# self.assertIn(
|
||||||
"error", response.json()
|
# "error", response.json()
|
||||||
) # Assuming the view returns an error for missing fields
|
# ) # Assuming the view returns an error for missing fields
|
||||||
|
|
||||||
|
|
||||||
# class CarFinanceCalculatorTests(TestCase):
|
# class CarFinanceCalculatorTests(TestCase):
|
||||||
|
|||||||
@ -1,5 +1,6 @@
|
|||||||
import json
|
import json
|
||||||
import secrets
|
import secrets
|
||||||
|
import logging
|
||||||
import datetime
|
import datetime
|
||||||
import requests
|
import requests
|
||||||
from decimal import Decimal
|
from decimal import Decimal
|
||||||
@ -27,7 +28,7 @@ from django.contrib.contenttypes.models import ContentType
|
|||||||
from django_ledger.models.transactions import TransactionModel
|
from django_ledger.models.transactions import TransactionModel
|
||||||
from django_ledger.models.journal_entry import JournalEntryModel
|
from django_ledger.models.journal_entry import JournalEntryModel
|
||||||
from django.db import transaction
|
from django.db import transaction
|
||||||
import logging
|
from django_q.models import Schedule as DjangoQSchedule
|
||||||
from django_ledger.io import roles
|
from django_ledger.io import roles
|
||||||
|
|
||||||
logger = logging.getLogger(__name__)
|
logger = logging.getLogger(__name__)
|
||||||
@ -223,13 +224,21 @@ def reserve_car(car, request):
|
|||||||
:return: Redirection to the car's detail page.
|
:return: Redirection to the car's detail page.
|
||||||
"""
|
"""
|
||||||
try:
|
try:
|
||||||
reserved_until = timezone.now() + timezone.timedelta(hours=24)
|
# reserved_until = timezone.now() + timezone.timedelta(hours=24)
|
||||||
models.CarReservation.objects.create(
|
reserved_until = timezone.now() + timezone.timedelta(minutes=1)
|
||||||
|
reservation = models.CarReservation.objects.create(
|
||||||
car=car, reserved_by=request.user, reserved_until=reserved_until
|
car=car, reserved_by=request.user, reserved_until=reserved_until
|
||||||
)
|
)
|
||||||
car.status = models.CarStatusChoices.RESERVED
|
car.status = models.CarStatusChoices.RESERVED
|
||||||
car.save()
|
car.save()
|
||||||
# --- Logging for Success ---
|
# --- Logging for Success ---
|
||||||
|
DjangoQSchedule.objects.create(
|
||||||
|
name=f"remove_reservation_for_car_with_vin_{car.vin}",
|
||||||
|
func='inventory.tasks.remove_reservation_by_id',
|
||||||
|
args=reservation.pk,
|
||||||
|
schedule_type=DjangoQSchedule.ONCE,
|
||||||
|
next_run=reserved_until,
|
||||||
|
)
|
||||||
logger.info(
|
logger.info(
|
||||||
f"Car {car.pk} ('{car.id_car_make} {car.id_car_model}') reserved successfully "
|
f"Car {car.pk} ('{car.id_car_make} {car.id_car_model}') reserved successfully "
|
||||||
f"by user {request.user}. "
|
f"by user {request.user}. "
|
||||||
@ -1305,7 +1314,7 @@ def get_finance_data(estimate,dealer):
|
|||||||
"final_price": discounted_price+ vat_amount,
|
"final_price": discounted_price+ vat_amount,
|
||||||
"total_services_vat":total_services_vat,
|
"total_services_vat":total_services_vat,
|
||||||
"total_vat":total_vat,
|
"total_vat":total_vat,
|
||||||
"grand_total": discounted_price + vat_amount + additional_services.get("total")
|
"grand_total": discounted_price + total_vat + additional_services.get("total")
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
@ -1581,6 +1590,7 @@ def _post_sale_and_cogs(invoice, dealer):
|
|||||||
vat_amount = Decimal(data['vat_amount'])
|
vat_amount = Decimal(data['vat_amount'])
|
||||||
grand_total = net_car_price + net_additionals_price + vat_amount
|
grand_total = net_car_price + net_additionals_price + vat_amount
|
||||||
cost_total = Decimal(car.cost_price)
|
cost_total = Decimal(car.cost_price)
|
||||||
|
discount_amount =Decimal(data['discount_amount'])
|
||||||
|
|
||||||
# ------------------------------------------------------------------
|
# ------------------------------------------------------------------
|
||||||
# 2A. Journal: Cash / A-R / VAT / Sales
|
# 2A. Journal: Cash / A-R / VAT / Sales
|
||||||
@ -1666,6 +1676,7 @@ def _post_sale_and_cogs(invoice, dealer):
|
|||||||
entity.get_items_inventory().filter(name=car.vin).update(for_inventory=False)
|
entity.get_items_inventory().filter(name=car.vin).update(for_inventory=False)
|
||||||
# car.item_model.for_inventory = False
|
# car.item_model.for_inventory = False
|
||||||
# car.item_model.save(update_fields=['for_inventory'])
|
# car.item_model.save(update_fields=['for_inventory'])
|
||||||
|
car.discount_amount=discount_amount
|
||||||
car.selling_price = grand_total
|
car.selling_price = grand_total
|
||||||
# car.is_sold = True
|
# car.is_sold = True
|
||||||
car.save()
|
car.save()
|
||||||
@ -1913,3 +1924,416 @@ def handle_payment(request, order):
|
|||||||
|
|
||||||
# def get_user_quota(user):
|
# def get_user_quota(user):
|
||||||
# return user.dealer.quota
|
# return user.dealer.quota
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
def get_accounts_data():
|
||||||
|
return [
|
||||||
|
# Current Assets (must start with 1)
|
||||||
|
{
|
||||||
|
"code": "1010",
|
||||||
|
"name": "Cash on Hand",
|
||||||
|
"role": roles.ASSET_CA_CASH,
|
||||||
|
"balance_type": roles.DEBIT,
|
||||||
|
"locked": False,
|
||||||
|
"default": True, # Default for ASSET_CA_CASH
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"code": "1020",
|
||||||
|
"name": "Bank",
|
||||||
|
"role": roles.ASSET_CA_CASH,
|
||||||
|
"balance_type": roles.DEBIT,
|
||||||
|
"locked": False,
|
||||||
|
"default": False,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"code": "1030",
|
||||||
|
"name": "Accounts Receivable",
|
||||||
|
"role": roles.ASSET_CA_RECEIVABLES,
|
||||||
|
"balance_type": roles.DEBIT,
|
||||||
|
"locked": False,
|
||||||
|
"default": True, # Default for ASSET_CA_RECEIVABLES
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"code": "1040",
|
||||||
|
"name": "Inventory (Cars)",
|
||||||
|
"role": roles.ASSET_CA_INVENTORY,
|
||||||
|
"balance_type": roles.DEBIT,
|
||||||
|
"locked": False,
|
||||||
|
"default": True, # Default for ASSET_CA_INVENTORY
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"code": "1045",
|
||||||
|
"name": "Spare Parts Inventory",
|
||||||
|
"role": roles.ASSET_CA_INVENTORY,
|
||||||
|
"balance_type": roles.DEBIT,
|
||||||
|
"locked": False,
|
||||||
|
"default": False,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"code": "1050",
|
||||||
|
"name": "Employee Advances",
|
||||||
|
"role": roles.ASSET_CA_RECEIVABLES,
|
||||||
|
"balance_type": roles.DEBIT,
|
||||||
|
"locked": False,
|
||||||
|
"default": False,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"code": "1060",
|
||||||
|
"name": "Prepaid Expenses",
|
||||||
|
"role": roles.ASSET_CA_PREPAID,
|
||||||
|
"balance_type": roles.DEBIT,
|
||||||
|
"locked": False,
|
||||||
|
"default": True, # Default for ASSET_CA_PREPAID
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"code": "1070",
|
||||||
|
"name": "Notes Receivable",
|
||||||
|
"role": roles.ASSET_LTI_NOTES_RECEIVABLE,
|
||||||
|
"balance_type": roles.DEBIT,
|
||||||
|
"locked": False,
|
||||||
|
"default": True, # Default for ASSET_LTI_NOTES_RECEIVABLE
|
||||||
|
},
|
||||||
|
# Fixed Assets (must also start with 1)
|
||||||
|
{
|
||||||
|
"code": "1110",
|
||||||
|
"name": "Lands",
|
||||||
|
"role": roles.ASSET_LTI_LAND,
|
||||||
|
"balance_type": roles.DEBIT,
|
||||||
|
"locked": False,
|
||||||
|
"default": True, # Default for ASSET_LTI_LAND
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"code": "1111",
|
||||||
|
"name": "Buildings",
|
||||||
|
"role": roles.ASSET_PPE_BUILDINGS,
|
||||||
|
"balance_type": roles.DEBIT,
|
||||||
|
"locked": False,
|
||||||
|
"default": True, # Default for ASSET_PPE_BUILDINGS
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"code": "1112",
|
||||||
|
"name": "Company Vehicles",
|
||||||
|
"role": roles.ASSET_PPE_EQUIPMENT,
|
||||||
|
"balance_type": roles.DEBIT,
|
||||||
|
"locked": False,
|
||||||
|
"default": True, # Default for ASSET_PPE_EQUIPMENT
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"code": "1113",
|
||||||
|
"name": "Equipment & Tools",
|
||||||
|
"role": roles.ASSET_PPE_EQUIPMENT,
|
||||||
|
"balance_type": roles.DEBIT,
|
||||||
|
"locked": False,
|
||||||
|
"default": False,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"code": "1114",
|
||||||
|
"name": "Furniture & Fixtures",
|
||||||
|
"role": roles.ASSET_PPE_EQUIPMENT,
|
||||||
|
"balance_type": roles.DEBIT,
|
||||||
|
"locked": False,
|
||||||
|
"default": False,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"code": "1115",
|
||||||
|
"name": "Other Fixed Assets",
|
||||||
|
"role": roles.ASSET_PPE_EQUIPMENT,
|
||||||
|
"balance_type": roles.DEBIT,
|
||||||
|
"locked": False,
|
||||||
|
"default": False,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"code": "1120",
|
||||||
|
"name": "Long-term Investments",
|
||||||
|
"role": roles.ASSET_LTI_SECURITIES,
|
||||||
|
"balance_type": roles.DEBIT,
|
||||||
|
"locked": False,
|
||||||
|
"default": True, # Default for ASSET_LTI_SECURITIES
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"code": "1130",
|
||||||
|
"name": "Intangible Assets",
|
||||||
|
"role": roles.ASSET_INTANGIBLE_ASSETS,
|
||||||
|
"balance_type": roles.DEBIT,
|
||||||
|
"locked": False,
|
||||||
|
"default": True, # Default for ASSET_INTANGIBLE_ASSETS
|
||||||
|
},
|
||||||
|
# Current Liabilities (must start with 2)
|
||||||
|
{
|
||||||
|
"code": "2010",
|
||||||
|
"name": "Accounts Payable",
|
||||||
|
"role": roles.LIABILITY_CL_ACC_PAYABLE,
|
||||||
|
"balance_type": roles.CREDIT,
|
||||||
|
"locked": True,
|
||||||
|
"default": True, # Default for LIABILITY_CL_ACC_PAYABLE
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"code": "2020",
|
||||||
|
"name": "Notes Payable",
|
||||||
|
"role": roles.LIABILITY_CL_ST_NOTES_PAYABLE,
|
||||||
|
"balance_type": roles.CREDIT,
|
||||||
|
"locked": False,
|
||||||
|
"default": True, # Default for LIABILITY_CL_ST_NOTES_PAYABLE
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"code": "2030",
|
||||||
|
"name": "Short-term Loans",
|
||||||
|
"role": roles.LIABILITY_CL_ST_NOTES_PAYABLE,
|
||||||
|
"balance_type": roles.CREDIT,
|
||||||
|
"locked": False,
|
||||||
|
"default": False,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"code": "2040",
|
||||||
|
"name": "Employee Payables",
|
||||||
|
"role": roles.LIABILITY_CL_WAGES_PAYABLE,
|
||||||
|
"balance_type": roles.CREDIT,
|
||||||
|
"locked": False,
|
||||||
|
"default": True, # Default for LIABILITY_CL_WAGES_PAYABLE
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"code": "2050",
|
||||||
|
"name": "Accrued Expenses",
|
||||||
|
"role": roles.LIABILITY_CL_OTHER,
|
||||||
|
"balance_type": roles.CREDIT,
|
||||||
|
"locked": False,
|
||||||
|
"default": True, # Default for LIABILITY_CL_OTHER
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"code": "2060",
|
||||||
|
"name": "Accrued Taxes",
|
||||||
|
"role": roles.LIABILITY_CL_TAXES_PAYABLE,
|
||||||
|
"balance_type": roles.CREDIT,
|
||||||
|
"locked": False,
|
||||||
|
"default": False, # Default for LIABILITY_CL_TAXES_PAYABLE
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"code": "2070",
|
||||||
|
"name": "Provisions",
|
||||||
|
"role": roles.LIABILITY_CL_OTHER,
|
||||||
|
"balance_type": roles.CREDIT,
|
||||||
|
"locked": False,
|
||||||
|
"default": False,
|
||||||
|
},
|
||||||
|
# Long-term Liabilities (must also start with 2)
|
||||||
|
{
|
||||||
|
"code": "2103",
|
||||||
|
"name": "Deferred Revenue",
|
||||||
|
"role": roles.LIABILITY_CL_DEFERRED_REVENUE,
|
||||||
|
"balance_type": roles.CREDIT,
|
||||||
|
"locked": False,
|
||||||
|
"default": True, # Default for LIABILITY_CL_DEFERRED_REVENUE
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"code": "2200",
|
||||||
|
"name": "Tax Payable",
|
||||||
|
"role": roles.LIABILITY_CL_TAXES_PAYABLE,
|
||||||
|
"balance_type": roles.CREDIT,
|
||||||
|
"locked": False,
|
||||||
|
"default": True,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"code": "2210",
|
||||||
|
"name": "Long-term Bank Loans",
|
||||||
|
"role": roles.LIABILITY_LTL_NOTES_PAYABLE,
|
||||||
|
"balance_type": roles.CREDIT,
|
||||||
|
"locked": False,
|
||||||
|
"default": True, # Default for LIABILITY_LTL_NOTES_PAYABLE
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"code": "2220",
|
||||||
|
"name": "Lease Liabilities",
|
||||||
|
"role": roles.LIABILITY_LTL_NOTES_PAYABLE,
|
||||||
|
"balance_type": roles.CREDIT,
|
||||||
|
"locked": False,
|
||||||
|
"default": False,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"code": "2230",
|
||||||
|
"name": "Other Long-term Liabilities",
|
||||||
|
"role": roles.LIABILITY_LTL_NOTES_PAYABLE,
|
||||||
|
"balance_type": roles.CREDIT,
|
||||||
|
"locked": False,
|
||||||
|
"default": False,
|
||||||
|
},
|
||||||
|
# Equity (must start with 3)
|
||||||
|
{
|
||||||
|
"code": "3010",
|
||||||
|
"name": "Capital",
|
||||||
|
"role": roles.EQUITY_CAPITAL,
|
||||||
|
"balance_type": roles.CREDIT,
|
||||||
|
"locked": True,
|
||||||
|
"default": True, # Default for EQUITY_CAPITAL
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"code": "3020",
|
||||||
|
"name": "Statutory Reserve",
|
||||||
|
"role": roles.EQUITY_ADJUSTMENT,
|
||||||
|
"balance_type": roles.CREDIT,
|
||||||
|
"locked": False,
|
||||||
|
"default": True, # Default for EQUITY_ADJUSTMENT
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"code": "3030",
|
||||||
|
"name": "Retained Earnings",
|
||||||
|
"role": roles.EQUITY_ADJUSTMENT,
|
||||||
|
"balance_type": roles.CREDIT,
|
||||||
|
"locked": False,
|
||||||
|
"default": False,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"code": "3040",
|
||||||
|
"name": "Profit & Loss for the Period",
|
||||||
|
"role": roles.EQUITY_ADJUSTMENT,
|
||||||
|
"balance_type": roles.CREDIT,
|
||||||
|
"locked": False,
|
||||||
|
"default": False,
|
||||||
|
},
|
||||||
|
# Revenue (must start with 4)
|
||||||
|
{
|
||||||
|
"code": "4010",
|
||||||
|
"name": "Car Sales",
|
||||||
|
"role": roles.INCOME_OPERATIONAL,
|
||||||
|
"balance_type": roles.CREDIT,
|
||||||
|
"locked": True,
|
||||||
|
"default": True, # Default for INCOME_OPERATIONAL
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"code": "4020",
|
||||||
|
"name": "After-Sales Services",
|
||||||
|
"role": roles.INCOME_OPERATIONAL,
|
||||||
|
"balance_type": roles.CREDIT,
|
||||||
|
"locked": False,
|
||||||
|
"default": False,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"code": "4030",
|
||||||
|
"name": "Car Rental Income",
|
||||||
|
"role": roles.INCOME_PASSIVE,
|
||||||
|
"balance_type": roles.CREDIT,
|
||||||
|
"locked": False,
|
||||||
|
"default": True, # Default for INCOME_PASSIVE
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"code": "4040",
|
||||||
|
"name": "Other Income",
|
||||||
|
"role": roles.INCOME_OTHER,
|
||||||
|
"balance_type": roles.CREDIT,
|
||||||
|
"locked": False,
|
||||||
|
"default": True, # Default for INCOME_OTHER
|
||||||
|
},
|
||||||
|
# Expenses (must start with 5 for COGS, 6 for others)
|
||||||
|
{
|
||||||
|
"code": "5010",
|
||||||
|
"name": "Cost of Goods Sold",
|
||||||
|
"role": roles.COGS,
|
||||||
|
"balance_type": roles.DEBIT,
|
||||||
|
"locked": True,
|
||||||
|
"default": True, # Default for COGS
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"code": "5015",
|
||||||
|
"name": "Spare Parts Cost Consumed",
|
||||||
|
"role": roles.COGS,
|
||||||
|
"balance_type": roles.DEBIT,
|
||||||
|
"locked": False,
|
||||||
|
"default": False,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"code": "6010",
|
||||||
|
"name": "Salaries & Wages",
|
||||||
|
"role": roles.EXPENSE_OPERATIONAL,
|
||||||
|
"balance_type": roles.DEBIT,
|
||||||
|
"locked": False,
|
||||||
|
"default": True, # Default for EXPENSE_OPERATIONAL
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"code": "6020",
|
||||||
|
"name": "Rent",
|
||||||
|
"role": roles.EXPENSE_OPERATIONAL,
|
||||||
|
"balance_type": roles.DEBIT,
|
||||||
|
"locked": False,
|
||||||
|
"default": False,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"code": "6030",
|
||||||
|
"name": "Utilities",
|
||||||
|
"role": roles.EXPENSE_OPERATIONAL,
|
||||||
|
"balance_type": roles.DEBIT,
|
||||||
|
"locked": False,
|
||||||
|
"default": False,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"code": "6040",
|
||||||
|
"name": "Advertising & Marketing",
|
||||||
|
"role": roles.EXPENSE_OPERATIONAL,
|
||||||
|
"balance_type": roles.DEBIT,
|
||||||
|
"locked": False,
|
||||||
|
"default": False,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"code": "6050",
|
||||||
|
"name": "Maintenance",
|
||||||
|
"role": roles.EXPENSE_OPERATIONAL,
|
||||||
|
"balance_type": roles.DEBIT,
|
||||||
|
"locked": False,
|
||||||
|
"default": False,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"code": "6060",
|
||||||
|
"name": "Operating Expenses",
|
||||||
|
"role": roles.EXPENSE_OPERATIONAL,
|
||||||
|
"balance_type": roles.DEBIT,
|
||||||
|
"locked": False,
|
||||||
|
"default": False,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"code": "6070",
|
||||||
|
"name": "Depreciation",
|
||||||
|
"role": roles.EXPENSE_DEPRECIATION,
|
||||||
|
"balance_type": roles.DEBIT,
|
||||||
|
"locked": False,
|
||||||
|
"default": True, # Default for EXPENSE_DEPRECIATION
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"code": "6080",
|
||||||
|
"name": "Fees & Taxes",
|
||||||
|
"role": roles.EXPENSE_OPERATIONAL,
|
||||||
|
"balance_type": roles.DEBIT,
|
||||||
|
"locked": False,
|
||||||
|
"default": False,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"code": "6090",
|
||||||
|
"name": "Bank Charges",
|
||||||
|
"role": roles.EXPENSE_OPERATIONAL,
|
||||||
|
"balance_type": roles.DEBIT,
|
||||||
|
"locked": False,
|
||||||
|
"default": False,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"code": "6100",
|
||||||
|
"name": "Other Expenses",
|
||||||
|
"role": roles.EXPENSE_OTHER,
|
||||||
|
"balance_type": roles.DEBIT,
|
||||||
|
"locked": False,
|
||||||
|
"default": True, # Default for EXPENSE_OTHER
|
||||||
|
},
|
||||||
|
]
|
||||||
|
|
||||||
|
def create_account(entity, coa, account_data):
|
||||||
|
try:
|
||||||
|
account = entity.create_account(
|
||||||
|
coa_model=coa,
|
||||||
|
code=account_data["code"],
|
||||||
|
name=account_data["name"],
|
||||||
|
role=account_data["role"],
|
||||||
|
balance_type=_(account_data["balance_type"]),
|
||||||
|
active=True,
|
||||||
|
)
|
||||||
|
account.role_default = account_data["default"]
|
||||||
|
account.save()
|
||||||
|
logger.info(f"Created default account: {account}")
|
||||||
|
except Exception as e:
|
||||||
|
logger.error(f"Error creating default account: {account_data['code']}, {e}")
|
||||||
@ -451,13 +451,12 @@ def general_dashboard(request,dealer_slug):
|
|||||||
total_cars_sold = cars_sold_filtered.count()
|
total_cars_sold = cars_sold_filtered.count()
|
||||||
total_cost_of_cars_sold = cars_sold_filtered.aggregate(total=Sum('cost_price'))['total'] or 0
|
total_cost_of_cars_sold = cars_sold_filtered.aggregate(total=Sum('cost_price'))['total'] or 0
|
||||||
total_revenue_from_cars = cars_sold_filtered.aggregate(
|
total_revenue_from_cars = cars_sold_filtered.aggregate(
|
||||||
total=Sum(F('selling_price') - F('discount_amount'))
|
total=Sum(F('marked_price') - F('discount_amount'))
|
||||||
)['total'] or 0
|
)['total'] or 0
|
||||||
|
|
||||||
# Correct calculation for VAT based on the selling price
|
total_vat_collected_from_cars = cars_sold_filtered.annotate(
|
||||||
total_vat_collected_from_cars = cars_sold_filtered.aggregate(
|
final_price=F('marked_price') - F('discount_amount')).aggregate(
|
||||||
total=Sum(F('selling_price') * VAT_RATE)
|
total=Sum(F('final_price') * VAT_RATE))['total'] or 0
|
||||||
)['total'] or 0
|
|
||||||
|
|
||||||
net_profit_from_cars = total_revenue_from_cars - total_cost_of_cars_sold
|
net_profit_from_cars = total_revenue_from_cars - total_cost_of_cars_sold
|
||||||
total_discount = cars_sold_filtered.aggregate(total=Sum('discount_amount'))['total'] or 0
|
total_discount = cars_sold_filtered.aggregate(total=Sum('discount_amount'))['total'] or 0
|
||||||
@ -466,24 +465,31 @@ def general_dashboard(request,dealer_slug):
|
|||||||
new_cars_sold = cars_sold_filtered.filter(stock_type='new')
|
new_cars_sold = cars_sold_filtered.filter(stock_type='new')
|
||||||
total_new_cars_sold = new_cars_sold.count()
|
total_new_cars_sold = new_cars_sold.count()
|
||||||
total_cost_of_new_cars_sold = new_cars_sold.aggregate(total=Sum('cost_price'))['total'] or 0
|
total_cost_of_new_cars_sold = new_cars_sold.aggregate(total=Sum('cost_price'))['total'] or 0
|
||||||
|
# total_revenue_from_new_cars=sum([ car.final_price for car in new_cars_sold])
|
||||||
total_revenue_from_new_cars = new_cars_sold.aggregate(
|
total_revenue_from_new_cars = new_cars_sold.aggregate(
|
||||||
total=Sum(F('selling_price') - F('discount_amount'))
|
total=Sum(F('marked_price') - F('discount_amount'))
|
||||||
)['total'] or 0
|
)['total'] or 0
|
||||||
|
|
||||||
|
total_vat_collected_from_new_cars = new_cars_sold.annotate(
|
||||||
|
final_price=F('marked_price') - F('discount_amount')).aggregate(
|
||||||
|
total=Sum(F('final_price') * VAT_RATE))['total'] or 0
|
||||||
|
|
||||||
net_profit_from_new_cars = total_revenue_from_new_cars - total_cost_of_new_cars_sold
|
net_profit_from_new_cars = total_revenue_from_new_cars - total_cost_of_new_cars_sold
|
||||||
total_vat_collected_from_new_cars = new_cars_sold.aggregate(
|
|
||||||
total=Sum(F('selling_price') * VAT_RATE)
|
|
||||||
)['total'] or 0
|
|
||||||
|
|
||||||
used_cars_sold = cars_sold_filtered.filter(stock_type='used')
|
used_cars_sold = cars_sold_filtered.filter(stock_type='used')
|
||||||
total_used_cars_sold = used_cars_sold.count()
|
total_used_cars_sold = used_cars_sold.count()
|
||||||
total_cost_of_used_cars_sold = used_cars_sold.aggregate(total=Sum('cost_price'))['total'] or 0
|
total_cost_of_used_cars_sold = used_cars_sold.aggregate(total=Sum('cost_price'))['total'] or 0
|
||||||
total_revenue_from_used_cars = used_cars_sold.aggregate(
|
total_revenue_from_used_cars = used_cars_sold.aggregate(
|
||||||
total=Sum(F('selling_price') - F('discount_amount'))
|
total=Sum(F('marked_price') - F('discount_amount'))
|
||||||
)['total'] or 0
|
)['total'] or 0
|
||||||
|
|
||||||
|
total_vat_collected_from_used_cars = used_cars_sold.annotate(
|
||||||
|
final_price=F('marked_price') - F('discount_amount')).aggregate(
|
||||||
|
total=Sum(F('final_price') * VAT_RATE))['total'] or 0
|
||||||
|
|
||||||
net_profit_from_used_cars = total_revenue_from_used_cars - total_cost_of_used_cars_sold
|
net_profit_from_used_cars = total_revenue_from_used_cars - total_cost_of_used_cars_sold
|
||||||
total_vat_collected_from_used_cars = used_cars_sold.aggregate(
|
|
||||||
total=Sum(F('selling_price') * VAT_RATE)
|
|
||||||
)['total'] or 0
|
|
||||||
|
|
||||||
# Service & Overall KPIs
|
# Service & Overall KPIs
|
||||||
total_revenue_from_services = sum([car.get_additional_services()['total'] for car in cars_sold_filtered])
|
total_revenue_from_services = sum([car.get_additional_services()['total'] for car in cars_sold_filtered])
|
||||||
@ -502,8 +508,8 @@ def general_dashboard(request,dealer_slug):
|
|||||||
month=ExtractMonth('sold_date')
|
month=ExtractMonth('sold_date')
|
||||||
).values('month').annotate(
|
).values('month').annotate(
|
||||||
total_cars=Count('pk'),
|
total_cars=Count('pk'),
|
||||||
total_revenue=Sum(F('selling_price') - F('discount_amount')),
|
total_revenue=Sum(F('marked_price') - F('discount_amount')),
|
||||||
total_profit=Sum(F('selling_price') - F('discount_amount') - F('cost_price'))
|
total_profit=Sum(F('marked_price') - F('discount_amount') - F('cost_price'))
|
||||||
).order_by('month')
|
).order_by('month')
|
||||||
|
|
||||||
monthly_cars_sold = [0] * 12
|
monthly_cars_sold = [0] * 12
|
||||||
@ -1478,7 +1484,6 @@ def inventory_stats_view(request, dealer_slug):
|
|||||||
|
|
||||||
# Base queryset for cars belonging to the dealer
|
# Base queryset for cars belonging to the dealer
|
||||||
cars = models.Car.objects.filter(dealer=request.dealer)
|
cars = models.Car.objects.filter(dealer=request.dealer)
|
||||||
print(cars)
|
|
||||||
# Count for total, reserved, showroom, and unreserved cars
|
# Count for total, reserved, showroom, and unreserved cars
|
||||||
total_cars = cars.count()
|
total_cars = cars.count()
|
||||||
reserved_cars = models.CarReservation.objects.count()
|
reserved_cars = models.CarReservation.objects.count()
|
||||||
@ -11067,6 +11072,9 @@ def purchase_report_csv_export(request,dealer_slug):
|
|||||||
@login_required
|
@login_required
|
||||||
def car_sale_report_view(request, dealer_slug):
|
def car_sale_report_view(request, dealer_slug):
|
||||||
dealer = get_object_or_404(models.Dealer, slug=dealer_slug)
|
dealer = get_object_or_404(models.Dealer, slug=dealer_slug)
|
||||||
|
vat = models.VatRate.objects.filter(dealer=dealer,is_active=True).first()
|
||||||
|
VAT_RATE=vat.rate
|
||||||
|
|
||||||
cars_sold = models.Car.objects.filter(dealer=dealer, status='sold')
|
cars_sold = models.Car.objects.filter(dealer=dealer, status='sold')
|
||||||
|
|
||||||
|
|
||||||
@ -11092,8 +11100,13 @@ def car_sale_report_view(request, dealer_slug):
|
|||||||
|
|
||||||
# # Calculate summary data for the filtered results
|
# # Calculate summary data for the filtered results
|
||||||
total_cars_sold=cars_sold.count()
|
total_cars_sold=cars_sold.count()
|
||||||
total_revenue_from_cars = sum([ car.final_price for car in cars_sold])
|
total_revenue_from_cars = cars_sold.aggregate(
|
||||||
total_vat_on_cars=sum([car.vat_amount for car in cars_sold])
|
total=Sum(F('marked_price') - F('discount_amount'))
|
||||||
|
)['total'] or 0
|
||||||
|
|
||||||
|
total_vat_on_cars=cars_sold.annotate(
|
||||||
|
final_price=F('marked_price') - F('discount_amount')).aggregate(
|
||||||
|
total=Sum(F('final_price') * VAT_RATE))['total'] or 0
|
||||||
|
|
||||||
total_revenue_from_additonals=sum([car.get_additional_services()['total'] for car in cars_sold])
|
total_revenue_from_additonals=sum([car.get_additional_services()['total'] for car in cars_sold])
|
||||||
total_vat_from_additonals=sum([car.get_additional_services()['services_vat'] for car in cars_sold])
|
total_vat_from_additonals=sum([car.get_additional_services()['services_vat'] for car in cars_sold])
|
||||||
|
|||||||
@ -433,6 +433,15 @@
|
|||||||
</div>
|
</div>
|
||||||
</a>
|
</a>
|
||||||
</div>
|
</div>
|
||||||
|
{% if request.user.is_authenticated%}
|
||||||
|
<div class="d-flex mx-4 px-4">
|
||||||
|
<div class="navbar-logo">
|
||||||
|
<div class="d-flex align-items-center">
|
||||||
|
<h5 class="text-warning ms-2 d-none d-sm-block">{% trans 'Hello, ' %}{{ request.user.first_name|default:request.dealer.name }} {{ request.user.last_name }}</h5>
|
||||||
|
</div>
|
||||||
|
</a>
|
||||||
|
</div>
|
||||||
|
{% endif %}
|
||||||
|
|
||||||
<ul class="navbar-nav navbar-nav-icons flex-row gap-2" hx-boost="false">
|
<ul class="navbar-nav navbar-nav-icons flex-row gap-2" hx-boost="false">
|
||||||
<li class="nav-item">
|
<li class="nav-item">
|
||||||
@ -537,7 +546,7 @@
|
|||||||
</ul>
|
</ul>
|
||||||
</hr>
|
</hr>
|
||||||
<div class="px-3">
|
<div class="px-3">
|
||||||
<a class="btn btn-sm btn-phoenix-danger d-flex flex-center w-100" href="{% url 'account_logout' %}"> <span class="me-2" data-feather="log-out"> </span>{% trans 'Sign Out' %}</a>
|
<a class="btn btn-sm btn-phoenix-danger d-flex flex-center w-100" href="{% url 'account_logout' %}"> <span class="fas fa-power-off me-2"> </span>{% trans 'Sign Out' %}</a>
|
||||||
</div>
|
</div>
|
||||||
<div class="my-2 text-center fw-bold fs-10 text-body-quaternary">
|
<div class="my-2 text-center fw-bold fs-10 text-body-quaternary">
|
||||||
<a class="text-body-quaternary me-1" href="">{% trans 'Privacy policy' %}</a>•<a class="text-body-quaternary mx-1" href="">{% trans 'Terms' %}</a>•<a class="text-body-quaternary ms-1" href="">Cookies</a>
|
<a class="text-body-quaternary me-1" href="">{% trans 'Privacy policy' %}</a>•<a class="text-body-quaternary mx-1" href="">{% trans 'Terms' %}</a>•<a class="text-body-quaternary ms-1" href="">Cookies</a>
|
||||||
|
|||||||
@ -88,7 +88,7 @@
|
|||||||
<td class="align-middle white-space-nowrap text-center fw-bold text-body-tertiary">
|
<td class="align-middle white-space-nowrap text-center fw-bold text-body-tertiary">
|
||||||
{% if car.stock_type == "new" %}
|
{% if car.stock_type == "new" %}
|
||||||
<span class="badge badge-phoenix badge-phoenix-success"><span class="badge-label">{{ _("New") }}</span></span>
|
<span class="badge badge-phoenix badge-phoenix-success"><span class="badge-label">{{ _("New") }}</span></span>
|
||||||
{% elif car.status == "used" %}
|
{% elif car.stock_type == "used" %}
|
||||||
<span class="badge badge-phoenix badge-phoenix-info"><span class="badge-label">{{ _("Used") }}</span></span>
|
<span class="badge badge-phoenix badge-phoenix-info"><span class="badge-label">{{ _("Used") }}</span></span>
|
||||||
{% endif %}
|
{% endif %}
|
||||||
</td>
|
</td>
|
||||||
|
|||||||
@ -5,10 +5,50 @@
|
|||||||
{% endblock %}
|
{% endblock %}
|
||||||
{% block content %}
|
{% block content %}
|
||||||
|
|
||||||
|
{% block customCSS%}
|
||||||
|
<style>
|
||||||
|
.road {
|
||||||
|
width: 70%;
|
||||||
|
height: 10vh;
|
||||||
|
position: relative;
|
||||||
|
overflow: hidden; /* This is essential for the animation to work correctly */
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
.moving-car {
|
||||||
|
position: absolute;
|
||||||
|
font-size: 3rem;
|
||||||
|
font-weight: bold;
|
||||||
|
color: #2e5466;
|
||||||
|
bottom: 20%;
|
||||||
|
white-space: nowrap; /* Prevents the text from wrapping to a new line */
|
||||||
|
left: 100%; /* Start the text outside the right edge */
|
||||||
|
animation: moveText 5s linear infinite alternate;
|
||||||
|
}
|
||||||
|
|
||||||
|
@keyframes moveText {
|
||||||
|
0% {
|
||||||
|
transform: translateX(0);
|
||||||
|
}
|
||||||
|
100% {
|
||||||
|
transform: translateX(-100%);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
</style>
|
||||||
|
{% endblock %}
|
||||||
{% if inventory.total_cars > 0 %}
|
{% if inventory.total_cars > 0 %}
|
||||||
<div class="row justify-content-between">
|
<div class="row justify-content-between">
|
||||||
<div class="col-sm-12 ">
|
<div class="col-sm-12 ">
|
||||||
<div class="card border h-100 w-100 p-lg-10">
|
<div class="card border h-100 w-100 p-lg-10">
|
||||||
|
|
||||||
|
{% comment %} <div class="road-container">
|
||||||
|
<img src="https://encrypted-tbn0.gstatic.com/images?q=tbn:ANd9GcSY0ESBb8625a12EguCyY8j4eL93sY3ibEAuQ&s" alt="Car" id="car" class="img-fluid" style="height:30px; width:30px;">
|
||||||
|
</div> {% endcomment %}
|
||||||
|
|
||||||
|
<div class="road">
|
||||||
|
<p class="moving-car ">Powered By Tenhal </p>
|
||||||
|
</div>
|
||||||
|
|
||||||
<div class="bg-holder bg-card"
|
<div class="bg-holder bg-card"
|
||||||
style="background-image:url({% static 'images/spot-illustrations/32.png' %});
|
style="background-image:url({% static 'images/spot-illustrations/32.png' %});
|
||||||
background-position: top right"></div>
|
background-position: top right"></div>
|
||||||
@ -100,3 +140,29 @@
|
|||||||
{% include "empty-illustration-page.html" with value="car" url=create_car_url %}
|
{% include "empty-illustration-page.html" with value="car" url=create_car_url %}
|
||||||
{% endif %}
|
{% endif %}
|
||||||
{% endblock %}
|
{% endblock %}
|
||||||
|
|
||||||
|
|
||||||
|
{% block customJS%}
|
||||||
|
<script>
|
||||||
|
|
||||||
|
document.addEventListener('DOMContentLoaded', () => {
|
||||||
|
const car = document.getElementById('car');
|
||||||
|
let positionX = 10;
|
||||||
|
const speed = 5;
|
||||||
|
|
||||||
|
document.addEventListener('keydown', (event) => {
|
||||||
|
if (event.key === 'ArrowRight') {
|
||||||
|
positionX += speed;
|
||||||
|
car.style.left = positionX + '%';
|
||||||
|
} else if (event.key === 'ArrowLeft') {
|
||||||
|
positionX -= speed;
|
||||||
|
if (positionX < 0) {
|
||||||
|
positionX = 0;
|
||||||
|
}
|
||||||
|
car.style.left = positionX + '%';
|
||||||
|
}
|
||||||
|
});
|
||||||
|
});
|
||||||
|
</script>
|
||||||
|
|
||||||
|
{% endblock %}
|
||||||
@ -172,7 +172,7 @@
|
|||||||
{% for item in items %}
|
{% for item in items %}
|
||||||
<div class="option" data-value="{{ item.hash }}" data-image="{{item.logo}}">
|
<div class="option" data-value="{{ item.hash }}" data-image="{{item.logo}}">
|
||||||
<img src="{{item.logo}}" alt="{{item.model}}">
|
<img src="{{item.logo}}" alt="{{item.model}}">
|
||||||
<span>{{item.make}} {{item.model}} {{item.serie}} {{item.trim}} {{item.color_name}}</span>
|
<span>{{item.vin}} {{item.make}} {{item.model}} {{item.serie}} {{item.trim}} {{item.color_name}}</span>
|
||||||
<div class="color-box" style="background-color: rgb({{ item.exterior_color }});"></div>
|
<div class="color-box" style="background-color: rgb({{ item.exterior_color }});"></div>
|
||||||
<div class="color-box" style="background-color: rgb({{ item.interior_color }});"></div>
|
<div class="color-box" style="background-color: rgb({{ item.interior_color }});"></div>
|
||||||
<span style="color:gray;">({{item.hash_count}} in stock)</span>
|
<span style="color:gray;">({{item.hash_count}} in stock)</span>
|
||||||
|
|||||||
@ -29,7 +29,6 @@
|
|||||||
{% else %}
|
{% else %}
|
||||||
<span class="rounded-circle d-inline-flex align-items-center justify-content-center bg-primary text-white"
|
<span class="rounded-circle d-inline-flex align-items-center justify-content-center bg-primary text-white"
|
||||||
style="width: 160px; height: 160px; font-size: 4rem;">
|
style="width: 160px; height: 160px; font-size: 4rem;">
|
||||||
{{ staff.get_local_name|first|upper }}
|
|
||||||
</span>
|
</span>
|
||||||
{% endif %}
|
{% endif %}
|
||||||
{% comment %} <input class="d-none" id="avatarFile" type="file"/>
|
{% comment %} <input class="d-none" id="avatarFile" type="file"/>
|
||||||
@ -38,7 +37,7 @@
|
|||||||
<i class="fas fa-camera"></i>
|
<i class="fas fa-camera"></i>
|
||||||
</label> {% endcomment %}
|
</label> {% endcomment %}
|
||||||
</div>
|
</div>
|
||||||
<h2 class="h3 mb-1 fw-bold">{{ staff.get_local_name }}</h2>
|
<h2 class="h3 mb-1 fw-bold">{{ staff.fullname }}</h2>
|
||||||
<p class="text-body-secondary mt-3 mb-0">
|
<p class="text-body-secondary mt-3 mb-0">
|
||||||
<i class="fas fa-clock me-1"></i>{% trans 'Joined' %} {{ staff.created|timesince }} {% trans 'ago' %}
|
<i class="fas fa-clock me-1"></i>{% trans 'Joined' %} {{ staff.created|timesince }} {% trans 'ago' %}
|
||||||
</p>
|
</p>
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user