This commit is contained in:
gitea 2025-02-16 15:35:09 +00:00
commit 9b9edbefa8
52 changed files with 1317 additions and 910 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)
@ -9,7 +10,7 @@ admin.site.register(models.Vendor)
# admin.site.register(models.SaleQuotation)
# admin.site.register(models.SaleQuotationCar)
# admin.site.register(models.SalesOrder)
admin.site.register(models.Car)
admin.site.register(models.CarFinance)
admin.site.register(models.CarColors)
admin.site.register(models.CarRegistration)
@ -38,11 +39,18 @@ admin.site.register(models.Activity)
admin.site.register(models.Schedule)
admin.site.register(models.Notes)
@admin.register(models.Car)
class CarAdmin(admin.ModelAdmin):
search_fields = ('vin',)
actions = [export_to_pdf_landscape, export_to_pdf_portrait]
@admin.register(models.CarMake)
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"
@ -52,7 +60,7 @@ class CarMakeAdmin(admin.ModelAdmin):
@admin.register(models.CarModel)
class CarModelAdmin(admin.ModelAdmin):
list_display = ('name', 'arabic_name', 'id_car_make', 'get_is_sa_import')
search_fields = ('name', 'arabic_name')
search_fields = ('id_car_model', 'name', 'arabic_name')
list_filter = ('id_car_make__is_sa_import', 'id_car_make')
sortable_by = ['name', 'arabic_name', 'id_car_make']
@ -69,7 +77,7 @@ class CarModelAdmin(admin.ModelAdmin):
@admin.register(models.CarSerie)
class CarSeriesAdmin(admin.ModelAdmin):
list_display = ('name', 'arabic_name', 'id_car_model', )
search_fields = ('name', 'id_car_model__name')
search_fields = ('id_car_serie', 'name', 'id_car_model__name', 'id_car_model__id_car_make__name')
list_filter = ('id_car_model__id_car_make__is_sa_import',
'id_car_model__id_car_make__name',)
@ -115,3 +123,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

