This commit is contained in:
Marwan Alwali 2025-01-02 19:17:06 +03:00
parent 4664029c95
commit 7b3688cb89
33 changed files with 560 additions and 464 deletions

BIN
.DS_Store vendored

Binary file not shown.

BIN
db.sqlite

Binary file not shown.

View File

@ -254,6 +254,7 @@ wmi_manufacturer_mapping = {
"LND": "JMEV", "LND": "JMEV",
"LNP": "NAC MG", "LNP": "NAC MG",
"LNY": "Yuejin", "LNY": "Yuejin",
"LMX": "Dongfeng Forthing",
"LPA": "Changan", "LPA": "Changan",
"LPE": "BYD", "LPE": "BYD",
"LPS": "Polestar", "LPS": "Polestar",
@ -346,9 +347,7 @@ wmi_manufacturer_mapping = {
"MCG": "Atul", "MCG": "Atul",
"MC1": "Force", "MC1": "Force",
"MC2": "Eicher", "MC2": "Eicher",
"MDE": "Kinetic Engineering Limited",
"MDH": "Nissan", "MDH": "Nissan",
"MDT": "Kerala Limited",
"MD6": "TVS", "MD6": "TVS",
"MD9": "Shuttles", "MD9": "Shuttles",
"MEC": "Daimler", "MEC": "Daimler",
@ -358,8 +357,6 @@ wmi_manufacturer_mapping = {
"MET": "Piaggio", "MET": "Piaggio",
"MEX": "Škoda", "MEX": "Škoda",
"ME1": "Yamaha", "ME1": "Yamaha",
"ME3": "Royal Enfield",
"MYH": "Ather Energy",
"MZB": "Kia", "MZB": "Kia",
"MZZ": "Citroën", "MZZ": "Citroën",
"MZ7": "MG", "MZ7": "MG",
@ -1501,7 +1498,7 @@ def decode_vds(manufacturer, vds):
return "Unknown Model" return "Unknown Model"
def decode_vin(vin): def decode_vin_haikalna(vin):
if len(vin) != 17: if len(vin) != 17:
raise ValueError("Invalid VIN length. VIN must be 17 characters.") raise ValueError("Invalid VIN length. VIN must be 17 characters.")
@ -1518,21 +1515,22 @@ def decode_vin(vin):
manufacturer = wmi_manufacturer_mapping.get(wmi, vds) manufacturer = wmi_manufacturer_mapping.get(wmi, vds)
year_code = vis[0] year_code = vis[0]
year = vin_years(year_code) year = vin_years(year_code)[0]
model = decode_vds(manufacturer, vds) model = decode_vds(manufacturer, vds)
return { data = {
'VIN': vin, 'maker': manufacturer,
'Manufacturer': manufacturer, 'model': model,
'Year': year, 'modelYear': year,
'Model': model
} }
return data
# JTDKB3FU4G3507653 # JTDKB3FU4G3507653
# VR3USHNLWRJ521303 # VR3USHNLWRJ521303
# KNARH81E8P5194005 # KNARH81E8P5194005
# Example usage # Example usage
vin_number = 'VYFED9HP0SJ519559' # vin_number = 'LMXA14AF7PZ356070'
decoded_vin = decode_vin(vin_number) # decoded_vin = decode_vin_haikalna(vin_number)
print(decoded_vin) # print(decoded_vin)

View File

@ -1,18 +0,0 @@
# Generated by Django 5.1.4 on 2024-12-31 21:00
from django.db import migrations, models
class Migration(migrations.Migration):
dependencies = [
('inventory', '0021_opportunitylog'),
]
operations = [
migrations.AlterField(
model_name='customer',
name='is_lead',
field=models.BooleanField(default=False, verbose_name='Is Lead'),
),
]

View File

@ -0,0 +1,34 @@
# Generated by Django 5.1.4 on 2025-01-01 16:25
import django.db.models.deletion
from django.db import migrations, models
class Migration(migrations.Migration):
dependencies = [
('django_ledger', '0017_alter_accountmodel_unique_together_and_more'),
('inventory', '0021_opportunitylog'),
]
operations = [
migrations.RemoveField(
model_name='opportunitylog',
name='user',
),
migrations.AddField(
model_name='opportunitylog',
name='staff',
field=models.ForeignKey(null=True, on_delete=django.db.models.deletion.SET_NULL, to='inventory.staff', verbose_name='Staff'),
),
migrations.AlterField(
model_name='carfinance',
name='additional_services',
field=models.ManyToManyField(blank=True, related_name='additional_finances', to='django_ledger.itemmodel'),
),
migrations.AlterField(
model_name='customer',
name='is_lead',
field=models.BooleanField(default=False, verbose_name='Is Lead'),
),
]

View File

@ -1,5 +1,6 @@
# Generated by Django 4.2.17 on 2025-01-01 12:43 # Generated by Django 5.1.4 on 2025-01-01 16:40
import django.db.models.deletion
from django.db import migrations, models from django.db import migrations, models
@ -7,7 +8,7 @@ class Migration(migrations.Migration):
dependencies = [ dependencies = [
('django_ledger', '0017_alter_accountmodel_unique_together_and_more'), ('django_ledger', '0017_alter_accountmodel_unique_together_and_more'),
('inventory', '0022_rename_log_oportunitylog'), ('inventory', '0022_remove_opportunitylog_user_opportunitylog_staff_and_more'),
] ]
operations = [ operations = [
@ -17,7 +18,8 @@ class Migration(migrations.Migration):
), ),
migrations.AddField( migrations.AddField(
model_name='carfinance', model_name='carfinance',
name='services', name='additional_services',
field=models.ManyToManyField(blank=True, related_name='services', to='django_ledger.itemmodel'), field=models.ForeignKey(blank=True, default=1, on_delete=django.db.models.deletion.CASCADE, related_name='additional_finances', to='django_ledger.itemmodel'),
preserve_default=False,
), ),
] ]

View File

@ -1,23 +0,0 @@
# Generated by Django 5.1.4 on 2025-01-01 01:24
import django.db.models.deletion
from django.db import migrations, models
class Migration(migrations.Migration):
dependencies = [
('inventory', '0022_alter_customer_is_lead'),
]
operations = [
migrations.RemoveField(
model_name='opportunitylog',
name='user',
),
migrations.AddField(
model_name='opportunitylog',
name='staff',
field=models.ForeignKey(null=True, on_delete=django.db.models.deletion.SET_NULL, to='inventory.staff', verbose_name='Staff'),
),
]

View File

@ -1,4 +1,4 @@
# Generated by Django 4.2.17 on 2025-01-01 12:46 # Generated by Django 5.1.4 on 2025-01-01 16:51
from django.db import migrations, models from django.db import migrations, models
@ -13,7 +13,7 @@ class Migration(migrations.Migration):
operations = [ operations = [
migrations.RemoveField( migrations.RemoveField(
model_name='carfinance', model_name='carfinance',
name='services', name='additional_services',
), ),
migrations.AddField( migrations.AddField(
model_name='carfinance', model_name='carfinance',

View File

@ -154,7 +154,6 @@ class CarSpecificationValue(models.Model):
verbose_name = "Specification Value" verbose_name = "Specification Value"
# Car Model
class CarStatusChoices(models.TextChoices): class CarStatusChoices(models.TextChoices):
AVAILABLE = "available", _("Available") AVAILABLE = "available", _("Available")
SOLD = "sold", _("Sold") SOLD = "sold", _("Sold")
@ -184,6 +183,7 @@ class AdditionalServices(models.Model, LocalizedNameMixin):
def __str__(self): def __str__(self):
return self.name + " - " + str(self.price) return self.name + " - " + str(self.price)
class Car(models.Model): class Car(models.Model):
vin = models.CharField(max_length=17, unique=True, verbose_name=_("VIN")) vin = models.CharField(max_length=17, unique=True, verbose_name=_("VIN"))
dealer = models.ForeignKey( dealer = models.ForeignKey(
@ -308,6 +308,7 @@ class CarReservation(models.Model):
verbose_name = _("Car Reservation") verbose_name = _("Car Reservation")
verbose_name_plural = _("Car Reservations") verbose_name_plural = _("Car Reservations")
# Car Finance Model # Car Finance Model
class CarFinance(models.Model): class CarFinance(models.Model):
additional_services = models.ManyToManyField(ItemModel, related_name="additional_finances",blank=True) additional_services = models.ManyToManyField(ItemModel, related_name="additional_finances",blank=True)
@ -389,7 +390,6 @@ class InteriorColors(models.Model, LocalizedNameMixin):
return f"{self.name} ({self.rgb})" return f"{self.name} ({self.rgb})"
# Colors Model
class CarColors(models.Model): class CarColors(models.Model):
car = models.ForeignKey("Car", on_delete=models.CASCADE, related_name="colors") car = models.ForeignKey("Car", on_delete=models.CASCADE, related_name="colors")
exterior = models.ForeignKey( exterior = models.ForeignKey(
@ -408,7 +408,6 @@ class CarColors(models.Model):
return f"{self.car} ({self.exterior.name}) ({self.interior.name})" return f"{self.car} ({self.exterior.name}) ({self.interior.name})"
# Custom Card Model
class CustomCard(models.Model): class CustomCard(models.Model):
car = models.OneToOneField(Car, on_delete=models.CASCADE, related_name='custom_cards', verbose_name=_("Car")) car = models.OneToOneField(Car, on_delete=models.CASCADE, related_name='custom_cards', verbose_name=_("Car"))
custom_number = models.CharField(max_length=255, verbose_name=_("Custom Number")) custom_number = models.CharField(max_length=255, verbose_name=_("Custom Number"))
@ -471,7 +470,7 @@ class CarLocation(models.Model):
""" """
return self.owner == self.showroom return self.owner == self.showroom
# Car Registration Model
class CarRegistration(models.Model): class CarRegistration(models.Model):
car = models.ForeignKey( car = models.ForeignKey(
Car, Car,
@ -501,7 +500,7 @@ class TimestampedModel(models.Model):
class Meta: class Meta:
abstract = True abstract = True
#subscription
class Subscription(models.Model): class Subscription(models.Model):
plan = models.CharField(max_length=255) # e.g. "basic", "premium" plan = models.CharField(max_length=255) # e.g. "basic", "premium"
start_date = models.DateField() start_date = models.DateField()
@ -529,7 +528,6 @@ class SubscriptionPlan(models.Model):
return f"{self.name} - {self.price}" return f"{self.name} - {self.price}"
# Dealer Model
class Dealer(models.Model, LocalizedNameMixin): class Dealer(models.Model, LocalizedNameMixin):
user = models.OneToOneField(User, on_delete=models.CASCADE, related_name="dealer") user = models.OneToOneField(User, on_delete=models.CASCADE, related_name="dealer")
crn = models.CharField(max_length=10, crn = models.CharField(max_length=10,
@ -608,18 +606,21 @@ class Dealer(models.Model, LocalizedNameMixin):
# return self.parent_dealer if self.parent_dealer else self # return self.parent_dealer if self.parent_dealer else self
class StaffTypes(models.TextChoices):
class STAFF_TYPES(models.TextChoices):
MANAGER = "manager", _("Manager") MANAGER = "manager", _("Manager")
INVENTORY = "inventory", _("Inventory") INVENTORY = "inventory", _("Inventory")
ACCOUNTANT = "accountant", _("Accountant") ACCOUNTANT = "accountant", _("Accountant")
SALES = "sales", _("Sales") SALES = "sales", _("Sales")
##############################
# Additional staff types for later
# COORDINATOR = "coordinator", _("Coordinator") # COORDINATOR = "coordinator", _("Coordinator")
# RECEPTIONIST = "receptionist", _("Receptionist") # RECEPTIONIST = "receptionist", _("Receptionist")
# AGENT = "agent", _("Agent") # AGENT = "agent", _("Agent")
# TECHNICIAN = "technician", _("Technician") # TECHNICIAN = "technician", _("Technician")
# DRIVER = "driver", _("Driver") # DRIVER = "driver", _("Driver")
##############################
class Staff(models.Model, LocalizedNameMixin): class Staff(models.Model, LocalizedNameMixin):
@ -628,7 +629,7 @@ class Staff(models.Model, LocalizedNameMixin):
name = models.CharField(max_length=255, verbose_name=_("Name")) name = models.CharField(max_length=255, verbose_name=_("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 = PhoneNumberField(region="SA", verbose_name=_("Phone Number"))
staff_type = models.CharField(choices=STAFF_TYPES.choices, max_length=255, verbose_name=_("Staff Type")) staff_type = models.CharField(choices=StaffTypes.choices, max_length=255, verbose_name=_("Staff Type"))
created_at = models.DateTimeField(auto_now_add=True, verbose_name=_("Created At")) created_at = models.DateTimeField(auto_now_add=True, verbose_name=_("Created At"))
updated_at = models.DateTimeField(auto_now=True, verbose_name=_("Updated At")) updated_at = models.DateTimeField(auto_now=True, verbose_name=_("Updated At"))
@ -641,7 +642,6 @@ class Staff(models.Model, LocalizedNameMixin):
return f"{self.name} - {self.get_staff_type_display()}" return f"{self.name} - {self.get_staff_type_display()}"
# Vendor Model
class Vendor(models.Model, LocalizedNameMixin): class Vendor(models.Model, LocalizedNameMixin):
dealer = models.ForeignKey(Dealer, on_delete=models.CASCADE, related_name="vendors") dealer = models.ForeignKey(Dealer, on_delete=models.CASCADE, related_name="vendors")
crn = models.CharField( crn = models.CharField(
@ -671,7 +671,6 @@ class Vendor(models.Model, LocalizedNameMixin):
return self.name return self.name
# Customer Model
class Customer(models.Model): class Customer(models.Model):
dealer = models.ForeignKey(Dealer, dealer = models.ForeignKey(Dealer,
on_delete=models.CASCADE, on_delete=models.CASCADE,
@ -932,6 +931,7 @@ class SaleQuotation(models.Model):
last_quotation_number = 0 last_quotation_number = 0
return itertools.count(last_quotation_number + 1) return itertools.count(last_quotation_number + 1)
class SaleQuotationCar(models.Model): class SaleQuotationCar(models.Model):
quotation = models.ForeignKey( quotation = models.ForeignKey(
SaleQuotation, SaleQuotation,

View File

@ -12,6 +12,7 @@ from pyvin import VIN
from django.conf import settings from django.conf import settings
from openai import OpenAI from openai import OpenAI
from .models import Car,CarMake,CarModel from .models import Car,CarMake,CarModel
from inventory.haikalna import decode_vin_haikalna
def get_make(item): def get_make(item):
@ -37,6 +38,8 @@ def decodevin(vin):
return result return result
elif result:=elm(vin): elif result:=elm(vin):
return result return result
elif result:=decode_vin_haikalna(vin):
return result
else: else:
return None return None

View File

@ -30,3 +30,4 @@ def attr(field, args):
else: else:
attrs[definition.strip()] = True attrs[definition.strip()] = True
return field.as_widget(attrs=attrs) return field.as_widget(attrs=attrs)

View File

@ -27,7 +27,7 @@ urlpatterns = [
path('login/code/', allauth_views.RequestLoginCodeView.as_view(template_name='account/request_login_code.html')), path('login/code/', allauth_views.RequestLoginCodeView.as_view(template_name='account/request_login_code.html')),
#Dashboards #Dashboards
path('dashboards/accounting/', views.AccountingDashboard.as_view(), name='accounting'), path('dashboards/accounting/', views.AccountingDashboard.as_view(), name='accounting'),
path('test/', views.TestView.as_view(), name='test'),
# Dealer URLs # Dealer URLs
path('dealers/<int:pk>/', views.DealerDetailView.as_view(), name='dealer_detail'), path('dealers/<int:pk>/', views.DealerDetailView.as_view(), name='dealer_detail'),
path('dealers/<int:pk>/update/', views.DealerUpdateView.as_view(), name='dealer_update'), path('dealers/<int:pk>/update/', views.DealerUpdateView.as_view(), name='dealer_update'),

View File

@ -58,3 +58,11 @@ def send_email(from_, to_, subject, message):
from_email = from_ from_email = from_
recipient_list = [to_] recipient_list = [to_]
send_mail(subject, message, from_email, recipient_list) send_mail(subject, message, from_email, recipient_list)
def get_user_type(request):
if hasattr(request.user, 'dealer'):
dealer = request.user.dealer
elif hasattr(request.user, 'staff'):
dealer = request.user.staff.dealer
return dealer

View File

@ -66,7 +66,7 @@ from . import models, forms
from django.contrib.auth.mixins import PermissionRequiredMixin from django.contrib.auth.mixins import PermissionRequiredMixin
from django.contrib.messages.views import SuccessMessageMixin from django.contrib.messages.views import SuccessMessageMixin
from django.contrib.auth.models import Group from django.contrib.auth.models import Group
from .utils import get_calculations, send_email from .utils import get_calculations, send_email, get_user_type
from django.contrib.auth.models import User from django.contrib.auth.models import User
from allauth.account import views from allauth.account import views
from django.db.models import Count, F, Value from django.db.models import Count, F, Value
@ -166,6 +166,10 @@ class HomeView(TemplateView):
template_name = "index.html" template_name = "index.html"
class TestView(TemplateView):
template_name = "test.html"
class AccountingDashboard(LoginRequiredMixin, TemplateView): class AccountingDashboard(LoginRequiredMixin, TemplateView):
template_name = "dashboards/accounting.html" template_name = "dashboards/accounting.html"
@ -374,8 +378,9 @@ class CarInventory(LoginRequiredMixin, ListView):
model_id = self.kwargs["model_id"] model_id = self.kwargs["model_id"]
trim_id = self.kwargs["trim_id"] trim_id = self.kwargs["trim_id"]
dealer = get_user_type(self.request)
cars = models.Car.objects.filter( cars = models.Car.objects.filter(
dealer=self.request.user.dealer, dealer=dealer,
id_car_make=make_id, id_car_make=make_id,
id_car_model=model_id, id_car_model=model_id,
id_car_trim=trim_id, id_car_trim=trim_id,
@ -415,7 +420,7 @@ class CarColorCreate(LoginRequiredMixin, CreateView):
@login_required @login_required
def inventory_stats_view(request): def inventory_stats_view(request):
dealer = request.user.dealer dealer = get_user_type(request)
# Annotate total cars by make, model, and trim # Annotate total cars by make, model, and trim
cars = ( cars = (
@ -692,7 +697,6 @@ class DealerUpdateView(LoginRequiredMixin, SuccessMessageMixin, UpdateView):
class CustomerListView(LoginRequiredMixin, PermissionRequiredMixin, ListView): class CustomerListView(LoginRequiredMixin, PermissionRequiredMixin, ListView):
model = models.Customer model = models.Customer
home_label = _("customers")
context_object_name = "customers" context_object_name = "customers"
paginate_by = 10 paginate_by = 10
template_name = "customers/customer_list.html" template_name = "customers/customer_list.html"
@ -700,17 +704,15 @@ class CustomerListView(LoginRequiredMixin, PermissionRequiredMixin, ListView):
def get_queryset(self): def get_queryset(self):
query = self.request.GET.get("q") query = self.request.GET.get("q")
if self.request.user.is_staff: dealer = get_user_type(self.request)
dealer = self.request.user.staff.dealer
customers = models.Customer.objects.filter( customers = models.Customer.objects.filter(dealer=dealer)
dealer=dealer,
)
if query: if query:
customers = customers.filter( customers = customers.filter(
Q(national_id__icontains=query) Q(national_id__icontains=query) |
| Q(first_name__icontains=query) Q(first_name__icontains=query) |
| Q(last_name__icontains=query) Q(last_name__icontains=query)
) )
return customers return customers
@ -879,7 +881,7 @@ class QuotationDetailView(LoginRequiredMixin, PermissionRequiredMixin, DetailVie
@login_required @login_required
def generate_invoice(request, pk): def generate_invoice(request, pk):
quotation = get_object_or_404(models.SaleQuotation, pk=pk) quotation = get_object_or_404(models.SaleQuotation, pk=pk)
dealer = request.user.dealer dealer = get_user_type(request)
entity = dealer.entity entity = dealer.entity
if not quotation.is_approved: if not quotation.is_approved:
messages.error( messages.error(
@ -1731,7 +1733,8 @@ class EstimateListView(LoginRequiredMixin, ListView):
# @csrf_exempt # @csrf_exempt
@login_required @login_required
def create_estimate(request): def create_estimate(request):
entity = request.entity dealer = get_user_type(request)
entity = dealer.entity
if request.method == "POST": if request.method == "POST":
try: try:
data = json.loads(request.body) data = json.loads(request.body)
@ -1932,7 +1935,8 @@ class InvoiceListView(LoginRequiredMixin, ListView):
context_object_name = "invoices" context_object_name = "invoices"
def get_queryset(self): def get_queryset(self):
entity = self.request.user.dealer.entity dealer = get_user_type(self.request)
entity = dealer.entity
return entity.get_invoices() return entity.get_invoices()
@ -2137,7 +2141,9 @@ def PaymentCreateView(request, pk=None):
def PaymentListView(request): def PaymentListView(request):
entity = request.user.dealer.entity dealer = get_user_type(request)
entity = dealer.entity
journals = JournalEntryModel.objects.filter(ledger__entity=entity).all() journals = JournalEntryModel.objects.filter(ledger__entity=entity).all()
return render(request, "sales/payments/payment_list.html", {"journals": journals}) return render(request, "sales/payments/payment_list.html", {"journals": journals})
@ -2314,10 +2320,10 @@ def fetch_notifications(request):
class ItemServiceCreateView(CreateView): class ItemServiceCreateView(CreateView):
model = ItemModel model = ItemModel
form_class = ServiceCreateForm form_class = ServiceCreateForm
template_name = "items/service/service_create.html" template_name = "items/service/service_create.html"
success_url = reverse_lazy("item_service_list") success_url = reverse_lazy("item_service_list")
def get_form_kwargs(self): def get_form_kwargs(self):
kwargs = super().get_form_kwargs() kwargs = super().get_form_kwargs()

BIN
static/.DS_Store vendored

Binary file not shown.

View File

@ -0,0 +1,9 @@
.color-div {
width: 22px;
height: 22px;
border-radius: 50%;
border: 1px solid #ccc;
text-align: center;
vertical-align: middle;
line-height: 22px;
}

Binary file not shown.

Binary file not shown.

View File

@ -21,7 +21,7 @@ const getDataTableInit = () => {
button.classList[disabled ? 'add' : 'remove']('disabled'); button.classList[disabled ? 'add' : 'remove']('disabled');
}; };
// Selectors // Selectors
const table = document.getElementById('opportunityTable'); const table = document.getElementById('inventoryTable');
if (table) { if (table) {
const options = { const options = {

View File

@ -31,7 +31,7 @@
<link href="{% static 'vendors/simplebar/simplebar.min.css' %}" rel="stylesheet"> <link href="{% static 'vendors/simplebar/simplebar.min.css' %}" rel="stylesheet">
<link href="{% static 'css/sweetalert2.min.css' %}" rel="stylesheet"> <link href="{% static 'css/sweetalert2.min.css' %}" rel="stylesheet">
<link rel="stylesheet" href="https://unicons.iconscout.com/release/v4.0.8/css/line.css"> <link rel="stylesheet" href="https://unicons.iconscout.com/release/v4.0.8/css/line.css">
<link href="{% static 'css/custom.css' %}" rel="stylesheet">
{% if LANGUAGE_CODE == 'en' %} {% if LANGUAGE_CODE == 'en' %}
<link href="{% static 'css/theme.min.css' %}" type="text/css" rel="stylesheet" id="style-default"> <link href="{% static 'css/theme.min.css' %}" type="text/css" rel="stylesheet" id="style-default">
<link href="{% static 'css/user.min.css' %}" type="text/css" rel="stylesheet" id="user-style-default"> <link href="{% static 'css/user.min.css' %}" type="text/css" rel="stylesheet" id="user-style-default">
@ -39,6 +39,7 @@
<link href="{% static 'css/theme-rtl.min.css' %}" type="text/css" rel="stylesheet" id="style-rtl"> <link href="{% static 'css/theme-rtl.min.css' %}" type="text/css" rel="stylesheet" id="style-rtl">
<link href="{% static 'css/user-rtl.min.css' %}" type="text/css" rel="stylesheet" id="user-style-rtl"> <link href="{% static 'css/user-rtl.min.css' %}" type="text/css" rel="stylesheet" id="user-style-rtl">
{% endif %} {% endif %}
{% block extra_css %}{% endblock extra_css %} {% block extra_css %}{% endblock extra_css %}
</head> </head>
<body> <body>

View File

@ -1,94 +1,28 @@
{% extends 'base.html' %} {% extends 'base.html' %}
{% load static %} {% load i18n static custom_filters %}
{% load i18n %}
{% load custom_filters %}
{% block title %}{{ _("Car Details") }}{% endblock %} {% block title %}{{ _("Car Details") }}{% endblock %}
{% block content %} {% block content %}
<style> <style>
.color-circle{ .color-circle {
width: 32px; width: 32px;
height: 32px; height: 32px;
border-radius: 50px; border-radius: 50px;
border-style: solid; border-style: solid;
border-color: #fff; border-color: #fff;
} }
</style> </style>
<div class="container">
<div class="pb-5">
<!-- Custom Card Modal -->
<div class="modal fade" id="customCardModal" tabindex="-1" aria-labelledby="customCardModalLabel" aria-hidden="true">
<div class="modal-dialog modal-sm">
<div class="modal-content">
<div class="modal-header bg-primary">
<h5 class="modal-title text-light" id="customCardModalLabel">{% trans 'Custom Card' %}</h5>
<button type="button" class="btn-close" data-bs-dismiss="modal" aria-label="Close"></button>
</div>
<div class="modal-body">
<!-- Content will be loaded here via AJAX -->
</div>
</div>
</div>
</div>
<!-- Reservation Modal --> <!-- Main Container -->
<div class="modal fade" id="reserveModal" tabindex="-1" aria-labelledby="reserveModalLabel" aria-hidden="true"> <div class="container-fluid">
<div class="modal-dialog modal-sm"> <div class="row g-3 justify-content-between">
<div class="modal-content"> <div class="col-lg-12 col-xl-6">
<div class="modal-header bg-primary"> <div class="card rounded shadow d-flex align-content-center">
<h5 class="modal-title text-light" id="reserveModalLabel">{% trans 'Car Reservation' %}</h5> <p class="card-header rounded-top fw-bold">{% trans 'Car Details' %}</p>
<button type="button" class="btn-close" data-bs-dismiss="modal" aria-label="Close"></button> <div class="card-body">
</div> <div class="table-responsive scrollbar mb-3">
<div class="modal-body"> <table class="table table-sm fs-9 mb-0 overflow-hidden">
{% trans 'Are you sure you want to reserve this car?' %}
</div>
<div class="modal-footer">
<button type="button"
class="btn btn-sm btn-phoenix-danger"
data-bs-dismiss="modal">
{% trans 'No' %}
</button>
<form method="POST" action="{% url 'reserve_car' car.id %}" class="d-inline">
{% csrf_token %}
<button type="submit" class="btn btn-phoenix-success btn-sm">{% trans "Yes" %}</button>
</form>
</div>
</div>
</div>
</div>
<!-- Specification Modal -->
<div class="modal fade" id="specificationsModal" tabindex="-1" aria-labelledby="specificationsModalLabel" aria-hidden="true">
<div class="modal-dialog modal-lg modal-dialog-scrollable">
<div class="modal-content">
<div class="modal-header">
<h5 class="modal-title" id="specificationsModalLabel">{% trans 'specifications'|upper %}</h5>
<button class="btn btn-close p-1" type="button" data-bs-dismiss="modal" aria-label="Close"></button>
</div>
<div class="modal-body">
<div id="specificationsContent"></div>
</div>
<div class="modal-footer">
<button class="btn btn-phoenix-primary" type="button" data-bs-dismiss="modal">{% trans 'Close' %}</button>
</div>
</div>
</div>
</div>
<!-- Specification Modal -->
<!-- Main Container -->
<div class="row gx-6">
<div class="col-lg-12 col-xl-6">
<div class="container-small-fluid">
<div class="card rounded shadow d-flex align-content-center">
<p class="card-header rounded-top fw-bold">{% trans 'Car Details' %}</p>
<div class="card-body">
<div class="table-responsive">
<table class="table fs-9 mb-0">
<tr> <tr>
<th>{% trans "VIN" %}</th> <th>{% trans "VIN" %}</th>
<td>{{ car.vin }}</td> <td>{{ car.vin }}</td>
@ -129,7 +63,7 @@
<th>{% trans "Receiving Date"|capfirst %}</th> <th>{% trans "Receiving Date"|capfirst %}</th>
<td>{{ car.receiving_date|timesince }}</td> <td>{{ car.receiving_date|timesince }}</td>
</tr> </tr>
{% if car.vendor %} {% if car.vendor %}
<tr> <tr>
<th>{% trans "Vendor"|capfirst %}</th> <th>{% trans "Vendor"|capfirst %}</th>
<td>{{ car.vendor.get_local_name }}</td> <td>{{ car.vendor.get_local_name }}</td>
@ -142,11 +76,7 @@
<tr> <tr>
<th>{% trans 'specifications'|capfirst %}</th> <th>{% trans 'specifications'|capfirst %}</th>
<td> <td>
<button type="button" <button type="button" class="btn btn-phoenix-primary btn-sm" id="specification-btn" data-bs-toggle="modal" data-bs-target="#specificationsModal">
class="btn btn-phoenix-primary btn-sm"
id="specification-btn"
data-bs-toggle="modal"
data-bs-target="#specificationsModal">
{% trans 'view'|capfirst %} {% trans 'view'|capfirst %}
</button> </button>
</td> </td>
@ -164,10 +94,7 @@
<tr> <tr>
<th>{% trans "Custom Card" %}</th> <th>{% trans "Custom Card" %}</th>
<td> <td>
<button type="button" <button type="button" class="btn btn-sm btn-phoenix-success" data-bs-toggle="modal" data-bs-target="#customCardModal">
class="btn btn-sm btn-phoenix-success"
data-bs-toggle="modal"
data-bs-target="#customCardModal">
{% trans 'Add' %} {% trans 'Add' %}
</button> </button>
</td> </td>
@ -176,42 +103,33 @@
<tr> <tr>
<th>{% trans 'Location'|capfirst %}</th> <th>{% trans 'Location'|capfirst %}</th>
<td> <td>
{% if car.location %} {% if car.location %} {% if car.location.is_owner_showroom %} {% trans 'Our Showroom' %} {% else %} {{ car.location.showroom.get_local_name }} {% endif %}
{% if car.location.is_owner_showroom %} <a href="{% url 'transfer' car.location.pk %}" class="btn btn-phoenix-danger btn-sm">
{% trans 'Our Showroom' %} {% trans "transfer"|capfirst %}
{% else %}
{{ car.location.showroom.get_local_name }}
{% endif %}
<a href="{% url 'transfer' car.location.pk %}"
class="btn btn-phoenix-danger btn-sm">
{% trans "transfer"|capfirst %}
</a> </a>
{% else %} {% trans "No location available." %} {% else %} {% trans "No location available." %}
<a href="{% url 'add_car_location' car.pk %}" <a href="{% url 'add_car_location' car.pk %}" class="btn btn-phoenix-success btn-sm ms-2">
class="btn btn-phoenix-success btn-sm ms-2">
{% trans "Add" %} {% trans "Add" %}
</a> </a>
</td> </td>
{% endif %} {% endif %}
</tr> </tr>
</table> </table>
</div>
<div>
<a href="{% url 'car_update' car.pk %}" class="btn btn-phoenix-warning btn-sm mt-1">{% trans "Edit" %}</a>
</div>
</div>
</div> </div>
</div> <div>
<a href="{% url 'car_update' car.pk %}" class="btn btn-phoenix-warning btn-sm mt-1">{% trans "Edit" %}</a>
</div> </div>
<div class="col-lg-6 col-xl-6"> </div>
<div class="card rounded shadow"> </div>
<p class="card-header rounded-top fw-bold">{% trans 'Financial Details' %}</p> </div>
<div class="col-lg-6 col-xl-6">
<div class="card-body"> <div class="card rounded shadow d-flex align-content-center">
<p class="card-header rounded-top fw-bold">{% trans 'Financial Details' %}</p>
<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.finances %}
<div class="table-responsive">
<table class="table fs-9 mb-0">
{% if perms.inventory.view_carfinance %} {% if perms.inventory.view_carfinance %}
<tr> <tr>
<th>{% trans "Cost Price"|capfirst %}</th> <th>{% trans "Cost Price"|capfirst %}</th>
@ -231,12 +149,12 @@
<td></td> <td></td>
</tr> </tr>
{% if car.finances.additional_services.first.pk %} {% if car.finances.additional_services.first.pk %}
{% for service in car.finances.additional_services.all %} {% for service in car.finances.additional_services.all %}
<tr> <tr>
<td>{{service.name}}</td> <td>{{service.name}}</td>
<td>{{ service.default_amount }}</td> <td>{{ service.default_amount }}</td>
</tr> </tr>
{% endfor %} {% endfor %}
{% endif %} {% endif %}
<tr> <tr>
<th>{% trans "VAT Amount"|capfirst %}</th> <th>{% trans "VAT Amount"|capfirst %}</th>
@ -246,85 +164,74 @@
<th>{% trans "Total"|capfirst %}</th> <th>{% trans "Total"|capfirst %}</th>
<td>{{ car.finances.total }}</td> <td>{{ car.finances.total }}</td>
</tr> </tr>
<tr> <tr>
<td colspan="2"> <td colspan="2">
{% if perms.inventory.change_carfinance %} {% if perms.inventory.change_carfinance %}
<a href="{% url 'car_finance_update' car.finances.pk %}" <a href="{% url 'car_finance_update' car.finances.pk %}" class="btn btn-phoenix-warning btn-sm mb-3">
class="btn btn-phoenix-warning btn-sm mb-3">
{% trans "Edit" %} {% trans "Edit" %}
</a> </a>
{% endif %} {% endif %}
{% else %}
{% else %}
<p>{% trans "No finance details available." %}</p> <p>{% trans "No finance details available." %}</p>
<a href="{% url 'car_finance_create' car.pk %}" <a href="{% url 'car_finance_create' car.pk %}" class="btn btn-phoenix-success btn-sm mb-3">
class="btn btn-phoenix-success btn-sm mb-3">
{% trans "Add" %} {% trans "Add" %}
</a> </a>
</td> </td>
</tr> </tr>
{% endif %}
</table>
</div>
</div>
</div>
<div class="card rounded shadow d-flex align-content-center mt-3">
<p class="card-header rounded-top fw-bold">{% trans 'Colors Details' %}</p>
<div class="card-body">
<div class="table-responsive scrollbar mb-3">
<table class="table table-sm fs-9 mb-0 overflow-hidden">
{% if car.colors.exists %}
{% for color in car.colors.all %}
<tr>
<th>{% trans 'Exterior' %}</th>
<td>
<span>{{ color.exterior.get_local_name }}</span>
</td>
<td class="align-middle">
<div class="text-end color-circle" style="background-color: rgb({{ color.exterior.rgb }});"></div>
</td>
</tr>
<tr>
<th>{% trans 'Interior' %}</th>
<td>
<span>{{ color.interior.get_local_name }}</span>
</td>
<td class="align-middle">
<div class="text-end color-circle" style="background-color: rgb({{ color.interior.rgb }});"></div>
</td>
</tr>
{% endfor %} {% else %}
<tr>
<td colspan="2">
{% trans "No colors available for this car." %}
</td>
</tr>
<tr>
<td colspan="2">
<a href="{% url 'add_color' car.pk %}" class="btn btn-phoenix-success btn-sm">
{% trans "Add" %}
</a>
</td>
</tr>
{% endif %} {% endif %}
</table> </table>
</div>
</div>
</div> </div>
<div class="card rounded shadow mt-3"> </div>
<p class="card-header rounded-top fw-bold">{% trans 'Colors Details' %}</p> </div>
<div class="card-body"> <div class="card rounded shadow d-flex align-content-center mt-3">
<div class="table-responsive"> <p class="card-header rounded-top fw-bold">{% trans 'Reservations Details' %}</p>
<table class="table fs-9 mb-0"> <div class="card-body">
<tbody class="align-middle"> <div class="table-responsive scrollbar mb-3">
{% if car.colors.exists %} <table class="table table-sm fs-9 mb-0 overflow-hidden">
{% for color in car.colors.all %}
<tr>
<th>{% trans 'Exterior' %}</th>
<td>
<span>{{ color.exterior.get_local_name }}</span>
</td>
<td class="align-middle">
<div class="text-end color-circle"
style="background-color: rgb({{ color.exterior.rgb }});">
</div>
</td>
</tr>
<tr>
<th>{% trans 'Interior' %}</th>
<td>
<span>{{ color.interior.get_local_name }}</span>
</td>
<td class="align-middle">
<div class="text-end color-circle"
style="background-color: rgb({{ color.interior.rgb }});">
</div>
</td>
</tr>
{% endfor %}
{% else %}
<tr>
<td colspan="2">
{% trans "No colors available for this car." %}
</td>
</tr>
<tr>
<td colspan="2">
<a href="{% url 'add_color' car.pk %}" class="btn btn-phoenix-success btn-sm">
{% trans "Add" %}
</a>
</td>
</tr>
{% endif %}
</tbody>
</table>
</div>
</div>
</div>
<div class="card rounded shadow mt-3">
<p class="card-header rounded-top fw-bold">{% trans 'Reservations Details' %}</p>
<div class="card-body">
<div class="table-responsive">
{% if car.is_reserved %} {% if car.is_reserved %}
<table class="table fs-9 mb-0">
<thead> <thead>
<tr> <tr>
<th>{% trans "Reserved By" %}</th> <th>{% trans "Reserved By" %}</th>
@ -341,50 +248,92 @@
{% if reservation.is_active %} {% if reservation.is_active %}
<form method="post" action="{% url 'reservations' reservation.id %}"> <form method="post" action="{% url 'reservations' reservation.id %}">
{% csrf_token %} {% csrf_token %}
<button type="submit" <button type="submit" name="action" value="renew" class="btn btn-sm btn-phoenix-success">
name="action"
value="renew"
class="btn btn-sm btn-phoenix-success">
{% trans "Renew" %} {% trans "Renew" %}
</button> </button>
<button type="submit" <button type="submit" name="action" value="cancel" class="btn btn-sm btn-phoenix-secondary">
name="action"
value="cancel"
class="btn btn-sm btn-phoenix-secondary">
{% trans "Cancel" %} {% trans "Cancel" %}
</button> </button>
</form> </form>
{% else %} {% else %}
<span class="badge bg-danger" <span class="badge bg-danger" style="width: 120px;">
style="width: 120px;"> {% trans "Expired" %}
{% trans "Expired" %}
</span> </span>
{% endif %} {% endif %}
</td> </td>
</tr> </tr>
{% endfor %} {% endfor %} {% else %}
{% else %} <tr>
<tr> <td>
<td> <button type="button" class="btn btn-sm btn-phoenix-success" data-bs-toggle="modal" data-bs-target="#reserveModal">
<button type="button" {% trans 'Reserve' %}
class="btn btn-sm btn-phoenix-success" </button>
data-bs-toggle="modal"
data-bs-target="#reserveModal"> </td>
{% trans 'Reserve' %} </tr>
</button> </tbody>
{% endif %} {% endif %}
</td>
</tr>
</tbody>
</table> </table>
<div class="table-responsive">
</div>
</div>
</div> </div>
</div> </div>
</div>
</div>
</div>
</div> </div>
<!-- Custom Card Modal -->
<div class="modal fade" id="customCardModal" tabindex="-1" aria-labelledby="customCardModalLabel" aria-hidden="true">
<div class="modal-dialog modal-sm">
<div class="modal-content">
<div class="modal-header bg-primary">
<h5 class="modal-title text-light" id="customCardModalLabel">{% trans 'Custom Card' %}</h5>
<button type="button" class="btn-close" data-bs-dismiss="modal" aria-label="Close"></button>
</div>
<div class="modal-body">
<!-- Content will be loaded here via AJAX -->
</div>
</div>
</div>
</div>
<!-- Reservation Modal -->
<div class="modal fade" id="reserveModal" tabindex="-1" aria-labelledby="reserveModalLabel" aria-hidden="true">
<div class="modal-dialog modal-sm">
<div class="modal-content">
<div class="modal-header bg-primary">
<h5 class="modal-title text-light" id="reserveModalLabel">{% trans 'Car Reservation' %}</h5>
<button type="button" class="btn-close" data-bs-dismiss="modal" aria-label="Close"></button>
</div>
<div class="modal-body">
{% trans 'Are you sure you want to reserve this car?' %}
</div>
<div class="modal-footer">
<button type="button" class="btn btn-sm btn-phoenix-danger" data-bs-dismiss="modal">
{% trans 'No' %}
</button>
<form method="POST" action="{% url 'reserve_car' car.id %}" class="d-inline">
{% csrf_token %}
<button type="submit" class="btn btn-phoenix-success btn-sm">{% trans "Yes" %}</button>
</form>
</div>
</div>
</div>
</div>
<!-- Specification Modal -->
<div class="modal fade" id="specificationsModal" tabindex="-1" aria-labelledby="specificationsModalLabel" aria-hidden="true">
<div class="modal-dialog modal-lg modal-dialog-scrollable">
<div class="modal-content">
<div class="modal-header">
<h5 class="modal-title" id="specificationsModalLabel">{% trans 'specifications'|upper %}</h5>
<button class="btn btn-close p-1" type="button" data-bs-dismiss="modal" aria-label="Close"></button>
</div>
<div class="modal-body">
<div id="specificationsContent"></div>
</div>
<div class="modal-footer">
<button class="btn btn-phoenix-primary" type="button" data-bs-dismiss="modal">{% trans 'Close' %}</button>
</div>
</div>
</div>
</div> </div>
<script> <script>
document.addEventListener("DOMContentLoaded", function () { document.addEventListener("DOMContentLoaded", function () {
@ -403,7 +352,7 @@
return cookieValue; return cookieValue;
} }
const csrfToken = getCookie("csrftoken");
const ajaxUrl = "{% url 'ajax_handler' %}"; const ajaxUrl = "{% url 'ajax_handler' %}";
const modal = document.getElementById("customCardModal"); const modal = document.getElementById("customCardModal");
@ -436,7 +385,7 @@
fetch(`${ajaxUrl}?action=get_specifications&trim_id={{ car.id_car_trim.id_car_trim }}`, { fetch(`${ajaxUrl}?action=get_specifications&trim_id={{ car.id_car_trim.id_car_trim }}`, {
headers: { headers: {
"X-Requested-With": "XMLHttpRequest", "X-Requested-With": "XMLHttpRequest",
"X-CSRFToken": {% csrf_token %}, "X-CSRFToken": csrfToken,
}, },
}) })
.then((response) => response.json()) .then((response) => response.json())
@ -472,7 +421,7 @@
const response = await fetch(`{% url 'reserve_car' car.pk %}`, { const response = await fetch(`{% url 'reserve_car' car.pk %}`, {
method: "POST", method: "POST",
headers: { headers: {
"X-CSRFToken": {% csrf_token %}, "X-CSRFToken": csrfToken,
"X-Requested-With": "XMLHttpRequest", "X-Requested-With": "XMLHttpRequest",
}, },
}); });

View File

@ -1,172 +1,160 @@
{% extends 'base.html' %} {% extends 'base.html' %}
{% load i18n static%} {% load i18n static %}
{% block title %} {% block title %}
{% trans 'inventory'|capfirst %} {% trans 'inventory'|capfirst %}
{% endblock %} {% endblock %}
{% block inventory %} {% block inventory %}
<a class="nav-link active fw-bold">{% trans "inventory" %}<span class="visually-hidden">(current)</span></a> <a class="nav-link active fw-bold">{% trans "inventory" %}<span class="visually-hidden">(current)</span></a>
{% endblock %} {% endblock %}
{% block content %} {% block content %}
<style> <div class="container">
.color-div { <div class="row g-3 justify-content-between">
width: 22px; <div class="col-sm-12">
height: 22px; <div class="card border h-100 w-100 overflow-hidden">
border-radius: 50%; <div class="bg-holder d-block bg-card" style="background-image:url({% static 'images/spot-illustrations/32.png' %});background-position: top right;">
border: 1px solid #ccc; </div>
text-align: center; <div class="d-dark-none">
vertical-align: middle; <div class="bg-holder d-none d-sm-block d-xl-none d-xxl-block bg-card" style="background-image:url({% static 'images/spot-illustrations/dark_21.png' %});background-position: bottom right; background-size: auto;">
line-height: 22px; </div>
} </div>
</style> <div class="d-light-none">
<div class="container"> <div class="bg-holder d-none d-sm-block d-xl-none d-xxl-block bg-card" style="background-image:url({% static 'images/spot-illustrations/21.png' %});background-position: bottom right; background-size: auto;">
<div class="row g-3 justify-content-between mb-4"> </div>
<div class="col-sm-12"> </div>
<!----> <div class="card-body px-lg-5 position-relative">
<div class="card border h-100 w-100 overflow-hidden"> <br><br><h3 class="mb-2">{{ cars.first.id_car_make.get_local_name }}<span class="ms-2 text-body-tertiary fw-semibold">{{ cars.first.id_car_model.get_local_name }}</span></h3>
<div class="bg-holder d-block bg-card" style="background-image:url({% static 'images/spot-illustrations/32.png' %});background-position: top right;"> <p class="text-body-tertiary fw-semibold">{{ cars.first.id_car_serie.name }}, <span class="fs-10">{{ cars.first.id_car_trim.name }}</span></p>
</div>
</div> </div>
<!--/.bg-holder--> </div>
</div>
<div class="d-dark-none"> <div class="row g-3 justify-content-between mt-4">
<div class="bg-holder d-none d-sm-block d-xl-none d-xxl-block bg-card" style="background-image:url({% static 'images/spot-illustrations/dark_21.png' %});background-position: bottom right; background-size: auto;"> <div class="col-sm-12">
</div> <div class="table-list" id="inventoryTable">
<!--/.bg-holder--> <div class="table-responsive scrollbar mb-3">
<table class="table table-sm fs-9 mb-0 overflow-hidden">
</div> <thead class="text-body">
<div class="d-light-none">
<div class="bg-holder d-none d-sm-block d-xl-none d-xxl-block bg-card" style="background-image:url({% static 'images/spot-illustrations/21.png' %});background-position: bottom right; background-size: auto;">
</div>
<!--/.bg-holder-->
</div>
<div class="card-body px-5 position-relative">
<div class="badge badge-phoenix fs-10 badge-phoenix-warning mb-2">{{ _("Year") }}<span class="ms-1 fw-bold">{{ cars.first.year }}</span></div>
<h3 class="mb-2">{{ cars.first.id_car_make.get_local_name }}<span class="ms-2 text-body-tertiary fw-semibold">{{ cars.first.id_car_model.get_local_name }}</span></h3>
</div>
<div class="card-footer border-0 py-0 px-5 z-1">
<p class="text-body-tertiary fw-semibold">{{ cars.first.id_car_serie.name }}, <span class="fs-10">{{ cars.first.id_car_trim.name }}</span></p>
</div>
</div>
<!---->
</div>
</div>
<div class="row g-3 justify-content-between mb-4">
<div class="col-sm-12">
<div class="table-responsive scrollbar mx-n1 px-1">
<table class="table fs-9 mb-0 leads-table border-top border-translucent">
<thead>
<tr> <tr>
<th>{% trans "VIN" %}</th> <th class="pe-1 align-middle white-space-nowrap text-center" data-sort="stock">{% trans 'Stock' %}</th>
<th>{% trans "Year" %}</th> <th class="pe-1 align-middle white-space-nowrap text-start" data-sort="vin" style="min-width: 4.5rem;">{% trans "VIN" %}</th>
<th>{% trans 'Exterior Color' %}</th> <th class="pe-1 align-middle white-space-nowrap text-center" data-sort="date">{% trans "Year"|upper %}</th>
<th>{% trans 'Interior Color' %}</th> <th class="pe-1 align-middle white-space-nowrap text-center" data-sort="extColor" style="min-width: 8.5rem">{% trans 'Exterior Color'|upper %}</th>
<th>{% trans "Showroom Location" %}</th> <th class="pe-1 align-middle white-space-nowrap text-center" data-sort="intColor" style="min-width: 8.5rem">{% trans 'Interior Color'|upper %}</th>
<th>{% trans "Actions" %}</th> <th class="pe-1 align-middle white-space-nowrap text-center" data-sort="location" style="min-width: 12.5rem;">{% trans "Showroom Location"|upper %}</th>
<th class="pe-1 align-middle white-space-nowrap text-center" data-sort="status">{% trans 'Status'|upper %}</th>
<th class="pe-1 align-middle white-space-nowrap text-center" data-sort="age" style="min-width: 7rem">{% trans 'Age'|upper %}</th>
<th class="no-sort align-middle white-space-nowrap text-center"></th>
</tr> </tr>
</thead> </thead>
<tbody> <tbody>
{% for car in cars %} {% for car in cars %}
<tr class="{% if car.is_reserved %}table-danger{% endif %}"> <tr>
<td>{{ car.vin }}</td> <td class="align-middle white-space-nowrap text-center fw-bold text-body-tertiary">
<td>{{ car.year }}</td> {% if car.stock_type == "new" %}
<span class="badge badge-phoenix fs-10 badge-phoenix-success"><span class="badge-label">{{_("New")}}</span><span class="ms-1" data-feather="plus" style="height:13px;width:13px;"></span></span>
{% elif car.status == "used" %}
<span class="badge badge-phoenix fs-10 badge-phoenix-info"><span class="badge-label">{{_("Used")}}</span><span class="ms-1" data-feather="plus" style="height:13px;width:13px;"></span></span>
{% endif %}
</td>
<td class="align-middle white-space-nowrap text-start fw-bold">{{ car.vin }}</td>
<td class="align-middle white-space-nowrap text-center fw-bold">{{ car.year }}</td>
{% if car.colors.exists %} {% if car.colors.exists %}
<td> <td class="align-middle white-space-nowrap text-body fs-9 text-start">
<div class="d-flex flex-column align-items-center"> <div class="d-flex flex-column align-items-center">
<span class="color-div" <span class="color-div" style="background-color: rgb({{ car.colors.first.exterior.rgb }});" title="{{ car.colors.first.exterior.get_local_name }}"></span><span>{{ car.colors.first.exterior.get_local_name }}</span>
style="background-color: rgb({{ car.colors.first.exterior.rgb }});" </div>
title="{{ car.colors.first.exterior.get_local_name }}"> </td>
</span> <td class="align-middle white-space-nowrap text-body fs-9 text-start">
</div> <div class="d-flex flex-column align-items-center">
</td> <span class="color-div" style="background-color: rgb({{ car.colors.first.interior.rgb }});" title="{{ car.colors.first.interior.get_local_name }}"></span><span>{{ car.colors.first.interior.get_local_name }}</span>
<td> </div>
<div class="d-flex flex-column align-items-center"> </td>
<span class="color-div"
style="background-color: rgb({{ car.colors.first.interior.rgb }});"
title="{{ car.colors.first.interior.get_local_name }}">
</span>
</div>
</td>
{% else %} {% else %}
<td><span class="color-div">{% trans 'No Color' %}</span></td> <td class="align-middle white-space-nowrap text-body fs-9 text-center"><span class="color-div">{% trans 'No Color' %}</span></td>
<td><span class="color-div">{% trans 'No Color' %}</span></td> <td class="align-middle white-space-nowrap text-body fs-9 text-center"><span class="color-div">{% trans 'No Color' %}</span></td>
{% endif %} {% endif %}
{% if car.location.is_owner_showroom %} <td class="align-middle white-space-nowrap text-body fs-9 text-center">
<td> {% trans 'Our Showroom' %}</td> {% if car.location.is_owner_showroom %}
{% trans 'Our Showroom' %}
{% else %} {% else %}
<td>{{ car.location.showroom.get_local_name }}</td> {{ car.location.showroom.get_local_name }}
{% endif %} {% endif %}
<td> </td>
<a href="{% url 'car_detail' car.pk %}" class="btn btn-success"> <td class="status align-middle white-space-nowrap text-center fw-bold text-body-tertiary">
{% trans "view"|capfirst %} {% if car.status == "available" %}
</a> <span class="badge badge-phoenix fs-10 badge-phoenix-success"><span class="badge-label">{{_("Available")}}</span><span class="ms-1" data-feather="check" style="height:13px;width:13px;"></span></span>
{% elif car.status == "sold" %}
<span class="badge badge-phoenix fs-10 badge-phoenix-primary"><span class="badge-label">{{_("Sold")}}</span><span class="ms-1" data-feather="package" style="height:13px;width:13px;"></span></span>
{% elif car.status == "hold" %}
<span class="badge badge-phoenix fs-10 badge-phoenix-warning"><span class="badge-label">{{ _("Hold") }}</span><span class="ms-1" data-feather="alert-octagon" style="height:13px;width:13px;"></span></span>
{% elif car.status == "reserved" %}
<span class="badge badge-phoenix fs-10 badge-phoenix-danger"><span class="badge-label">{{ _("Reserved") }}</span><span class="ms-1" data-feather="info" style="height:13px;width:13px;"></span></span>
{% elif car.status == "damaged" %}
<span class="badge badge-phoenix fs-10 badge-phoenix-secondary"><span class="badge-label">{{ _("Damaged") }}</span><span class="ms-1" data-feather="x" style="height:13px;width:13px;"></span></span>
{% endif %}
</td>
<td class="date align-middle white-space-nowrap text-body-tertiary fs-9 ps-4 text-start">
<span class="fw-light">{{ car.receiving_date|timesince }}</span>
</td>
<td class="align-middle white-space-nowrap text-end pe-0 ps-4">
<div class="btn-reveal-trigger position-static">
<button class="btn btn-sm dropdown-toggle dropdown-caret-none transition-none btn-reveal fs-10" type="button" data-bs-toggle="dropdown" data-boundary="window" aria-haspopup="true" aria-expanded="false" data-bs-reference="parent"><span class="fas fa-ellipsis-h fs-10"></span></button>
<div class="dropdown-menu dropdown-menu-end py-2"><a class="dropdown-item" href="{% url 'car_detail' car.pk %}">{% trans "view"|capfirst %}</a>
<div class="dropdown-divider"></div><a class="dropdown-item text-danger" href="#!">Remove</a>
</div>
</div>
</td> </td>
</tr> </tr>
{% empty %} {% empty %}
<tr> <tr>
<td colspan="6">{% trans "No cars available." %}</td> <td colspan="7" class="d-flex flex-column align-items-center">
</tr> <p class="text-muted">{% trans "No cars available." %}</p>
<a href="{% url 'add_car' %}" class="btn btn-primary">{% trans "Add a Car" %}</a>
</td>
</tr>
{% endfor %} {% endfor %}
</tbody> </tbody>
</table> </table>
</div>
</div>
<!-- Pagination --> <!-- Pagination -->
{% if is_paginated %} {% if is_paginated %}
<nav aria-label="Page navigation"> <nav aria-label="Page navigation">
<ul class="pagination pagination-sm justify-content-center"> <ul class="pagination pagination-sm justify-content-center">
{% if page_obj.has_previous %} {% if page_obj.has_previous %}
<li class="page-item"> <li class="page-item">
<a class="page-link" href="?page=1" aria-label="First"> <a class="page-link" href="?page=1" aria-label="First">&laquo;&laquo;</a>
&laquo;&laquo; </li>
</a> <li class="page-item">
</li> <a class="page-link" href="?page={{ page_obj.previous_page_number }}" aria-label="Previous">&laquo;</a>
<li class="page-item"> </li>
<a class="page-link" href="?page={{ page_obj.previous_page_number }}" aria-label="Previous"> {% endif %}
&laquo; {% for num in page_obj.paginator.page_range %}
</a> {% if page_obj.number == num %}
</li> <li class="page-item active">
{% else %} <span class="page-link">{{ num }}</span>
<li class="page-item disabled"> </li>
<span class="page-link">&laquo;&laquo;</span> {% elif num > page_obj.number|add:'-3' and num < page_obj.number|add:'3' %}
</li> <li class="page-item">
<li class="page-item disabled"> <a class="page-link" href="?page={{ num }}">{{ num }}</a>
<span class="page-link">&laquo;</span> </li>
</li> {% endif %}
{% endif %} {% endfor %}
{% for num in page_obj.paginator.page_range %} {% if page_obj.has_next %}
{% if page_obj.number == num %} <li class="page-item">
<li class="page-item active"> <a class="page-link" href="?page={{ page_obj.next_page_number }}" aria-label="Next">&raquo;</a>
<span class="page-link">{{ num }}</span> </li>
</li> <li class="page-item">
{% elif num > page_obj.number|add:'-3' and num < page_obj.number|add:'3' %} <a class="page-link" href="?page={{ page_obj.paginator.num_pages }}" aria-label="Last">&raquo;&raquo;</a>
<li class="page-item"> </li>
<a class="page-link" href="?page={{ num }}">{{ num }}</a> {% endif %}
</li> </ul>
{% endif %} </nav>
{% endfor %}
{% if page_obj.has_next %}
<li class="page-item">
<a class="page-link" href="?page={{ page_obj.next_page_number }}" aria-label="Next">
&raquo;
</a>
</li>
<li class="page-item">
<a class="page-link" href="?page={{ page_obj.paginator.num_pages }}" aria-label="Last">
&raquo;&raquo;
</a>
</li>
{% else %}
<li class="page-item disabled">
<span class="page-link">&raquo;</span>
</li>
<li class="page-item disabled">
<span class="page-link">&raquo;&raquo;</span>
</li>
{% endif %}
</ul>
</nav>
{% endif %} {% endif %}
</div> </div>
</div> </div>
</div> </div>
</div>
{% endblock %} {% endblock %}

View File

@ -0,0 +1 @@
{% load static %}

137
templates/test.html Normal file
View File

@ -0,0 +1,137 @@
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Car Inventory</title>
<!-- Bootstrap CSS -->
<link href="https://cdn.jsdelivr.net/npm/bootstrap@5.3.0/dist/css/bootstrap.min.css" rel="stylesheet">
<!-- Font Awesome for Icons -->
<link href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/6.0.0-beta3/css/all.min.css" rel="stylesheet">
<!-- Custom CSS -->
<style>
.color-circle {
width: 22px;
height: 22px;
border-radius: 50%;
border: 1px solid #ccc;
display: inline-block;
}
.table-hover tbody tr:hover {
background-color: #f8f9fa;
}
.badge {
font-size: 0.9rem;
}
.table-responsive {
overflow-x: auto;
}
.table thead th {
background-color: #007bff;
color: white;
}
.table tbody td {
vertical-align: middle;
}
</style>
</head>
<body>
<div class="container my-5">
<h1 class="text-center mb-4">Car Inventory</h1>
<!-- Search and Filter Section -->
<div class="row mb-4 g-3">
<div class="col-md-6">
<div class="input-group">
<span class="input-group-text"><i class="fas fa-search"></i></span>
<input type="text" class="form-control" placeholder="Search by VIN, Make, or Model">
</div>
</div>
<div class="col-md-3">
<select class="form-select">
<option>All Years</option>
<option>2023</option>
<option>2022</option>
<option>2021</option>
</select>
</div>
<div class="col-md-3">
<button class="btn btn-primary w-100"><i class="fas fa-filter"></i> Filter</button>
</div>
</div>
<!-- Car Listing Table -->
<div class="table-responsive">
<table class="table table-hover table-bordered">
<thead>
<tr>
<th>#</th>
<th>Image</th>
<th>Make & Model</th>
<th>Year</th>
<th>VIN</th>
<th>Exterior Color</th>
<th>Interior Color</th>
<th>Location</th>
<th>Status</th>
<th>Actions</th>
</tr>
</thead>
<tbody>
<!-- Example Row -->
<tr>
<td>1</td>
<td><img src="https://via.placeholder.com/100" alt="Car Image" class="img-thumbnail" style="width: 100px;"></td>
<td>Toyota Camry</td>
<td>2023</td>
<td>1HGCM82633A123456</td>
<td><span class="color-circle" style="background-color: #0000ff;"></span> Blue</td>
<td><span class="color-circle" style="background-color: #ffffff;"></span> White</td>
<td>Main Showroom</td>
<td><span class="badge bg-success">Available</span></td>
<td>
<a href="#" class="btn btn-sm btn-outline-primary"><i class="fas fa-eye"></i> View</a>
<a href="#" class="btn btn-sm btn-outline-warning"><i class="fas fa-edit"></i> Edit</a>
</td>
</tr>
<!-- Add more rows here -->
<tr>
<td>2</td>
<td><img src="https://via.placeholder.com/100" alt="Car Image" class="img-thumbnail" style="width: 100px;"></td>
<td>Honda Accord</td>
<td>2022</td>
<td>1HGCM82633A654321</td>
<td><span class="color-circle" style="background-color: #ff0000;"></span> Red</td>
<td><span class="color-circle" style="background-color: #000000;"></span> Black</td>
<td>Downtown Showroom</td>
<td><span class="badge bg-warning">Reserved</span></td>
<td>
<a href="#" class="btn btn-sm btn-outline-primary"><i class="fas fa-eye"></i> View</a>
<a href="#" class="btn btn-sm btn-outline-warning"><i class="fas fa-edit"></i> Edit</a>
</td>
</tr>
</tbody>
</table>
</div>
<!-- Pagination -->
<nav aria-label="Page navigation" class="mt-4">
<ul class="pagination justify-content-center">
<li class="page-item disabled">
<a class="page-link" href="#" tabindex="-1" aria-disabled="true"><i class="fas fa-chevron-left"></i></a>
</li>
<li class="page-item active"><a class="page-link" href="#">1</a></li>
<li class="page-item"><a class="page-link" href="#">2</a></li>
<li class="page-item"><a class="page-link" href="#">3</a></li>
<li class="page-item">
<a class="page-link" href="#"><i class="fas fa-chevron-right"></i></a>
</li>
</ul>
</nav>
</div>
<!-- Bootstrap JS -->
<script src="https://cdn.jsdelivr.net/npm/bootstrap@5.3.0/dist/js/bootstrap.bundle.min.js"></script>
</body>
</html>