This commit is contained in:
Marwan Alwali 2025-02-16 11:48:12 +03:00
parent dbf4c6f91d
commit 692fa3f0ed
26 changed files with 349 additions and 128 deletions

View File

@ -33,6 +33,7 @@
<orderEntry type="library" name="fullcalendar" level="application" />
<orderEntry type="library" name="popper.js" level="application" />
<orderEntry type="library" name="tempusdominus-bootstrap-4" level="application" />
<orderEntry type="library" name="chart.js" level="application" />
</component>
<component name="TemplatesService">
<option name="TEMPLATE_CONFIGURATION" value="Django" />

View File

@ -1,6 +1,6 @@
<?xml version="1.0" encoding="UTF-8"?>
<project version="4">
<component name="JavaScriptLibraryMappings">
<file url="file://$PROJECT_DIR$" libraries="{@turf/turf, @zxing, apexcharts, bootstrap-icons, fullcalendar, jquery, jquery-3.5.1, line, moment-timezone, moment.js, popper.js, quagga, sweetalert2, tempusdominus-bootstrap-4, tesseract.js}" />
<file url="file://$PROJECT_DIR$" libraries="{@turf/turf, @zxing, apexcharts, bootstrap-icons, chart.js, fullcalendar, jquery, jquery-3.5.1, line, moment-timezone, moment.js, popper.js, quagga, sweetalert2, tempusdominus-bootstrap-4, tesseract.js}" />
</component>
</project>

View File

@ -1,6 +1,7 @@
from django.contrib import admin
from . import models
from django_ledger import models as ledger_models
from django_pdf_actions.actions import export_to_pdf_landscape, export_to_pdf_portrait
admin.site.register(models.Dealer)
@ -43,6 +44,7 @@ class CarMakeAdmin(admin.ModelAdmin):
list_display = ('name', 'arabic_name', 'is_sa_import')
search_fields = ('name', 'arabic_name')
list_filter = ('is_sa_import', 'name',)
actions = [export_to_pdf_landscape, export_to_pdf_portrait]
class Meta:
verbose_name = "Car Make"
@ -115,3 +117,7 @@ class CarOptionAdmin(admin.ModelAdmin):
# search_fields = ('user__username', 'action')
# list_filter = ('timestamp',)
@admin.register(ledger_models.ItemTransactionModel)
class ItemTransactionModelAdmin(admin.ModelAdmin):
actions = [export_to_pdf_landscape, export_to_pdf_portrait]

View File