@ -94,9 +94,9 @@ urlpatterns = [
),
path("crm/leads/<int:pk>/delete/", views.LeadDeleteView, name="lead_delete"),
path("crm/leads/<int:pk>/lead-convert/", views.lead_convert, name="lead_convert"),
path("crm/leads/<int:pk>/add-note/", views.add_note_to_lead, name="add_note"),
path('crm/leads/<int:pk>/update-note/', views.update_note, name='update_note'),
path("crm/leads/<int:pk>/delete-note/", views.delete_note, name="delete_note"),
path("crm/leads/<int:pk>/add-note/", views.add_note_to_lead, name="add_note_to_lead"),
path('crm/leads/<int:pk>/update-note/', views.update_note, name='update_note_to_lead'),
path("crm/leads/<int:pk>/delete-note/", views.delete_note, name="delete_note_to_lead"),
path(
"crm/leads/<int:pk>/add-activity/",
views.add_activity_to_lead,

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__)
@ -265,6 +269,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)
@ -273,33 +278,55 @@ class HomeView(TemplateView):
context = super().get_context_data(**kwargs)
dealer = get_user_type(self.request)
total_cars = models.Car.objects.filter(dealer=dealer).count()
total_reservations = models.CarReservation.objects.filter(
reserved_until__gte=timezone.now()
).count()
cars_in_house = models.CarLocation.objects.filter(
owner=dealer,
).count()
cars_outside = total_cars - cars_in_house
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_profit = total_selling_price - total_cost_price
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()
).count()
cars_in_house = models.CarLocation.objects.filter(
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.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
# 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,
})
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
class TestView(TemplateView):
template_name = "test.html"
@ -617,9 +644,7 @@ class CarInventory(LoginRequiredMixin, ListView):
id_car_trim=trim_id,
).order_by("receiving_date")
if query:
cars = cars.filter(Q(vin__icontains=query))
return cars
return apply_search_filters(cars, query)
def get_context_data(self, **kwargs):
context = super().get_context_data(**kwargs)
@ -652,11 +677,12 @@ class CarListView(LoginRequiredMixin, ListView):
model = models.Car
template_name = "inventory/car_list_view.html"
context_object_name = "cars"
paginate_by = 10
paginate_by = 20
def get_context_data(self, **kwargs):
context = super().get_context_data(**kwargs)
dealer = get_user_type(self.request)
cars = models.Car.objects.filter(dealer=dealer)
dealer = get_user_type(self.request)
cars = models.Car.objects.filter(dealer=dealer).order_by("receiving_date")
context["stats"] = {
'all': cars.count(),
'available':cars.filter(status='available').count(),
@ -664,16 +690,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))
@ -1198,17 +1223,9 @@ 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(
Q(first_name__icontains=query)
| Q(last_name__icontains=query)
| Q(additional_info__info__icontains=query)
)
return customers
return apply_search_filters(customers, query)
def get_context_data(self, **kwargs):
context = super().get_context_data(**kwargs)
@ -1255,7 +1272,7 @@ def add_note_to_customer(request, pk):
return redirect("customer_detail", pk=pk)
else:
form = forms.NoteForm()
return render(request, "crm/add_note.html", {"form": form, "customer": customer})
return render(request, "crm/note_form.html", {"form": form, "customer": customer})
def add_activity_to_customer(request, pk):
@ -1305,6 +1322,7 @@ def CustomerCreateView(request):
"address_1": form.cleaned_data["address"],
"phone": form.cleaned_data["phone_number"],
"email": email,
}
)
@ -1388,10 +1406,11 @@ class VendorListView(LoginRequiredMixin, ListView):
ordering = ["-created"]
def get_queryset(self):
query = self.request.GET.get("q")
dealer = get_user_type(self.request)
# vendors = models.Vendor.objects.filter(dealer=dealer)
return dealer.entity.get_vendors().filter(active=True)
# return vendors
vendors = dealer.entity.get_vendors().filter(active=True)
return apply_search_filters(vendors, query)
# class VendorDetailView(LoginRequiredMixin, DetailView):
@ -1828,8 +1847,10 @@ class UserListView(LoginRequiredMixin, ListView):
template_name = "users/user_list.html"
def get_queryset(self):
query = self.request.GET.get("q")
dealer = get_user_type(self.request)
return models.Staff.objects.filter(dealer=dealer).all()
staff = models.Staff.objects.filter(dealer=dealer).all()
return apply_search_filters(staff, query)
class UserDetailView(LoginRequiredMixin, DetailView):
@ -1911,12 +1932,13 @@ class OrganizationListView(LoginRequiredMixin, ListView):
paginate_by = 10
def get_queryset(self):
query = self.request.GET.get("q")
dealer = get_user_type(self.request)
return (
dealer.entity.get_customers()
.filter(additional_info__type="organization", active=True)
.all()
)
organization = dealer.entity.get_customers().filter(additional_info__type="organization", active=True)
return apply_search_filters(organization, query)
class OrganizationDetailView(DetailView):
@ -2007,8 +2029,10 @@ class RepresentativeListView(LoginRequiredMixin, ListView):
paginate_by = 10
def get_queryset(self):
query = self.request.GET.get("q")
dealer = get_user_type(self.request)
return models.Representative.objects.filter(dealer=dealer).all()
representative = models.Representative.objects.filter(dealer=dealer)
return apply_search_filters(representative, query)
class RepresentativeDetailView(DetailView):
@ -2233,8 +2257,10 @@ class BankAccountListView(LoginRequiredMixin, ListView):
paginate_by = 10
def get_queryset(self):
query = self.request.GET.get("q")
dealer = get_user_type(self.request)
return BankAccountModel.objects.filter(entity_model=dealer.entity)
bank_accounts = BankAccountModel.objects.filter(entity_model=dealer.entity)
return apply_search_filters(bank_accounts, query)
class BankAccountCreateView(LoginRequiredMixin, SuccessMessageMixin, CreateView):
@ -2304,9 +2330,10 @@ class AccountListView(LoginRequiredMixin, ListView):
paginate_by = 10
def get_queryset(self):
query = self.request.GET.get("q")
dealer = get_user_type(self.request)
entity = dealer.entity
return entity.get_all_accounts()
accounts = dealer.entity.get_all_accounts()
return apply_search_filters(accounts, query)
class AccountCreateView(LoginRequiredMixin, SuccessMessageMixin, CreateView):
@ -2400,17 +2427,24 @@ 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, 10)
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):
model = EstimateModel
template_name = "sales/estimates/estimate_list.html"
context_object_name = "estimates"
paginate_by = 10
paginate_by = 20
def get_queryset(self):
dealer = get_user_type(self.request)
@ -2422,6 +2456,10 @@ class EstimateListView(LoginRequiredMixin, ListView):
return queryset
# class EstimateCreateView(LoginRequiredMixin, SuccessMessageMixin, CreateView):
# model = EstimateModel
# form_class = EstimateModelCreateForm
@ -2766,9 +2804,10 @@ class InvoiceListView(LoginRequiredMixin, ListView):
paginate_by = 20
def get_queryset(self):
query = self.request.GET.get("q")
dealer = get_user_type(self.request)
entity = dealer.entity
return entity.get_invoices()
invoices = dealer.entity.get_invoices()
return apply_search_filters(invoices, query)
class InvoiceDetailView(LoginRequiredMixin, DetailView):
@ -3106,7 +3145,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 +4357,25 @@ 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):
query = self.request.GET.get('q')
dealer = get_user_type(self.request)
staff = getattr(self.request, 'staff', None)
if staff:
appointments = Appointment.objects.filter(appointment_request__staff_member=staff, ppointment_request__date__gt=timezone.now())
appointments = Appointment.objects.filter(appointment_request__date__gt=timezone.now())
return apply_search_filters(appointments, query)
def apply_search_filters(queryset, query):
if not query:
return queryset
search_filters = Q()
model = queryset.model
for field in model._meta.get_fields():
if hasattr(field, 'attname') and field.get_internal_type() in ["CharField", "TextField", "EmailField"]:
search_filters |= Q(**{f"{field.name}__icontains": query})
return queryset.filter(search_filters).distinct()

