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",
"LNP": "NAC MG",
"LNY": "Yuejin",
"LMX": "Dongfeng Forthing",
"LPA": "Changan",
"LPE": "BYD",
"LPS": "Polestar",
@ -346,9 +347,7 @@ wmi_manufacturer_mapping = {
"MCG": "Atul",
"MC1": "Force",
"MC2": "Eicher",
"MDE": "Kinetic Engineering Limited",
"MDH": "Nissan",
"MDT": "Kerala Limited",
"MD6": "TVS",
"MD9": "Shuttles",
"MEC": "Daimler",
@ -358,8 +357,6 @@ wmi_manufacturer_mapping = {
"MET": "Piaggio",
"MEX": "Škoda",
"ME1": "Yamaha",
"ME3": "Royal Enfield",
"MYH": "Ather Energy",
"MZB": "Kia",
"MZZ": "Citroën",
"MZ7": "MG",
@ -1501,7 +1498,7 @@ def decode_vds(manufacturer, vds):
return "Unknown Model"
def decode_vin(vin):
def decode_vin_haikalna(vin):
if len(vin) != 17:
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)
year_code = vis[0]
year = vin_years(year_code)
year = vin_years(year_code)[0]
model = decode_vds(manufacturer, vds)
return {
'VIN': vin,
'Manufacturer': manufacturer,
'Year': year,
'Model': model
data = {
'maker': manufacturer,
'model': model,
'modelYear': year,
}
return data
# JTDKB3FU4G3507653
# VR3USHNLWRJ521303
# KNARH81E8P5194005
# Example usage
vin_number = 'VYFED9HP0SJ519559'
decoded_vin = decode_vin(vin_number)
print(decoded_vin)
# vin_number = 'LMXA14AF7PZ356070'
# decoded_vin = decode_vin_haikalna(vin_number)
# 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
@ -7,7 +8,7 @@ class Migration(migrations.Migration):
dependencies = [
('django_ledger', '0017_alter_accountmodel_unique_together_and_more'),
('inventory', '0022_rename_log_oportunitylog'),
('inventory', '0022_remove_opportunitylog_user_opportunitylog_staff_and_more'),
]
operations = [
@ -17,7 +18,8 @@ class Migration(migrations.Migration):
),
migrations.AddField(
model_name='carfinance',
name='services',
field=models.ManyToManyField(blank=True, related_name='services', to='django_ledger.itemmodel'),
name='additional_services',
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
@ -13,7 +13,7 @@ class Migration(migrations.Migration):
operations = [
migrations.RemoveField(
model_name='carfinance',
name='services',
name='additional_services',
),
migrations.AddField(
model_name='carfinance',

View File

@ -154,7 +154,6 @@ class CarSpecificationValue(models.Model):
verbose_name = "Specification Value"
# Car Model
class CarStatusChoices(models.TextChoices):
AVAILABLE = "available", _("Available")
SOLD = "sold", _("Sold")
@ -184,6 +183,7 @@ class AdditionalServices(models.Model, LocalizedNameMixin):
def __str__(self):
return self.name + " - " + str(self.price)
class Car(models.Model):
vin = models.CharField(max_length=17, unique=True, verbose_name=_("VIN"))
dealer = models.ForeignKey(
@ -308,6 +308,7 @@ class CarReservation(models.Model):
verbose_name = _("Car Reservation")
verbose_name_plural = _("Car Reservations")
# Car Finance Model
class CarFinance(models.Model):
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})"
# Colors Model
class CarColors(models.Model):
car = models.ForeignKey("Car", on_delete=models.CASCADE, related_name="colors")
exterior = models.ForeignKey(
@ -408,7 +408,6 @@ class CarColors(models.Model):
return f"{self.car} ({self.exterior.name}) ({self.interior.name})"
# Custom Card Model
class CustomCard(models.Model):
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"))
@ -471,7 +470,7 @@ class CarLocation(models.Model):
"""
return self.owner == self.showroom
# Car Registration Model
class CarRegistration(models.Model):
car = models.ForeignKey(
Car,
@ -501,7 +500,7 @@ class TimestampedModel(models.Model):
class Meta:
abstract = True
#subscription
class Subscription(models.Model):
plan = models.CharField(max_length=255) # e.g. "basic", "premium"
start_date = models.DateField()
@ -529,7 +528,6 @@ class SubscriptionPlan(models.Model):
return f"{self.name} - {self.price}"
# Dealer Model
class Dealer(models.Model, LocalizedNameMixin):
user = models.OneToOneField(User, on_delete=models.CASCADE, related_name="dealer")
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
class STAFF_TYPES(models.TextChoices):
class StaffTypes(models.TextChoices):
MANAGER = "manager", _("Manager")
INVENTORY = "inventory", _("Inventory")
ACCOUNTANT = "accountant", _("Accountant")
SALES = "sales", _("Sales")
##############################
# Additional staff types for later
# COORDINATOR = "coordinator", _("Coordinator")
# RECEPTIONIST = "receptionist", _("Receptionist")
# AGENT = "agent", _("Agent")
# TECHNICIAN = "technician", _("Technician")
# DRIVER = "driver", _("Driver")
##############################
class Staff(models.Model, LocalizedNameMixin):
@ -628,7 +629,7 @@ class Staff(models.Model, LocalizedNameMixin):
name = models.CharField(max_length=255, verbose_name=_("Name"))
arabic_name = models.CharField(max_length=255, verbose_name=_("Arabic Name"))
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"))
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()}"
# Vendor Model
class Vendor(models.Model, LocalizedNameMixin):
dealer = models.ForeignKey(Dealer, on_delete=models.CASCADE, related_name="vendors")
crn = models.CharField(
@ -671,7 +671,6 @@ class Vendor(models.Model, LocalizedNameMixin):
return self.name
# Customer Model
class Customer(models.Model):
dealer = models.ForeignKey(Dealer,
on_delete=models.CASCADE,
@ -932,6 +931,7 @@ class SaleQuotation(models.Model):
last_quotation_number = 0
return itertools.count(last_quotation_number + 1)
class SaleQuotationCar(models.Model):
quotation = models.ForeignKey(
SaleQuotation,

View File

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

View File

@ -30,3 +30,4 @@ def attr(field, args):
else:
attrs[definition.strip()] = True
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')),
#Dashboards
path('dashboards/accounting/', views.AccountingDashboard.as_view(), name='accounting'),
path('test/', views.TestView.as_view(), name='test'),
# Dealer URLs
path('dealers/<int:pk>/', views.DealerDetailView.as_view(), name='dealer_detail'),
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_
recipient_list = [to_]
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.messages.views import SuccessMessageMixin
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 allauth.account import views
from django.db.models import Count, F, Value
@ -166,6 +166,10 @@ class HomeView(TemplateView):
template_name = "index.html"
class TestView(TemplateView):
template_name = "test.html"
class AccountingDashboard(LoginRequiredMixin, TemplateView):
template_name = "dashboards/accounting.html"
@ -374,8 +378,9 @@ class CarInventory(LoginRequiredMixin, ListView):
model_id = self.kwargs["model_id"]
trim_id = self.kwargs["trim_id"]
dealer = get_user_type(self.request)
cars = models.Car.objects.filter(
dealer=self.request.user.dealer,
dealer=dealer,
id_car_make=make_id,
id_car_model=model_id,
id_car_trim=trim_id,
@ -415,7 +420,7 @@ class CarColorCreate(LoginRequiredMixin, CreateView):
@login_required
def inventory_stats_view(request):
dealer = request.user.dealer
dealer = get_user_type(request)
# Annotate total cars by make, model, and trim
cars = (
@ -692,7 +697,6 @@ class DealerUpdateView(LoginRequiredMixin, SuccessMessageMixin, UpdateView):
class CustomerListView(LoginRequiredMixin, PermissionRequiredMixin, ListView):
model = models.Customer
home_label = _("customers")
context_object_name = "customers"
paginate_by = 10
template_name = "customers/customer_list.html"
@ -700,17 +704,15 @@ class CustomerListView(LoginRequiredMixin, PermissionRequiredMixin, ListView):
def get_queryset(self):
query = self.request.GET.get("q")
if self.request.user.is_staff:
dealer = self.request.user.staff.dealer
customers = models.Customer.objects.filter(
dealer=dealer,
)
dealer = get_user_type(self.request)
customers = models.Customer.objects.filter(dealer=dealer)
if query:
customers = customers.filter(
Q(national_id__icontains=query)
| Q(first_name__icontains=query)
| Q(last_name__icontains=query)
Q(national_id__icontains=query) |
Q(first_name__icontains=query) |
Q(last_name__icontains=query)
)
return customers
@ -879,7 +881,7 @@ class QuotationDetailView(LoginRequiredMixin, PermissionRequiredMixin, DetailVie
@login_required
def generate_invoice(request, pk):
quotation = get_object_or_404(models.SaleQuotation, pk=pk)
dealer = request.user.dealer
dealer = get_user_type(request)
entity = dealer.entity
if not quotation.is_approved:
messages.error(
@ -1731,7 +1733,8 @@ class EstimateListView(LoginRequiredMixin, ListView):
# @csrf_exempt
@login_required
def create_estimate(request):
entity = request.entity
dealer = get_user_type(request)
entity = dealer.entity
if request.method == "POST":
try:
data = json.loads(request.body)
@ -1932,7 +1935,8 @@ class InvoiceListView(LoginRequiredMixin, ListView):
context_object_name = "invoices"
def get_queryset(self):
entity = self.request.user.dealer.entity
dealer = get_user_type(self.request)
entity = dealer.entity
return entity.get_invoices()
@ -2137,7 +2141,9 @@ def PaymentCreateView(request, pk=None):
def PaymentListView(request):
entity = request.user.dealer.entity
dealer = get_user_type(request)
entity = dealer.entity
journals = JournalEntryModel.objects.filter(ledger__entity=entity).all()
return render(request, "sales/payments/payment_list.html", {"journals": journals})

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');
};
// Selectors
const table = document.getElementById('opportunityTable');
const table = document.getElementById('inventoryTable');
if (table) {
const options = {

View File

@ -31,7 +31,7 @@
<link href="{% static 'vendors/simplebar/simplebar.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 href="{% static 'css/custom.css' %}" rel="stylesheet">
{% if LANGUAGE_CODE == 'en' %}
<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">
@ -39,6 +39,7 @@
<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">
{% endif %}
{% block extra_css %}{% endblock extra_css %}
</head>
<body>

View File

@ -1,9 +1,9 @@
{% extends 'base.html' %}
{% load static %}
{% load i18n %}
{% load custom_filters %}
{% load i18n static custom_filters %}
{% block title %}{{ _("Car Details") }}{% endblock %}
{% block content %}
<style>
.color-circle {
width: 32px;
@ -13,82 +13,16 @@
border-color: #fff;
}
</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 -->
<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>
<!-- Specification Modal -->
<!-- Main Container -->
<div class="row gx-6">
<div class="container-fluid">
<div class="row g-3 justify-content-between">
<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">
<div class="table-responsive scrollbar mb-3">
<table class="table table-sm fs-9 mb-0 overflow-hidden">
<tr>
<th>{% trans "VIN" %}</th>
<td>{{ car.vin }}</td>
@ -142,11 +76,7 @@
<tr>
<th>{% trans 'specifications'|capfirst %}</th>
<td>
<button type="button"
class="btn btn-phoenix-primary btn-sm"
id="specification-btn"
data-bs-toggle="modal"
data-bs-target="#specificationsModal">
<button type="button" class="btn btn-phoenix-primary btn-sm" id="specification-btn" data-bs-toggle="modal" data-bs-target="#specificationsModal">
{% trans 'view'|capfirst %}
</button>
</td>
@ -164,10 +94,7 @@
<tr>
<th>{% trans "Custom Card" %}</th>
<td>
<button type="button"
class="btn btn-sm btn-phoenix-success"
data-bs-toggle="modal"
data-bs-target="#customCardModal">
<button type="button" class="btn btn-sm btn-phoenix-success" data-bs-toggle="modal" data-bs-target="#customCardModal">
{% trans 'Add' %}
</button>
</td>
@ -176,19 +103,12 @@
<tr>
<th>{% trans 'Location'|capfirst %}</th>
<td>
{% if car.location %}
{% if car.location.is_owner_showroom %}
{% trans 'Our Showroom' %}
{% else %}
{{ car.location.showroom.get_local_name }}
{% endif %}
<a href="{% url 'transfer' car.location.pk %}"
class="btn btn-phoenix-danger btn-sm">
{% if car.location %} {% if car.location.is_owner_showroom %} {% trans 'Our Showroom' %} {% 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>
{% else %} {% trans "No location available." %}
<a href="{% url 'add_car_location' car.pk %}"
class="btn btn-phoenix-success btn-sm ms-2">
<a href="{% url 'add_car_location' car.pk %}" class="btn btn-phoenix-success btn-sm ms-2">
{% trans "Add" %}
</a>
</td>
@ -201,17 +121,15 @@
</div>
</div>
</div>
</div>
</div>
<div class="col-lg-6 col-xl-6">
<div class="card rounded shadow">
<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 %}
<div class="table-responsive">
<table class="table fs-9 mb-0">
{% if perms.inventory.view_carfinance %}
<tr>
<th>{% trans "Cost Price"|capfirst %}</th>
@ -249,16 +167,13 @@
<tr>
<td colspan="2">
{% if perms.inventory.change_carfinance %}
<a href="{% url 'car_finance_update' car.finances.pk %}"
class="btn btn-phoenix-warning btn-sm mb-3">
<a href="{% url 'car_finance_update' car.finances.pk %}" class="btn btn-phoenix-warning btn-sm mb-3">
{% trans "Edit" %}
</a>
{% endif %}
{% else %}
<p>{% trans "No finance details available." %}</p>
<a href="{% url 'car_finance_create' car.pk %}"
class="btn btn-phoenix-success btn-sm mb-3">
<a href="{% url 'car_finance_create' car.pk %}" class="btn btn-phoenix-success btn-sm mb-3">
{% trans "Add" %}
</a>
</td>
@ -268,12 +183,11 @@
</div>
</div>
</div>
<div class="card rounded shadow mt-3">
<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">
<table class="table fs-9 mb-0">
<tbody class="align-middle">
<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>
@ -282,9 +196,7 @@
<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>
<div class="text-end color-circle" style="background-color: rgb({{ color.exterior.rgb }});"></div>
</td>
</tr>
<tr>
@ -293,13 +205,10 @@
<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>
<div class="text-end color-circle" style="background-color: rgb({{ color.interior.rgb }});"></div>
</td>
</tr>
{% endfor %}
{% else %}
{% endfor %} {% else %}
<tr>
<td colspan="2">
{% trans "No colors available for this car." %}
@ -313,18 +222,16 @@
</td>
</tr>
{% endif %}
</tbody>
</table>
</div>
</div>
</div>
<div class="card rounded shadow mt-3">
<div class="card rounded shadow d-flex align-content-center mt-3">
<p class="card-header rounded-top fw-bold">{% trans 'Reservations Details' %}</p>
<div class="card-body">
<div class="table-responsive">
<div class="table-responsive scrollbar mb-3">
<table class="table table-sm fs-9 mb-0 overflow-hidden">
{% if car.is_reserved %}
<table class="table fs-9 mb-0">
<thead>
<tr>
<th>{% trans "Reserved By" %}</th>
@ -341,50 +248,92 @@
{% if reservation.is_active %}
<form method="post" action="{% url 'reservations' reservation.id %}">
{% csrf_token %}
<button type="submit"
name="action"
value="renew"
class="btn btn-sm btn-phoenix-success">
<button type="submit" name="action" value="renew" class="btn btn-sm btn-phoenix-success">
{% trans "Renew" %}
</button>
<button type="submit"
name="action"
value="cancel"
class="btn btn-sm btn-phoenix-secondary">
<button type="submit" name="action" value="cancel" class="btn btn-sm btn-phoenix-secondary">
{% trans "Cancel" %}
</button>
</form>
{% else %}
<span class="badge bg-danger"
style="width: 120px;">
<span class="badge bg-danger" style="width: 120px;">
{% trans "Expired" %}
</span>
{% endif %}
</td>
</tr>
{% endfor %}
{% else %}
{% endfor %} {% else %}
<tr>
<td>
<button type="button"
class="btn btn-sm btn-phoenix-success"
data-bs-toggle="modal"
data-bs-target="#reserveModal">
<button type="button" class="btn btn-sm btn-phoenix-success" data-bs-toggle="modal" data-bs-target="#reserveModal">
{% trans 'Reserve' %}
</button>
{% endif %}
</td>
</tr>
</tbody>
{% endif %}
</table>
<div class="table-responsive">
</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>
<script>
document.addEventListener("DOMContentLoaded", function () {
@ -403,7 +352,7 @@
return cookieValue;
}
const csrfToken = getCookie("csrftoken");
const ajaxUrl = "{% url 'ajax_handler' %}";
const modal = document.getElementById("customCardModal");
@ -436,7 +385,7 @@
fetch(`${ajaxUrl}?action=get_specifications&trim_id={{ car.id_car_trim.id_car_trim }}`, {
headers: {
"X-Requested-With": "XMLHttpRequest",
"X-CSRFToken": {% csrf_token %},
"X-CSRFToken": csrfToken,
},
})
.then((response) => response.json())
@ -472,7 +421,7 @@
const response = await fetch(`{% url 'reserve_car' car.pk %}`, {
method: "POST",
headers: {
"X-CSRFToken": {% csrf_token %},
"X-CSRFToken": csrfToken,
"X-Requested-With": "XMLHttpRequest",
},
});

View File

@ -1,135 +1,135 @@
{% extends 'base.html' %}
{% load i18n static %}
{% block title %}
{% trans 'inventory'|capfirst %}
{% endblock %}
{% block inventory %}
<a class="nav-link active fw-bold">{% trans "inventory" %}<span class="visually-hidden">(current)</span></a>
{% endblock %}
{% block content %}
<style>
.color-div {
width: 22px;
height: 22px;
border-radius: 50%;
border: 1px solid #ccc;
text-align: center;
vertical-align: middle;
line-height: 22px;
}
</style>
<div class="container">
<div class="row g-3 justify-content-between mb-4">
<div class="row g-3 justify-content-between">
<div class="col-sm-12">
<!---->
<div class="card border h-100 w-100 overflow-hidden">
<div class="bg-holder d-block bg-card" style="background-image:url({% static 'images/spot-illustrations/32.png' %});background-position: top right;">
</div>
<!--/.bg-holder-->
<div class="d-dark-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/dark_21.png' %});background-position: bottom right; background-size: auto;">
</div>
<!--/.bg-holder-->
</div>
<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">
<div class="card-body px-lg-5 position-relative">
<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>
<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="row g-3 justify-content-between mt-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>
<div class="table-list" id="inventoryTable">
<div class="table-responsive scrollbar mb-3">
<table class="table table-sm fs-9 mb-0 overflow-hidden">
<thead class="text-body">
<tr>
<th>{% trans "VIN" %}</th>
<th>{% trans "Year" %}</th>
<th>{% trans 'Exterior Color' %}</th>
<th>{% trans 'Interior Color' %}</th>
<th>{% trans "Showroom Location" %}</th>
<th>{% trans "Actions" %}</th>
<th class="pe-1 align-middle white-space-nowrap text-center" data-sort="stock">{% trans 'Stock' %}</th>
<th class="pe-1 align-middle white-space-nowrap text-start" data-sort="vin" style="min-width: 4.5rem;">{% trans "VIN" %}</th>
<th class="pe-1 align-middle white-space-nowrap text-center" data-sort="date">{% trans "Year"|upper %}</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 class="pe-1 align-middle white-space-nowrap text-center" data-sort="intColor" style="min-width: 8.5rem">{% trans 'Interior Color'|upper %}</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>
</thead>
<tbody>
{% for car in cars %}
<tr class="{% if car.is_reserved %}table-danger{% endif %}">
<td>{{ car.vin }}</td>
<td>{{ car.year }}</td>
<tr>
<td class="align-middle white-space-nowrap text-center fw-bold text-body-tertiary">
{% 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 %}
<td>
<td class="align-middle white-space-nowrap text-body fs-9 text-start">
<div class="d-flex flex-column align-items-center">
<span class="color-div"
style="background-color: rgb({{ car.colors.first.exterior.rgb }});"
title="{{ car.colors.first.exterior.get_local_name }}">
</span>
<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>
</div>
</td>
<td>
<td class="align-middle white-space-nowrap text-body fs-9 text-start">
<div class="d-flex flex-column align-items-center">
<span class="color-div"
style="background-color: rgb({{ car.colors.first.interior.rgb }});"
title="{{ car.colors.first.interior.get_local_name }}">
</span>
<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>
</div>
</td>
{% else %}
<td><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>
<td class="align-middle white-space-nowrap text-body fs-9 text-center"><span class="color-div">{% trans 'No Color' %}</span></td>
{% endif %}
<td class="align-middle white-space-nowrap text-body fs-9 text-center">
{% if car.location.is_owner_showroom %}
<td> {% trans 'Our Showroom' %}</td>
{% trans 'Our Showroom' %}
{% else %}
<td>{{ car.location.showroom.get_local_name }}</td>
{{ car.location.showroom.get_local_name }}
{% endif %}
<td>
<a href="{% url 'car_detail' car.pk %}" class="btn btn-success">
{% trans "view"|capfirst %}
</a>
</td>
<td class="status align-middle white-space-nowrap text-center fw-bold text-body-tertiary">
{% if car.status == "available" %}
<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>
</tr>
{% empty %}
<tr>
<td colspan="6">{% trans "No cars available." %}</td>
<td colspan="7" class="d-flex flex-column align-items-center">
<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 %}
</tbody>
</table>
</div>
</div>
<!-- Pagination -->
{% if is_paginated %}
<nav aria-label="Page navigation">
<ul class="pagination pagination-sm justify-content-center">
{% if page_obj.has_previous %}
<li class="page-item">
<a class="page-link" href="?page=1" aria-label="First">
&laquo;&laquo;
</a>
<a class="page-link" href="?page=1" aria-label="First">&laquo;&laquo;</a>
</li>
<li class="page-item">
<a class="page-link" href="?page={{ page_obj.previous_page_number }}" aria-label="Previous">
&laquo;
</a>
</li>
{% else %}
<li class="page-item disabled">
<span class="page-link">&laquo;&laquo;</span>
</li>
<li class="page-item disabled">
<span class="page-link">&laquo;</span>
<a class="page-link" href="?page={{ page_obj.previous_page_number }}" aria-label="Previous">&laquo;</a>
</li>
{% endif %}
{% for num in page_obj.paginator.page_range %}
@ -145,21 +145,10 @@
{% 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>
<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>
<a class="page-link" href="?page={{ page_obj.paginator.num_pages }}" aria-label="Last">&raquo;&raquo;</a>
</li>
{% endif %}
</ul>
@ -168,5 +157,4 @@
</div>
</div>
</div>
</div>
{% 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>