@ -35,8 +35,8 @@ def get_jwt_token():
"Content-Type": "application/json",
}
data = {
"api_token": "f5204a00-6f31-4de2-96d8-ed998e0d230c",
"api_secret": "8c11320781a5b8f4f327b6937e6f8241",
"api_token": settings.CAR_API_TOKEN,
"api_secret": settings.CAR_API_SECRET,
}
try:
response = requests.post(url, headers=headers, json=data)
@ -247,6 +247,7 @@ def get_financial_values(model):
additional_services = []
for i in data:
print(i)
if i.item_model.additional_info["additional_services"]:
additional_services.extend(
[

View File

@ -3,6 +3,9 @@ from appointment.models import Appointment,AppointmentRequest,Service,StaffMembe
from datetime import timedelta
from calendar import month_name
from random import randint
from django_pdf_actions.actions import export_to_pdf_landscape
from reportlab.lib.pagesizes import landscape, A4
from rich import print
from decimal import Decimal
from django.core.paginator import Paginator
@ -139,6 +142,7 @@ from django_ledger.views.mixins import (
BaseDateNavigationUrlMixIn,
PDFReportMixIn,
)
from django_pdf_actions import actions
logger = logging.getLogger(__name__)
@ -273,6 +277,7 @@ class HomeView(TemplateView):
template_name = "index.html"
def dispatch(self, request, *args, **kwargs):
# Redirect unauthenticated users to the welcome page
if not request.user.is_authenticated:
return redirect("welcome")
return super().dispatch(request, *args, **kwargs)
@ -281,6 +286,8 @@ class HomeView(TemplateView):
context = super().get_context_data(**kwargs)
dealer = get_user_type(self.request)
try:
# Fetch car-related statistics
total_cars = models.Car.objects.filter(dealer=dealer).count()
total_reservations = models.CarReservation.objects.filter(
reserved_until__gte=timezone.now()
@ -289,24 +296,44 @@ class HomeView(TemplateView):
owner=dealer,
).count()
cars_outside = total_cars - cars_in_house
# Fetch financial statistics
stats = models.CarFinance.objects.aggregate(
total_cost_price=Sum("cost_price"),
total_selling_price=Sum("selling_price"),
)
total_cost_price = stats["total_cost_price"] or 0
total_selling_price = stats["total_selling_price"] or 0
total_cost_price = stats.get("total_cost_price", 0) or 0
total_selling_price = stats.get("total_selling_price", 0) or 0
total_profit = total_selling_price - total_cost_price
context["dealer"] = dealer
context["total_cars"] = total_cars
context["cars_in_house"] = cars_in_house
context["cars_outside"] = cars_outside
context["total_reservations"] = total_reservations
context["total_cost_price"] = total_cost_price
context["total_selling_price"] = total_selling_price
context["total_profit"] = total_profit
return context
# Prepare context data
context.update({
"dealer": dealer,
"total_cars": total_cars,
"cars_in_house": cars_in_house,
"cars_outside": cars_outside,
"total_reservations": total_reservations,
"total_cost_price": total_cost_price,
"total_selling_price": total_selling_price,
"total_profit": total_profit,
})
except Exception as e:
# Log the error (you can use Django's logging framework)
print(f"Error fetching data: {e}")
# Provide default values in case of an error
context.update({
"dealer": dealer,
"total_cars": 0,
"cars_in_house": 0,
"cars_outside": 0,
"total_reservations": 0,
"total_cost_price": 0,
"total_selling_price": 0,
"total_profit": 0,
})
return context
class TestView(TemplateView):
template_name = "test.html"
@ -663,7 +690,8 @@ class CarListView(LoginRequiredMixin, ListView):
paginate_by = 10
def get_context_data(self, **kwargs):
context = super().get_context_data(**kwargs)
cars = models.Car.objects.all()
dealer = get_user_type(self.request)
cars = models.Car.objects.filter(dealer=dealer).order_by("receiving_date")
context["stats"] = {
'all': cars.count(),
@ -672,16 +700,15 @@ class CarListView(LoginRequiredMixin, ListView):
'sold':cars.filter(status='sold').count(),
'transfer':cars.filter(status='transfer').count()
}
context['make'] = models.CarMake.objects.all()
context['make'] = models.CarMake.objects.filter(car__in=cars).distinct()
context['model'] = models.CarModel.objects.none()
context['year'] = models.Car.objects.none()
make = self.request.GET.get('make')
model = self.request.GET.get('model')
if make:
make_ = models.CarMake.objects.get(pk=int(make))
context['model'] = make_.carmodel_set.all()
make_ = models.CarMake.objects.get(id_car_make=int(make))
context['model'] = make_.carmodel_set.filter(car__in=cars).distinct()
if make and model:
make_ = models.CarMake.objects.get(id_car_make=int(make))
model_ = models.CarModel.objects.get(id_car_model=int(model))
@ -1205,9 +1232,7 @@ class CustomerListView(LoginRequiredMixin, ListView):
query = self.request.GET.get("q")
dealer = get_user_type(self.request)
customers = dealer.entity.get_customers().filter(
active=True, additional_info__type="customer"
)
customers = dealer.entity.get_customers().filter(additional_info__type="customer")
if query:
customers = customers.filter(
@ -1312,6 +1337,7 @@ def CustomerCreateView(request):
"address_1": form.cleaned_data["address"],
"phone": form.cleaned_data["phone_number"],
"email": email,
}
)
@ -2406,10 +2432,17 @@ def account_delete(request, pk):
# Sales list
def sales_list_view(request):
transactions = ItemTransactionModel.objects.all()[:10]
txs = get_item_transactions(transactions)
context = {"txs":txs}
return render(request, "sales/sales_list.html",context)
dealer = get_user_type(request)
entity = dealer.entity
transactions = ItemTransactionModel.objects.for_entity(entity_slug=entity.slug, user_model=dealer.user)
paginator = Paginator(transactions, 20)
page_number = request.GET.get('page')
page_obj = paginator.get_page(page_number)
txs = get_item_transactions(page_obj)
context = {"txs": txs, "page_obj": page_obj}
return render(request, "sales/sales_list.html", context)
# Estimates
class EstimateListView(LoginRequiredMixin, ListView):
@ -2428,6 +2461,10 @@ class EstimateListView(LoginRequiredMixin, ListView):
return queryset
# class EstimateCreateView(LoginRequiredMixin, SuccessMessageMixin, CreateView):
# model = EstimateModel
# form_class = EstimateModelCreateForm
@ -3106,7 +3143,11 @@ class LeadListView(ListView):
def get_queryset(self):
dealer = get_user_type(self.request)
return models.Lead.objects.filter(dealer=dealer).all()
staff = getattr(self.request.user, "staff", None)
if staff:
return models.Lead.objects.filter(dealer=dealer, staff=staff)
return models.Lead.objects.filter(dealer=dealer)
class LeadDetailView(DetailView):
@ -4314,9 +4355,9 @@ class EmployeeCalendarView(ListView):
model = Appointment
context_object_name = 'appointments'
# def get_context_data(self):
# self.context['dealer'] = get_user_type(self.request)
# dealer =
# return Appointment.objects.all()
def get_queryset(self):
dealer = get_user_type(self.request)
staff = self.request.user.staff
return Appointment.objects.filter(appointment_request__staff_member=staff).all()

89
ofx_sample.ofx Normal file
View File

@ -0,0 +1,89 @@
OFXHEADER:100
DATA:OFXSGML
VERSION:102
SECURITY:NONE
ENCODING:UTF-8
CHARSET:NONE
COMPRESSION:NONE
OLDFILEUID:NONE
NEWFILEUID:NONE
<?xml version="1.0" encoding="UTF-8"?>
<OFX>
<SIGNONMSGSRSV1>
<SONRS>
<STATUS>
<CODE>0</CODE>
<SEVERITY>INFO</SEVERITY>
</STATUS>
<DTSERVER>20250214000000</DTSERVER>
<LANGUAGE>ENG</LANGUAGE>
</SONRS>
</SIGNONMSGSRSV1>
<BANKMSGSRSV1>
<STMTTRNRS>
<TRNUID>1</TRNUID>
<STATUS>
<CODE>0</CODE>
<SEVERITY>INFO</SEVERITY>
</STATUS>
<STMTRS>
<CURDEF>USD</CURDEF>
<BANKACCTFROM>
<BANKID>123456789</BANKID>
<ACCTID>987654321</ACCTID>
<ACCTTYPE>CHECKING</ACCTTYPE>
</BANKACCTFROM>
<BANKTRANLIST>
<DTSTART>20240214000000</DTSTART>
<DTEND>20250213000000</DTEND>
<!-- Sample Transactions -->
<STMTTRN>
<TRNTYPE>DEP</TRNTYPE>
<DTPOSTED>20240301090000</DTPOSTED>
<TRNAMT>2500.00</TRNAMT>
<FITID>100001</FITID>
<NAME>PAYROLL DEPOSIT</NAME>
</STMTTRN>
<STMTTRN>
<TRNTYPE>POS</TRNTYPE>
<DTPOSTED>20240305120000</DTPOSTED>
<TRNAMT>-150.25</TRNAMT>
<FITID>100002</FITID>
<NAME>GROCERY STORE</NAME>
</STMTTRN>
<STMTTRN>
<TRNTYPE>ATM</TRNTYPE>
<DTPOSTED>20240310080000</DTPOSTED>
<TRNAMT>-100.00</TRNAMT>
<FITID>100003</FITID>
<NAME>ATM WITHDRAWAL</NAME>
</STMTTRN>
<STMTTRN>
<TRNTYPE>DEBIT</TRNTYPE>
<DTPOSTED>20240402070000</DTPOSTED>
<TRNAMT>-50.00</TRNAMT>
<FITID>100004</FITID>
<NAME>ONLINE SUBSCRIPTION</NAME>
</STMTTRN>
<STMTTRN>
<TRNTYPE>CHECK</TRNTYPE>
<DTPOSTED>20240415090000</DTPOSTED>
<TRNAMT>-500.00</TRNAMT>
<FITID>100005</FITID>
<NAME>RENT PAYMENT</NAME>
</STMTTRN>
</BANKTRANLIST>
<LEDGERBAL>
<BALAMT>7500.00</BALAMT>
<DTASOF>20250213000000</DTASOF>
</LEDGERBAL>
</STMTRS>
</STMTTRNRS>
</BANKMSGSRSV1>
</OFX>

View File

@ -53,11 +53,13 @@ django-extensions==3.2.3
django-filter==24.3
django-formtools==2.5.1
django-ledger==0.7.4.1
django-model-utils==5.0.0
django-money==3.5.3
django-next-url-mixin==0.4.0
django-nine==0.2.7
django-nonefield==0.4
django-ordered-model==3.7.4
django-pdf-actions==0.1.38
django-phonenumber-field==8.0.0
django-picklefield==3.2
django-plans==1.2.0

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

After

Width:  |  Height:  |  Size: 12 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.2 KiB

View File

@ -46,6 +46,9 @@
<div class="text-start mt-1">
<a class="fs-9" href="{% url 'account_reset_password' %}">{{ _("Forgot Password?")}}</a>
</div>
{% include 'partials/form_errors.html' %}
</form>
<div class="text-center my-3 fs-9">
{% trans 'If you have not created an account yet, then please' %}

View File

@ -105,12 +105,10 @@
</table>
</div>
<div class="row align-items-center justify-content-end py-4 pe-0 fs-9">
<!-- Optional: Pagination -->
{% if is_paginated %}
{% include 'partials/pagination.html' %}
{% endif %}
</div>
</div>
</div>
{% include 'modal/delete_modal.html' %}

View File

@ -2,6 +2,8 @@
{% load i18n static %}
{% block content %}
<script src="https://cdn.jsdelivr.net/npm/chart.js"></script>
<div class="px-3 mb-6">
<div class="row justify-content-between">
@ -46,7 +48,7 @@
<div class="border-translucent mb-sm-4">
<div class="d-flex align-items-center">
<div class="d-flex align-items-center icon-wrapper-sm shadow-primary-100" style="transform: rotate(-7.45deg);">
<div class="icon icon-shape bg-gradient-info text-white rounded-circle shadow">MMMMMMMMMMMMMMM</div>
<div class="icon icon-shape bg-gradient-info text-white rounded-circle shadow"></div>
</div>
<p class="text-body-tertiary fs-9 mb-0 ms-2 mt-3">Outgoing call</p>
</div>
@ -71,6 +73,12 @@
</div>
</div>
</div>
<div class="col-sm-6 col-md-4 col-xl-3 col-xxl-4 align-content-center">
</div>
</div>
<div class="row g-3 mb-3">
<canvas id="pnlChart"></canvas>
</div>
</div>
</div>
@ -103,4 +111,55 @@
</div>
</div>
</div>
<script>
const url = "{% url 'django_ledger:entity-json-pnl' dealer.entity.slug %}"
async function fetchPnLData() {
try {
const response = await fetch(url); // Update with your API URL
const jsonResponse = await response.json();
const pnlData = jsonResponse.results.pnl_data;
console.log(pnlData);
const labels = Object.keys(pnlData);
const profits = labels.map(label => pnlData[label].GROUP_NET_PROFIT);
const sales = labels.map(label => pnlData[label].GROUP_NET_SALES);
renderChart(labels, profits, sales);
} catch (error) {
console.error("Error fetching PnL data:", error);
}
}
function renderChart(labels, profits, sales) {
const ctx = document.getElementById("pnlChart").getContext("2d");
new Chart(ctx, {
type: "bar",
data: {
labels: labels,
datasets: [
{
label: "Net Profit",
data: profits,
backgroundColor: "green"
},
{
label: "Net Sales",
data: sales,
backgroundColor: "blue"
}
]
},
options: {
responsive: true,
scales: {
y: {
beginAtZero: true
}
}
}
});
}
fetchPnLData();
</script>
{% endblock %}

View File

@ -38,7 +38,7 @@
<li class="nav-item"><a class="nav-link px-2 py-1" href="{% url 'car_list' %}?status=reserved"><span>Reserved</span><span class="text-body-tertiary fw-semibold">({{stats.reserved}})</span></a></li>
<li class="nav-item"><a class="nav-link px-2 py-1" href="{% url 'car_list' %}?status=transfer"><span>Transfer</span><span class="text-body-tertiary fw-semibold">({{stats.transfer}})</span></a></li>
<li class="nav-item"><a class="nav-link px-2 py-1" href="{% url 'car_list' %}?status=sold"><span>Sold</span><span class="text-body-tertiary fw-semibold">({{stats.sold}})</span></a></li>
<li class="nav-item"><button hx-on:click="toggle_filter()" class="nav-link px-2 py-1"><span>Filter</span><span class="text-body-tertiary fw-semibold"></span></button></li>
<li class="nav-item"><button hx-on:click="toggle_filter()" class="btn btn-sm btn-primary px-2 py-1"><span>{{ _("Filter") }}</span><span class="fas fa-caret-down fs-9 ms-1 filter-icon"></span></button></li>
</ul>
</div>
<div class="col-12 col-sm-auto">
@ -52,38 +52,20 @@
/>
<span class="fas fa-search search-box-icon"></span>
</form>
</div><a class="btn btn-phoenix-primary px-3 me-1 border-0 text-body" href="#" data-bs-toggle="tooltip" data-bs-placement="top" data-bs-title="List view"><span class="fa-solid fa-list fs-10"></span></a><a class="btn btn-phoenix-primary px-3 me-1" href="#" data-bs-toggle="tooltip" data-bs-placement="top" data-bs-title="Board view">
<svg width="9" height="9" viewbox="0 0 9 9" fill="none" xmlns="http://www.w3.org/2000/svg">
<path d="M0 0.5C0 0.223857 0.223858 0 0.5 0H1.83333C2.10948 0 2.33333 0.223858 2.33333 0.5V1.83333C2.33333 2.10948 2.10948 2.33333 1.83333 2.33333H0.5C0.223857 2.33333 0 2.10948 0 1.83333V0.5Z" fill="currentColor"></path>
<path d="M3.33333 0.5C3.33333 0.223857 3.55719 0 3.83333 0H5.16667C5.44281 0 5.66667 0.223858 5.66667 0.5V1.83333C5.66667 2.10948 5.44281 2.33333 5.16667 2.33333H3.83333C3.55719 2.33333 3.33333 2.10948 3.33333 1.83333V0.5Z" fill="currentColor"></path>
<path d="M6.66667 0.5C6.66667 0.223857 6.89052 0 7.16667 0H8.5C8.77614 0 9 0.223858 9 0.5V1.83333C9 2.10948 8.77614 2.33333 8.5 2.33333H7.16667C6.89052 2.33333 6.66667 2.10948 6.66667 1.83333V0.5Z" fill="currentColor"></path>
<path d="M0 3.83333C0 3.55719 0.223858 3.33333 0.5 3.33333H1.83333C2.10948 3.33333 2.33333 3.55719 2.33333 3.83333V5.16667C2.33333 5.44281 2.10948 5.66667 1.83333 5.66667H0.5C0.223857 5.66667 0 5.44281 0 5.16667V3.83333Z" fill="currentColor"></path>
<path d="M3.33333 3.83333C3.33333 3.55719 3.55719 3.33333 3.83333 3.33333H5.16667C5.44281 3.33333 5.66667 3.55719 5.66667 3.83333V5.16667C5.66667 5.44281 5.44281 5.66667 5.16667 5.66667H3.83333C3.55719 5.66667 3.33333 5.44281 3.33333 5.16667V3.83333Z" fill="currentColor"></path>
<path d="M6.66667 3.83333C6.66667 3.55719 6.89052 3.33333 7.16667 3.33333H8.5C8.77614 3.33333 9 3.55719 9 3.83333V5.16667C9 5.44281 8.77614 5.66667 8.5 5.66667H7.16667C6.89052 5.66667 6.66667 5.44281 6.66667 5.16667V3.83333Z" fill="currentColor"></path>
<path d="M0 7.16667C0 6.89052 0.223858 6.66667 0.5 6.66667H1.83333C2.10948 6.66667 2.33333 6.89052 2.33333 7.16667V8.5C2.33333 8.77614 2.10948 9 1.83333 9H0.5C0.223857 9 0 8.77614 0 8.5V7.16667Z" fill="currentColor"></path>
<path d="M3.33333 7.16667C3.33333 6.89052 3.55719 6.66667 3.83333 6.66667H5.16667C5.44281 6.66667 5.66667 6.89052 5.66667 7.16667V8.5C5.66667 8.77614 5.44281 9 5.16667 9H3.83333C3.55719 9 3.33333 8.77614 3.33333 8.5V7.16667Z" fill="currentColor"></path>
<path d="M6.66667 7.16667C6.66667 6.89052 6.89052 6.66667 7.16667 6.66667H8.5C8.77614 6.66667 9 6.89052 9 7.16667V8.5C9 8.77614 8.77614 9 8.5 9H7.16667C6.89052 9 6.66667 8.77614 6.66667 8.5V7.16667Z" fill="currentColor"></path>
</svg></a><a class="btn btn-phoenix-primary px-3" href="#" data-bs-toggle="tooltip" data-bs-placement="top" data-bs-title="Card view">
<svg width="9" height="9" viewBox="0 0 9 9" fill="none" xmlns="http://www.w3.org/2000/svg">
<path d="M0 0.5C0 0.223858 0.223858 0 0.5 0H3.5C3.77614 0 4 0.223858 4 0.5V3.5C4 3.77614 3.77614 4 3.5 4H0.5C0.223858 4 0 3.77614 0 3.5V0.5Z" fill="currentColor"></path>
<path d="M0 5.5C0 5.22386 0.223858 5 0.5 5H3.5C3.77614 5 4 5.22386 4 5.5V8.5C4 8.77614 3.77614 9 3.5 9H0.5C0.223858 9 0 8.77614 0 8.5V5.5Z" fill="currentColor"></path>
<path d="M5 0.5C5 0.223858 5.22386 0 5.5 0H8.5C8.77614 0 9 0.223858 9 0.5V3.5C9 3.77614 8.77614 4 8.5 4H5.5C5.22386 4 5 3.77614 5 3.5V0.5Z" fill="currentColor"></path>
<path d="M5 5.5C5 5.22386 5.22386 5 5.5 5H8.5C8.77614 5 9 5.22386 9 5.5V8.5C9 8.77614 8.77614 9 8.5 9H5.5C5.22386 9 5 8.77614 5 8.5V5.5Z" fill="currentColor"></path>
</svg></a>
</div>
</div>
</div>
</div>
<div class="d-flex align-items-center d-none filter">
<select hx-get="{% url 'car_list' %}" name="make" hx-target='.model-select' hx-select='.model-select' hx-swap="outerHTML show:window:top" hx-indicator=".htmx-indicator" class="form-select make" aria-label="Default select example"
<select hx-get="{% url 'car_list' %}" name="make" hx-target='.model-select' hx-select='.model-select' hx-swap="outerHTML show:window:top" hx-indicator=".htmx-indicator" class="form-select form-control-sm me-1 make" aria-label="Default select example"
hx-on::before-request="filter_before_request()"
hx-on::after-request="filter_after_request()"
>
hx-on::after-request="filter_after_request()">
<option selected="" value="" disabled>Select Make</option>
{% for m in make %}
<option value="{{ m.pk }}">{{ m.name }}</option>
{% endfor %}
</select>
<select hx-get="{% url 'car_list' %}" hx-include=".make" name="model" hx-target='.year' hx-select='.year' hx-swap="outerHTML show:window:top" hx-indicator=".htmx-indicator" class="form-select model-select" aria-label="Default select example"
<select hx-get="{% url 'car_list' %}" hx-include=".make" name="model" hx-target='.year' hx-select='.year' hx-swap="outerHTML show:window:top" hx-indicator=".htmx-indicator" class="form-select form-control-sm me-1 model-select" aria-label="Default select example"
hx-on::before-request="filter_before_request()"
hx-on::after-request="filter_after_request()"
>
@ -92,24 +74,24 @@
<option value="{{ m.pk }}">{{ m.name }}</option>
{% endfor %}
</select>
<select class="form-select year" name="year" aria-label="Default select example">
<select class="form-select form-control-sm me-1 year" name="year" aria-label="Default select example">
<option selected="" value="" disabled>Select Year</option>
{% for y in year %}
<option value="{{ y.0 }}">{{ y.0 }}</option>
{% endfor %}
</select>
<select class="form-select car_status" name="car_status" aria-label="Default select example">
<select class="form-select form-control-sm me-1 car_status" name="car_status" aria-label="Default select example">
<option selected="" value="">All</option>
<option value="available">Available</option>
<option value="reserved">Reserved</option>
<option value="sold">Sold</option>
<option value="transfer">Transfer</option>
</select>
<button id="search" hx-get="{% url 'car_list' %}" hx-include=".make,.model,.year,.car_status" hx-indicator=".htmx-indicator" hx-target='.table-responsive' hx-select='.table-responsive' hx-swap="outerHTML show:window:top" class="btn btn-sm btn-phoenix-primary"
<button id="search" hx-get="{% url 'car_list' %}" hx-include=".make,.model,.year,.car_status" hx-indicator=".htmx-indicator" hx-target='.table-responsive' hx-select='.table-responsive' hx-swap="outerHTML show:window:top" class="btn btn-sm btn-phoenix-primary ms-1"
hx-on::before-request="filter_before_request()"
hx-on::after-request="filter_after_request()">Search</button>
hx-on::after-request="filter_after_request()">{{ _("Search") }}</button>
</div>
<div class="row">
<div class="table-responsive scrollbar transition">
<div class="d-flex flex-wrap align-items-center justify-content-between py-3 pe-0 fs-9 border-bottom border-translucent">
<div class="d-flex">
@ -117,65 +99,62 @@
<div class="d-flex" hx-boost="true" hx-push-url='false' hx-include=".make,.model,.year,.car_status" hx-target=".table-responsive" hx-select=".table-responsive" hx-swap="innerHTML show:window:top" hx-indicator=".htmx-indicator"
hx-on::before-request="on_before_request()"
hx-on::after-request="on_after_request()">
{% if page_obj.has_previous %}
<a href="{% url 'car_list' %}?page={{page_obj.previous_page_number}}" class="page-link" data-list-pagination="prev"><span class="fas fa-chevron-left"></span></a>
{% endif %}
<ul class="mb-0 pagination">Page {{ page_obj.number }} of {{ page_obj.paginator.num_pages }}</ul>
{% if page_obj.has_next %}
<a href="{% url 'car_list' %}?page={{page_obj.next_page_number}}" class="page-link pe-0" data-list-pagination="next"><span class="fas fa-chevron-right"></span></a>
{% endif %}
</div>
</div>
<table class="table fs-9 mb-0 border-top border-translucent">
<table class="table table-sm fs-9 mb-0 border-top border-translucent">
<thead>
<tr>
<th class="sort white-space-nowrap align-middle ps-0" scope="col" data-sort="projectName" style="width:10%;">Make</th>
<th class="sort align-middle ps-3" scope="col" data-sort="assignees" style="width:10%;">Model</th>
<th class="sort align-middle ps-3" scope="col" data-sort="start" style="width:10%;">Year</th>
<th class="sort align-middle ps-3" scope="col" data-sort="deadline" style="width:15%;">Trim</th>
<th class="sort align-middle ps-3" scope="col" data-sort="task" style="width:12%;">VIN</th>
<th class="sort align-middle ps-3" scope="col" data-sort="task" style="width:12%;">Receiving Date</th>
<th class="sort white-space-nowrap align-middle ps-0" scope="col" data-sort="projectName" style="width:10%;">
{{ _("Make") }}</th>
<th class="sort align-middle ps-3" scope="col" data-sort="assignees" style="width:10%;">{{ _("Model") }}</th>
<th class="sort align-middle ps-3" scope="col" data-sort="start" style="width:10%;">{{ _("Year") }}</th>
<th class="sort align-middle ps-3" scope="col" data-sort="deadline" style="width:15%;">{{ _("Trim") }}</th>
<th class="sort align-middle ps-3" scope="col" data-sort="task" style="width:12%;">{{ _("VIN") }}</th>
<th class="sort align-middle ps-3" scope="col" data-sort="task" style="width:12%;">{{ _("Age") }}</th>
<th class="sort align-middle text-end" scope="col" data-sort="statuses" style="width:10%;">STATUS</th>
<th class="sort align-middle text-end" scope="col" data-sort="statuses" style="width:10%;">{{ _("Status") }}</th>
<th class="sort align-middle text-end" scope="col" style="width:10%;"></th>
</tr>
</thead>
<tbody class="list" id="project-list-table-body">
{% for car in cars %}
{% for car in page_obj %}
<tr class="position-static">
<td class="align-middle time white-space-nowrap ps-0 projectName py-4"><a class="fw-bold fs-8" href="{% url 'car_detail' car.pk %}">{{car.id_car_make}}</a></td>
<td class="align-middle white-space-nowrap start ps-3 py-4">
<td class="align-middle time white-space-nowrap ps-0 projectName"><a class="fw-bold fs-8" href="{% url 'car_detail' car.pk %}">{{car.id_car_make}}</a></td>
<td class="align-middle white-space-nowrap start">
<p class="mb-0 fs-9 text-body">{{car.id_car_model}}</p>
</td>
<td class="align-middle white-space-nowrap deadline ps-3 py-4">
<td class="align-middle white-space-nowrap deadline">
<p class="mb-0 fs-9 text-body">{{car.year}}</p>
</td>
<td class="align-middle white-space-nowrap task ps-3 py-4">
<td class="align-middle white-space-nowrap task">
<p class="fw-bo text-body fs-9 mb-0">{{car.id_car_trim}}</p>
</td>
<td class="align-middle white-space-nowrap task ps-3 py-4">
<td class="align-middle white-space-nowrap task">
<p class="fw-bo text-body fs-9 mb-0">{{car.vin}}</p>
</td>
<td class="align-middle white-space-nowrap task ps-3 py-4">
<p class="fw-bo text-body fs-9 mb-0">{{car.receiving_date}}</p>
<td class="align-middle white-space-nowrap task">
<p class="fw-bo text-body fs-9 mb-0">{{car.receiving_date|timesince}}</p>
</td>
<td class="align-middle white-space-nowrap text-end statuses">
{% if car.status == "available" %}
<span class="badge badge-phoenix fs-10 badge-phoenix-success">{{car.status}}</span>
<span class="badge badge-phoenix fs-11 badge-phoenix-success">{{car.status}}</span>
{% elif car.status == "reserved" %}
<span class="badge badge-phoenix fs-10 badge-phoenix-danger">{{car.status}}</span>
<span class="badge badge-phoenix fs-11 badge-phoenix-danger">{{car.status}}</span>
{% elif car.status == "sold" %}
<span class="badge badge-phoenix fs-10 badge-phoenix-info">{{car.status}}</span>
<span class="badge badge-phoenix fs-11 badge-phoenix-info">{{car.status}}</span>
{% elif car.status == "transfer" %}
<span class="badge badge-phoenix fs-10 badge-phoenix-warning">{{car.status}}</span>
<span class="badge badge-phoenix fs-11 badge-phoenix-warning">{{car.status}}</span>
{% endif %}
</td>
<td class="align-middle text-end white-space-nowrap pe-0 action">
<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="#!">View</a><a class="dropdown-item" href="#!">Export</a>
<div class="dropdown-divider"></div><a class="dropdown-item text-danger" href="#!">Remove</a>
<div class="dropdown-menu dropdown-menu-end py-2">
<a class="dropdown-item" href="#!">{{ _("View") }}</a>
<a class="dropdown-item" href="#!">{{ _("Export") }}</a>
</div>
</div>
</td>
@ -183,24 +162,21 @@
{% endfor %}
</tbody>
</table>
</div>
<div class="d-flex flex-wrap align-items-center justify-content-between py-3 pe-0 fs-9 border-bottom border-translucent">
<div class="d-flex">
<p class="mb-0 d-none d-sm-block me-3 fw-semibold text-body" data-list-info="data-list-info"></p><a class="fw-semibold" href="#!" data-list-view="*">View all<span class="fas fa-angle-right ms-1" data-fa-transform="down-1"></span></a><a class="fw-semibold d-none" href="#!" data-list-view="less">View Less<span class="fas fa-angle-right ms-1" data-fa-transform="down-1"></span></a>
</div>
<div class="d-flex" hx-boost="true" hx-push-url='false' hx-include=".make,.model,.year,.car_status" hx-target=".table-responsive" hx-select=".table-responsive" hx-swap="innerHTML" hx-indicator=".htmx-indicator"
hx-on::before-request="on_before_request()"
hx-on::after-request="on_after_request()">
{% if page_obj.has_previous %}
<a href="{% url 'car_list' %}?page={{page_obj.previous_page_number}}" class="page-link" data-list-pagination="prev"><span class="fas fa-chevron-left"></span></a>
{% endif %}
<ul class="mb-0 pagination">Page {{ page_obj.number }} of {{ page_obj.paginator.num_pages }}</ul>
{% if page_obj.has_next %}
<a href="{% url 'car_list' %}?page={{page_obj.next_page_number}}" class="page-link pe-0" data-list-pagination="next"><span class="fas fa-chevron-right"></span></a>
{% endif %}
</div>
</div>
</div>
<div class="row d-flex justify-content-end align-items-center p-3">
{% if is_paginated %}
{% include 'partials/pagination.html' %}
{% endif %}
</div>
</div>
</div>
</div>
{% endblock %}
@ -226,6 +202,8 @@
}
function toggle_filter(){
document.querySelector('.filter').classList.toggle('d-none')
document.querySelector('.filter-icon').classList.toggle("fa-caret-down");
document.querySelector('.filter-icon').classList.toggle("fa-caret-up");
}
function filter_before_request(){
document.querySelector('.model-select').setAttribute('disabled', true)

View File

@ -1,6 +1,6 @@
<!-- Display Form Errors -->
{% if form.errors %}
<div class="alert alert-danger">
<div class="alert alert-danger alert-dismissible fade show" role="alert">
<ul>
{% for field in form %}
{% for error in field.errors %}

View File

@ -11,7 +11,7 @@
<table class="table fs-9 mb-0 border-top border-translucent">
<thead>
<tr>
<th class="sort white-space-nowrap align-middle" scope="col">{% trans "Estimate Number" %}</th>
<th class="sort white-space-nowrap align-middle" scope="col">{% trans "Quotation Number" %}</th>
<th class="sort white-space-nowrap align-middle" scope="col">{% trans "Customer" %}</th>
<th class="sort white-space-nowrap align-middle" scope="col">{% trans "Status" %}</th>
<th class="sort white-space-nowrap align-middle" scope="col">{% trans "Status Date" %}</th>
@ -23,7 +23,7 @@
{% for estimate in estimates %}
<tr class="hover-actions-trigger btn-reveal-trigger position-static">
<td class="align-middle product white-space-nowrap py-0">{{ estimate.estimate_number }}</td>
<td class="align-middle product white-space-nowrap">{{ estimate.customer }}</td>
<td class="align-middle product white-space-nowrap">{{ estimate.customer.customer_name }}</td>
<td class="align-middle product white-space-nowrap">
{% if estimate.status == 'draft' %}
<span class="badge badge-phoenix badge-phoenix-warning">{% trans "Draft" %}</span>
@ -48,6 +48,10 @@
class="btn btn-sm btn-phoenix-success">
{% trans "view"|capfirst %}
</a>
<a href="{% url 'estimate_detail' estimate.pk %}"
class="btn btn-sm btn-phoenix-success">
{% trans "pdf"|capfirst %}
</a>
</td>
</tr>
{% empty %}

View File

@ -118,11 +118,11 @@
hx-on::before-request="on_before_request()"
hx-on::after-request="on_after_request()">
{% if page_obj.has_previous %}
<a href="{% url 'car_list' %}?page={{page_obj.previous_page_number}}" class="page-link" data-list-pagination="prev"><span class="fas fa-chevron-left"></span></a>
<a href="{% url 'sales_list' %}?page={{page_obj.previous_page_number}}" class="page-link" data-list-pagination="prev"><span class="fas fa-chevron-left"></span></a>
{% endif %}
<ul class="mb-0 pagination">Page {{ page_obj.number }} of {{ page_obj.paginator.num_pages }}</ul>
{% if page_obj.has_next %}
<a href="{% url 'car_list' %}?page={{page_obj.next_page_number}}" class="page-link pe-0" data-list-pagination="next"><span class="fas fa-chevron-right"></span></a>
<a href="{% url 'sales_list' %}?page={{page_obj.next_page_number}}" class="page-link pe-0" data-list-pagination="next"><span class="fas fa-chevron-right"></span></a>
{% endif %}
</div>
</div>

39
test.json Normal file
View File

@ -0,0 +1,39 @@
{
"type": "customer",
"customer_info":
{
"first_name": "firstNameT",
"first_name_arabic": "firstName",
"middle_name": "fatherNameT",
"middle_name_arabic": "fatherName",
"last_name": "familyNameT",
"last_name_arabic": "familyName",
"national_id": "id",
"birth_date": "birthDate",
"nationality": "nationalityDescEn"
},
"elm":
{
"birthDate": "1985-12-20",
"familyName": "العنزي",
"familyNameT": "Alenazi",
"fatherName": "الراوي",
"fatherNameT": "Alrawi",
"firstName": "هليل",
"firstNameT": "Hilayel",
"grandFatherName": "عثمان",
"grandFatherNameT": "Othman",
"hasIdExpired": 2,
"id": 1020060808,
"idExpirationDate": "2029-01-15",
"nationalityCode": 100,
"nationalityDescAr": "سعودي",
"nationalityDescEn": "Saudi",
"sexCode": 1,
"sexDescAr": "ذكر",
"sexDescEn": "Male"
}
}