Binary file not shown.

File diff suppressed because it is too large Load Diff

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: 46 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 132 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 15 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 16 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 13 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 29 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 11 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 6.7 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 7.8 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 32 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 60 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 40 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 73 KiB

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

@ -1,22 +1,38 @@
<!-- crm/employee_calendar.html -->
<h1>Upcoming Test Drives</h1>
<table>
{% extends 'base.html' %}
{% load static %}
{% block content %}
<div class="row">
<div class="table-responsive border-translucent">
<table class="table table-sm fs-9">
<thead>
<tr>
<th>Customer</th>
<th>Vehicle</th>
<th>Date/Time</th>
<th>Service</th>
<th>Date</th>
<th>Start Time</th>
<th>End Time</th>
<th>Staff</th>
<th>Status</th>
<th></th>
</tr>
</thead>
<tbody>
{% for appointment in appointments %}
<tr>
<td>{{ appointment.client.name }}</td>
<td>{{ appointment.service.name }}</td>
<td>{{ appointment.start_time|date:"M j, Y H:i" }}</td>
<td>{{ appointment.staff.user.get_full_name }}</td>
<td>{{ appointment.get_client_name }}</td>
<td>{{ appointment.get_service }}</td>
<td>{{ appointment.appointment_request.date|date:"Y-m-d" }}</td>
<td>{{ appointment.appointment_request.start_time }}</td>
<td>{{ appointment.appointment_request.end_time }}</td>
<td>{{ appointment.get_staff_member_name }}</td>
<td></td>
<td>
<a href="{% url 'appointment:display_appointment' appointment.id %}">view</a>
</td>
</tr>
{% endfor %}
</tbody>
</table>
</table>
</div>
</div>
{% endblock %}

View File

@ -181,7 +181,7 @@
<h3 class="mb-4" id="scrollspyNotes">{{ _("Notes") }}</h3>
</div>
<div class="d-flex align-items-center justify-content-start">
<a id="addBtn" href="#" class="btn btn-sm btn-phoenix-primary mb-3" data-url="{% url 'add_note' lead.pk %}" data-bs-toggle="modal" data-bs-target="#noteModal" data-note-title="{{ _("Add") }}<i class='fa fa-plus-circle text-success ms-2'></i>">
<a id="addBtn" href="#" class="btn btn-sm btn-phoenix-primary mb-3" data-url="{% url 'add_note_to_lead' lead.pk %}" data-bs-toggle="modal" data-bs-target="#noteModal" data-note-title="{{ _("Add") }}<i class='fa fa-plus-circle text-success ms-2'></i>">
<span class="fas fa-plus me-1"></span>
{% trans 'Add Note' %}
</a>
@ -197,7 +197,7 @@
<th class="align-middle pe-0 text-end" scope="col" style="width:10%;"> </th>
</tr>
</thead>
<tbody class="list" id="lead-details-table-body">
<tbody >
{% for note in notes %}
<tr class="hover-actions-trigger btn-reveal-trigger position-static">
@ -205,17 +205,17 @@
{% if note.created_by.staff %}
<td class="align-middle white-space-nowrap text-start white-space-nowrap">{{ note.created_by.staff.name }}</td>
{% else %}
<td class="align-middle white-space-nowrap text-start white-space-nowrap">{{ note.created_by.dealer.get_local_name }}</td>
<td class="align-middle white-space-nowrap text-start white-space-nowrap">{{ note.created_by.dealer.get_local_name|default:note.created_by.dealer.name }}</td>
{% endif %}
<td class="align-middle text-body-tertiary text-start white-space-nowrap">{{ note.created }}</td>
<td class="align-middle text-end white-space-nowrap pe-0 action py-2">
{% if note.created_by == request.user %}
<a id="updateBtn" href="#" class="btn btn-sm btn-phoenix-primary me-2" data-url="{% url 'update_note' note.pk %}" data-bs-toggle="modal" data-bs-target="#noteModal" data-note-title="{{ _("Update") }}<i class='fas fa-pen-square text-primary ms-2'></i>">
<a id="updateBtn" href="#" class="btn btn-sm btn-phoenix-primary me-2" data-url="{% url 'update_note_to_lead' note.pk %}" data-bs-toggle="modal" data-bs-target="#noteModal" data-note-title="{{ _("Update") }}<i class='fas fa-pen-square text-primary ms-2'></i>">
<i class="fas fa-pen"></i>
</a>
<button class="btn btn-phoenix-danger btn-sm delete-btn"
data-url="{% url 'delete_note' note.pk %}"
data-url="{% url 'delete_note_to_lead' note.pk %}"
data-message="Are you sure you want to delete this note?"
data-bs-toggle="modal" data-bs-target="#deleteModal">
<i class="fas fa-trash"></i>

View File

@ -121,8 +121,7 @@
</div>
<tr class="hover-actions-trigger btn-reveal-trigger position-static">
<td class="name align-middle white-space-nowrap ps-0">
<div class="d-flex align-items-center"><a href="#!">
</a>
<div class="d-flex align-items-center">
<div><a class="fs-8 fw-bold" href="{% url 'lead_detail' lead.pk %}">{{lead.full_name}}</a>
<div class="d-flex align-items-center">
<p class="mb-0 text-body-highlight fw-semibold fs-9 me-2"></p>

View File

@ -1,9 +1,9 @@
{% load i18n static crispy_forms_filters %}
{% if form.instance.pk %}
<form method="post" action="{% url 'update_note' note.pk %}" enctype="multipart/form-data">
<form method="post" action="{% url 'update_note_to_lead' note.pk %}" enctype="multipart/form-data">
{% else %}
<form method="post" action="{% url 'add_note' lead.pk %}" enctype="multipart/form-data">
<form method="post" action="{% url 'add_note_to_lead' lead.pk %}" enctype="multipart/form-data">
{% endif %}
{% csrf_token %}

View File

@ -6,7 +6,7 @@
{% block content %}
<div class="row">
<h2 class="mb-4">{{ _("Customers")|capfirst }}</h2>
<h4 class="mb-4">{{ _("Customers")|capfirst }}</h4>
<div class="row g-3 justify-content-between mb-4">
<div class="col-auto">
<div class="d-md-flex justify-content-between">
@ -17,25 +17,15 @@
</div>
<div class="col-auto">
<div class="d-flex">
<div class="search-box me-2">
<form method="get" class="d-inline-block position-relative">
<div class="input-group">
<button type="submit" class="btn btn-phoenix-primary"><span class="fas fa-search search-box-icon"></span></button>
<input name="q" class="form-control search-input search" type="search" placeholder="{{ _('Enter customer name') }}" value="{{ request.GET.q }}"/>
{% if request.GET.q %}
<a href="{% url request.resolver_match.view_name %}" class="btn btn-close"></a>
</div>
{% endif %}
</form>
</div>
{% include 'partials/search_box.html' %}
</div>
</div>
</div>
{% if page_obj.object_list %}
<div class="table-responsive scrollbar mx-n1 px-1">
<div class="table-responsive scrollbar transition">
<table class="table fs-9 mb-0 border-top border-translucent">
<table class="table table-sm fs-9 mb-0 border-translucent">
<thead>
<tr>
<th></th>
@ -104,14 +94,10 @@
{% endif %}
</table>
</div>
<div class="row align-items-center justify-content-end py-4 pe-0 fs-9">
{% include 'partials/pagination.html' %}
<!-- Optional: Pagination -->
{% if is_paginated %}
{% include 'partials/pagination.html' %}
{% endif %}
</div>
</div>
</div>
{% include 'modal/delete_modal.html' %}
{% endblock %}

View File

@ -106,7 +106,10 @@
<div class="card">
<div class="card-body">
<div class="d-flex align-items-center justify-content-end">
<a class="fw-bold fs-9 mb-0 text-end" href="{% url 'add_note_to_customer' customer.pk %}"><span class="fas fa-plus me-1"></span>{{ _("Add Note")}}</a>
<a id="addBtn" href="#" class="btn btn-sm btn-phoenix-primary mb-3" data-url="{% url 'add_note_to_customer' customer.pk %}" data-bs-toggle="modal" data-bs-target="#noteModal" data-note-title="{{ _("Add") }}<i class='fa fa-plus-circle text-success ms-2'></i>">
<span class="fas fa-plus me-1"></span>
{% trans 'Add Note' %}
</a>
</div>
<table class="table fs-9 mb-0 table-responsive">
<thead>
@ -214,21 +217,13 @@
</tbody>
</table>
</div>
<div class="row align-items-center justify-content-between py-2 pe-0 fs-9">
<div class="col-auto 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="col-auto d-flex">
<button class="page-link" data-list-pagination="prev"><span class="fas fa-chevron-left"></span></button>
<ul class="mb-0 pagination"></ul>
<button class="page-link pe-0" data-list-pagination="next"><span class="fas fa-chevron-right"></span></button>
</div>
</div>
{% include 'partials/pagination.html' %}
</div>
</div>
</div>
</div>
</div>
{% include 'partials/notes.html' %}
</div>
{% endblock %}

View File

@ -436,15 +436,17 @@
</li>
{% else %}
<li class="nav-item">
<a class="nav-link px-3 d-block" href="{% url 'user_detail' user.staff.pk %}"> <span class="me-2 text-body align-bottom" data-feather="user"></span><span>{% translate 'profile'|capfirst %}</span></a>
<a class="nav-link px-3 d-block" href="{% url 'appointment:user_profile' request.user.id %}"> <span class="me-2 text-body align-bottom" data-feather="user"></span><span>{% translate 'profile'|capfirst %}</span></a>
</li>
{% endif %}
{% if user.dealer %}
<li class="nav-item">
<a class="nav-link px-3 d-block" href="{% url 'user_list' %}"><span class="me-2 text-body align-bottom" data-feather="users"></span>{{ _("Staff") }}</a>
</li>
<li class="nav-item">
<a class="nav-link px-3 d-block" href="{% url 'dealer_activity' %}"> <span class="me-2 text-body align-bottom" data-feather="lock"></span>{{ _("Activity") }}</a>
</li>
{% endif %}
<li class="nav-item">
<a class="nav-link px-3 d-block" href=""> <span class="me-2 text-body align-bottom" data-feather="settings"></span>Settings &amp; Privacy </a>
</li>

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,7 +73,13 @@
</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>
</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,144 +38,121 @@
<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><span class="fa fa-filter me-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">
<div class="d-flex align-items-center">
<div class="spinner-border mx-3 htmx-indicator" role="status"><span class="visually-hidden">Loading...</span></div>
<div class="search-box me-3">
<div class="search-box me-3">
<form class="position-relative">
<input class="form-control search-input search" name='search' type="search" placeholder="Search" aria-label="Search" hx-get="{% url 'car_list' %}" hx-trigger='keyup changed delay:500ms' hx-target='.table-responsive' hx-select='.table-responsive' hx-swap="innerHTML show:window:top" hx-indicator=".htmx-indicator"
<input class="form-control search-input search" name='search' type="search" placeholder="Search" aria-label="Search" hx-get="{% url 'car_list' %}" hx-trigger='keyup changed delay:500ms' 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()"
/>
/>
<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>
</form>
</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()"
>
<option selected="" value="" disabled>Select Make</option>
hx-on::after-request="filter_after_request()">
<option selected="" value="" disabled>{{ _("Make")}}</option>
{% for m in make %}
<option value="{{ m.pk }}">{{ m.name }}</option>
<option value="{{ m.pk }}">{{ m.get_local_name|default: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()"
>
<option selected="" value="" disabled>Select Model</option>
<option selected="" value="" disabled>{{ _("Model")}}</option>
{% for m in model %}
<option value="{{ m.pk }}">{{ m.name }}</option>
<option value="{{ m.pk }}">{{ m.get_local_name|default:m.name }}</option>
{% endfor %}
</select>
<select class="form-select year" name="year" aria-label="Default select example">
<option selected="" value="" disabled>Select Year</option>
<select class="form-select form-control-sm me-1 year" name="year" aria-label="Default select example">
<option selected="" value="" disabled>{{ _("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">
<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 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">
</div>
<div class="d-flex flex-wrap align-items-center justify-content-between py-3 pe-0 fs-9">
<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-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">
<p class="mb-0 fs-9 text-body">{{car.id_car_model}}</p>
<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.get_local_name|default:car.id_car_make.name}}</a></td>
<td class="align-middle white-space-nowrap start">
<p class="mb-0 fs-9 text-body">{{car.id_car_model.get_local_name|default:car.id_car_model.name}}</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">
<p class="fw-bo text-body fs-9 mb-0">{{car.id_car_trim}}</p>
<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,23 +160,16 @@
{% endfor %}
</tbody>
</table>
<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>
{% include 'partials/pagination.html' %}
</div>
</div>
</div>
</div>
@ -225,7 +195,9 @@
document.querySelector('.model-select').classList.remove('on-after-request')
}
function toggle_filter(){
document.querySelector('.filter').classList.toggle('d-none')
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

@ -7,7 +7,7 @@
<div class="row">
<div class="row justify-content-center">
<div class="col-md-8">
<div class="card">
<div class="card bg-body">
<div class="card-header">
{% if service.pk %}
{{ _("Update Service") }}
@ -19,7 +19,7 @@
<form method="post" action="">
{% csrf_token %}
{{ form|crispy }}
<button type="submit" class="btn btn-primary">{% trans 'Save' %}</button>
<button type="submit" class="btn btn-sm btn-primary">{% trans 'Save' %}</button>
</form>
</div>
</div>

View File

@ -4,16 +4,16 @@
{% block title %}{{ _("Expenses") }}{% endblock title %}
{% block content %}
<div class="row mt-4 mx-4">
<div class="d-flex justify-content-between mb-2 p-6">
<span></span>
<h3 class="text-center">{% trans "Services" %}</h3>
<a href="{% url 'item_service_create' %}" class="btn btn-sm btn-success ">{% trans "Add Service" %}</a>
<div class="row">
<div class="d-flex justify-content-between">
<h3 class="mb-2">{% trans "Services" %}</h3>
<a href="{% url 'item_service_create' %}" class="btn btn-sm btn-phoenix-primary ">{% trans "Add Service" %}</a>
</div>
<div class="mx-n4 px-4 mx-lg-n6 px-lg-6 bg-body-emphasis pt-7 border-y">
<div class="table-responsive mx-n1 px-1 scrollbar">
<table class="table fs-9 mb-0 border-top border-translucent">
<div class="table-responsive scrollbar transition">
<table class="table table-sm fs-9 mb-0 border-translucent">
<thead>
<tr>
<th class="sort white-space-nowrap align-middle" scope="col">{% trans "Item Number" %}</th>
@ -56,5 +56,4 @@
<div class="d-flex justify-content-center">
</div>
</div>
</div>
{% endblock %}

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

@ -1,65 +1,39 @@
{% load i18n static %}
<div class="pagination my-1">
<nav aria-label="Page navigation">
<ul class="pagination pagination-sm justify-content-center mb-0">
{% if page_obj.has_previous %}
<li class="page-item">
<a class="page-link" href="?page={{ page_obj.previous_page_number }}" aria-label="{% trans 'Previous' %}">
<span aria-hidden="true">
{% if LANGUAGE_CODE == 'ar' %}
<span class="fa fa-chevron-right"></span>
{% else %}
<span class="fa fa-chevron-left"></span>
{% endif %}
</span>
</a>
</li>
{% else %}
<li class="page-item disabled">
<span class="page-link" aria-hidden="true">
{% if LANGUAGE_CODE == 'ar' %}
<span class="fa fa-chevron-right"></span>
{% else %}
<span class="fa fa-chevron-left"></span>
{% endif %}
</span>
</li>
{% endif %}
<div class="row align-items-center justify-content-end py-4 pe-0 fs-9">
<div class="col-auto d-flex">
{% if page_obj.has_previous %}
<button class="page-link" data-list-pagination="prev" onclick="window.location.href='?page={{ page_obj.previous_page_number }}'">
<span class="fas {% if LANGUAGE_CODE == 'ar' %}fa-chevron-right{% else %}fa-chevron-left{% endif %}"></span>
</button>
{% else %}
<button class="page-link" disabled>
<span class="fas {% if LANGUAGE_CODE == 'ar' %}fa-chevron-right{% else %}fa-chevron-left{% endif %}"></span>
</button>
{% endif %}
<ul class="mb-0 pagination">
{% for num in page_obj.paginator.page_range %}
{% if page_obj.number == num %}
<li class="page-item active">
<span class="page-link">{{ num }}</span>
</li>
{% else %}
<li class="page-item">
<a class="page-link" href="?page={{ num }}">{{ num }}</a>
</li>
{% endif %}
{% if page_obj.number == num %}
<li class="page-item active">
<span class="page-link">{{ num }}</span>
</li>
{% else %}
<li class="page-item">
<a class="page-link" href="?page={{ num }}">{{ num }}</a>
</li>
{% endif %}
{% endfor %}
{% if page_obj.has_next %}
<li class="page-item">
<a class="page-link" href="?page={{ page_obj.next_page_number }}" aria-label="{% trans 'Next' %}">
<span aria-hidden="true">
{% if LANGUAGE_CODE == 'ar' %}
<span class="fa fa-chevron-left"></span>
{% else %}
<span class="fa fa-chevron-right"></span>
{% endif %}
</span>
</a>
</li>
{% else %}
<li class="page-item disabled">
<span class="page-link" aria-hidden="true">
{% if LANGUAGE_CODE == 'ar' %}
<span class="fa fa-chevron-left"></span>
{% else %}
<span class="fa fa-chevron-right"></span>
{% endif %}
</span>
</li>
{% endif %}
</ul>
</nav>
{% if page_obj.has_next %}
<button class="page-link pe-0" data-list-pagination="next" onclick="window.location.href='?page={{ page_obj.next_page_number }}'">
<span class="fas {% if LANGUAGE_CODE == 'ar' %}fa-chevron-left{% else %}fa-chevron-right{% endif %}"></span>
</button>
{% else %}
<button class="page-link pe-0" disabled>
<span class="fas {% if LANGUAGE_CODE == 'ar' %}fa-chevron-left{% else %}fa-chevron-right{% endif %}"></span>
</button>
{% endif %}
</div>
</div>

View File

@ -0,0 +1,15 @@
<div class="search-box me-2">
<form class="position-relative justify-content-between">
<input name="q" class="form-control form-control-sm search-input search" type="search" aria-label="Search" placeholder="{{ _('Search') }}" value="{{ request.GET.q }}"/>
<span class="fa fa-magnifying-glass search-box-icon"></span>
</form>
<div class="position-absolute top-50 start-50 translate-middle ">
{% if request.GET.q %}
<a href="{% url request.resolver_match.view_name %}" class="btn p-0"><span class="fas fa-times text-danger "></span></a>
{% endif %}
</div>
</div>

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>
@ -49,6 +49,10 @@
<i class="fa-regular fa-eye"></i>
{% 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

@ -52,24 +52,7 @@
/>
<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>
@ -109,24 +92,16 @@
hx-on::before-request="filter_before_request()"
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">
</div>
<div class="d-flex flex-wrap align-items-center justify-content-between py-3 pe-0 fs-9">
<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-translucent">
<thead>
<tr>
<th class="sort white-space-nowrap align-middle ps-0" scope="col" data-sort="projectName" style="width:10%;">
@ -147,29 +122,29 @@
<tbody class="list" id="project-list-table-body">
{% for tx in txs %}
<tr class="position-static">
<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">{{tx.customer.customer_name}}</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">{{tx.customer.address_1}}</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">{{tx.customer.phone}}</p>
</td>
<td class="align-middle time white-space-nowrap ps-0 projectName py-4">{{tx.info.make}}</td>
<td class="align-middle white-space-nowrap start ps-3 py-4">
<td class="align-middle time white-space-nowrap projectName">{{tx.info.make}}</td>
<td class="align-middle white-space-nowrap start">
<p class="mb-0 fs-9 text-body">{{tx.info.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">{{tx.info.vin}}</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">{{tx.info.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">{{tx.finance.total}}</p>
</td>
<td class="align-middle white-space-nowrap task ps-3 py-4">
<td class="align-middle white-space-nowrap task">
{% if tx.has_estimate %}
<p class="fw-bo text-body fs-9 mb-0">
<a href="{% url 'estimate_detail' tx.estimate.uuid %}">
@ -187,7 +162,7 @@
</p>
{% endif %}
</td>
<td class="align-middle white-space-nowrap task ps-3 py-4">
<td class="align-middle white-space-nowrap task">
{% if tx.has_invoice %}
<p class="fw-bo text-body fs-9 mb-0">
<a href="{% url 'invoice_detail' tx.invoice.uuid %}">
@ -235,18 +210,15 @@
{% endfor %}
</tbody>
</table>
<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">
{% if is_paginated %}
{% include 'partials/pagination.html' %}
{% endif %}
</div>
</div>
</div>
</div>
</div>
</div>
</div>
{% endblock %}
{% block customJS %}

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"
}
}