Compare commits

...

10 Commits

63 changed files with 2197 additions and 1352 deletions

3
.gitignore vendored
View File

@ -45,6 +45,9 @@ Makefile
.idea/**/dbnavigator.xml
**/migrations/**
**haikalbot/migrations/**
# Gradle
.idea/**/gradle.xml
.idea/**/libraries

4
.vscode/launch.json vendored
View File

@ -8,13 +8,13 @@
"name": "Python Debugger: Django",
"type": "debugpy",
"request": "launch",
"args": [
"args": [
"runserver",
"0.0.0.0:8000"
],
"django": true,
"autoStartBrowser": false,
"program": "${workspaceFolder}/manage.py"
"program": "C:\\Users\\user\\Desktop\\haikal_projects\\venv\\bin\\activate & ${workspaceFolder}/manage.py"
}
]
}

View File

@ -4,6 +4,8 @@ from django.conf.urls.static import static
from django.conf import settings
from django.conf.urls.i18n import i18n_patterns
from inventory import views
# from debug_toolbar.toolbar import debug_toolbar_urls
# import debug_toolbar
from schema_graph.views import Schema
@ -15,7 +17,7 @@ urlpatterns = [
path("api-auth/", include("rest_framework.urls")),
path("api/", include("api.urls")),
# path('dj-rest-auth/', include('dj_rest_auth.urls')),
]
]# + debug_toolbar_urls()
urlpatterns += i18n_patterns(
path("admin/", admin.site.urls),
path("switch_language/", views.switch_language, name="switch_language"),
@ -32,3 +34,5 @@ urlpatterns += i18n_patterns(
)
urlpatterns += static(settings.MEDIA_URL, document_root=settings.MEDIA_ROOT)

View File

@ -70,6 +70,7 @@ admin.site.register(models.Schedule)
admin.site.register(models.Notes)
admin.site.register(models.UserActivityLog)
admin.site.register(models.DealersMake)
admin.site.register(models.ExtraInfo)
@admin.register(models.Car)

View File

@ -40,3 +40,46 @@ def breadcrumbs(request):
url = "/" + "/".join(path[: i + 1]) + "/"
breadcrumbs.append({"name": path[i].capitalize(), "url": url})
return {"breadcrumbs": breadcrumbs}
def user_types(request):
"""
Sets various flags indicating the user's role types.
The flags are:
- request.is_dealer
- request.is_staff
- request.is_manager
- request.is_accountant
- request.is_sales
- request.is_inventory
:param request: The request object to set the flags upon.
:type request: HttpRequest
"""
request.is_dealer = False
request.is_staff = False
request.is_manager = False
request.is_accountant = False
request.is_sales = False
request.is_inventory = False
if hasattr(request.user, "dealer"):
request.is_dealer = True
elif hasattr(request.user, "staffmember"):
request.is_staff = True
staff = getattr(request.user.staffmember, "staff")
if "Accountant" in staff.groups.values_list("name", flat=True):
request.is_accountant = True
return {"is_accountant": True}
elif "Manager" in staff.groups.values_list("name", flat=True):
request.is_manager = True
return {"is_manager": True}
elif "Sales" in staff.groups.values_list("name", flat=True):
request.is_sales = True
return {"is_sales": True}
elif "Inventory" in staff.groups.values_list("name", flat=True):
request.is_inventory = True
return {"is_inventory": True}
return {}

View File

@ -30,6 +30,7 @@ from .models import (
Vendor,
Schedule,
Car,
VatRate,
CarTransfer,
CarFinance,
CustomCard,
@ -143,7 +144,7 @@ class StaffForm(forms.ModelForm):
)
class Meta:
model = Staff
fields = ["name", "arabic_name", "phone_number", "group"]
fields = ["name", "arabic_name", "phone_number", "address","image","group"]
# Dealer Form
@ -2091,3 +2092,16 @@ class CSVUploadForm(forms.Form):
# Reset file pointer for later processing
csv_file.file.seek(0)
return csv_file
class AdditionalFinancesForm(forms.Form):
additional_finances = forms.ModelMultipleChoiceField(
queryset=AdditionalServices.objects.all(),
widget=forms.CheckboxSelectMultiple(attrs={"class": "form-check-input"}),
required=False,
)
class VatRateForm(forms.ModelForm):
class Meta:
model = VatRate
fields = ['rate']

View File

@ -1,9 +1,9 @@
import logging
from django.http import Http404, HttpResponseForbidden
from django.shortcuts import redirect
from inventory import models
from django.utils import timezone
# from django.http import Http404, HttpResponseForbidden
# from django.shortcuts import redirect
# from inventory import models
# from django.utils import timezone
from inventory.utils import get_user_type
@ -102,17 +102,24 @@ class InjectDealerMiddleware:
request.is_inventory = False
if hasattr(request.user, "dealer"):
request.is_dealer = True
request.dealer = request.user.dealer
elif hasattr(request.user, "staffmember"):
request.is_staff = True
request.staff = request.user.staffmember.staff
request.dealer = request.staff.dealer
staff = getattr(request.user.staffmember, "staff")
if "Accountant" in staff.groups.values_list("name", flat=True):
staff_groups = staff.groups.values_list("name", flat=True)
if "Accountant" in staff_groups:
request.is_accountant = True
if "Manager" in staff.groups.values_list("name", flat=True):
elif "Manager" in staff_groups:
request.is_manager = True
if "Sales" in staff.groups.values_list("name", flat=True):
elif "Sales" in staff_groups:
request.is_sales = True
if "Inventory" in staff.groups.values_list("name", flat=True):
elif "Inventory" in staff_groups:
request.is_inventory = True
request.entity = request.dealer.entity
request.admin = request.dealer.entity.admin
except Exception:
pass
response = self.get_response(request)

View File

@ -19,7 +19,7 @@ class Migration(migrations.Migration):
initial = True
dependencies = [
('appointment', '0001_initial'),
('appointment', '__first__'),
('auth', '0012_alter_user_first_name_max_length'),
('contenttypes', '0002_remove_content_type_name'),
('django_ledger', '0021_alter_bankaccountmodel_account_model_and_more'),

View File

@ -43,6 +43,7 @@ from django.core.serializers.json import DjangoJSONEncoder
from appointment.models import StaffMember
from plans.quota import get_user_quota
from plans.models import UserPlan
from django.db.models import Q
# from plans.models import AbstractPlan
# from simple_history.models import HistoricalRecords
@ -190,6 +191,7 @@ class UnitOfMeasure(models.TextChoices):
class VatRate(models.Model):
dealer = models.ForeignKey("Dealer", on_delete=models.CASCADE)
rate = models.DecimalField(max_digits=5, decimal_places=2, default=Decimal("0.15"))
is_active = models.BooleanField(default=True)
created_at = models.DateTimeField(auto_now_add=True)
@ -243,6 +245,11 @@ class CarMake(models.Model, LocalizedNameMixin):
class Meta:
verbose_name = _("Make")
indexes = [
models.Index(fields=['name'], name='car_make_name_idx'),
models.Index(fields=['is_sa_import'], name='car_make_sa_import_idx'),
models.Index(fields=['car_type'], name='car_make_type_idx'),
]
class CarModel(models.Model, LocalizedNameMixin):
@ -272,6 +279,11 @@ class CarModel(models.Model, LocalizedNameMixin):
class Meta:
verbose_name = _("Model")
indexes = [
models.Index(fields=['id_car_make'], name='car_model_make_idx'),
models.Index(fields=['name'], name='car_model_name_idx'),
models.Index(fields=['id_car_make', 'name'], name='car_model_make_name_idx'),
]
class CarSerie(models.Model, LocalizedNameMixin):
@ -306,6 +318,12 @@ class CarSerie(models.Model, LocalizedNameMixin):
class Meta:
verbose_name = _("Series")
indexes = [
models.Index(fields=['id_car_model'], name='car_serie_model_idx'),
models.Index(fields=['year_begin', 'year_end'], name='car_serie_years_idx'),
models.Index(fields=['name'], name='car_serie_name_idx'),
models.Index(fields=['generation_name'], name='car_serie_generation_idx'),
]
class CarTrim(models.Model, LocalizedNameMixin):
@ -339,6 +357,11 @@ class CarTrim(models.Model, LocalizedNameMixin):
class Meta:
verbose_name = _("Trim")
indexes = [
models.Index(fields=['id_car_serie'], name='car_trim_serie_idx'),
models.Index(fields=['start_production_year', 'end_production_year'], name='car_trim_prod_years_idx'),
models.Index(fields=['name'], name='car_trim_name_idx'),
]
class CarEquipment(models.Model, LocalizedNameMixin):
@ -359,6 +382,11 @@ class CarEquipment(models.Model, LocalizedNameMixin):
class Meta:
verbose_name = _("Equipment")
indexes = [
models.Index(fields=['id_car_trim'], name='car_equipment_trim_idx'),
models.Index(fields=['year_begin'], name='car_equipment_year_idx'),
models.Index(fields=['name'], name='car_equipment_name_idx'),
]
class CarSpecification(models.Model, LocalizedNameMixin):
@ -390,6 +418,10 @@ class CarSpecification(models.Model, LocalizedNameMixin):
class Meta:
verbose_name = _("Specification")
indexes = [
models.Index(fields=['id_parent'], name='car_spec_parent_idx'),
models.Index(fields=['name'], name='car_spec_name_idx'),
]
class CarSpecificationValue(models.Model):
@ -406,6 +438,11 @@ class CarSpecificationValue(models.Model):
class Meta:
verbose_name = _("Specification Value")
indexes = [
models.Index(fields=['id_car_trim'], name='car_spec_val_trim_idx'),
models.Index(fields=['id_car_specification'], name='car_spec_val_spec_idx'),
models.Index(fields=['id_car_trim', 'id_car_specification'], name='car_spec_val_trim_spec_idx'),
]
class CarOption(models.Model, LocalizedNameMixin):
@ -437,6 +474,10 @@ class CarOption(models.Model, LocalizedNameMixin):
class Meta:
verbose_name = _("Option")
indexes = [
models.Index(fields=['id_parent'], name='car_option_parent_idx'),
models.Index(fields=['name'], name='car_option_name_idx'),
]
class CarOptionValue(models.Model):
@ -456,6 +497,12 @@ class CarOptionValue(models.Model):
class Meta:
verbose_name = _("Option Value")
indexes = [
models.Index(fields=['id_car_option'], name='car_opt_val_option_idx'),
models.Index(fields=['id_car_equipment'], name='car_opt_val_equipment_idx'),
models.Index(fields=['is_base'], name='car_opt_val_is_base_idx'),
models.Index(fields=['id_car_option', 'id_car_equipment'], name='cov_option_equipment_idx'),
]
class CarTransferStatusChoices(models.TextChoices):
@ -517,7 +564,7 @@ class AdditionalServices(models.Model, LocalizedNameMixin):
@property
def price_(self):
vat = VatRate.objects.filter(is_active=True).first()
vat = VatRate.objects.filter(dealer=self.dealer,is_active=True).first()
return (
Decimal(self.price + (self.price * vat.rate))
if self.taxable
@ -614,6 +661,26 @@ class Car(Base):
class Meta:
verbose_name = _("Car")
verbose_name_plural = _("Cars")
indexes = [
models.Index(fields=['vin'], name='car_vin_idx'),
models.Index(fields=['year'], name='car_year_idx'),
models.Index(fields=['status'], name='car_status_idx'),
models.Index(fields=['dealer'], name='car_dealer_idx'),
models.Index(fields=['vendor'], name='car_vendor_idx'),
models.Index(fields=['id_car_make'], name='car_make_idx'),
models.Index(fields=['id_car_model'], name='car_model_idx'),
models.Index(fields=['id_car_serie'], name='car_serie_idx'),
models.Index(fields=['id_car_trim'], name='car_trim_idx'),
models.Index(fields=['id_car_make', 'id_car_model'], name='car_make_model_idx'),
models.Index(fields=['id_car_make', 'year'], name='car_make_year_idx'),
models.Index(fields=['dealer', 'status'], name='car_dealer_status_idx'),
models.Index(fields=['vendor', 'status'], name='car_vendor_status_idx'),
models.Index(fields=['year', 'status'], name='car_year_status_idx'),
models.Index(fields=['status'], name='car_active_status_idx',
condition=Q(status=CarStatusChoices.AVAILABLE)),
]
def __str__(self):
make = self.id_car_make.name if self.id_car_make else "Unknown Make"
@ -823,6 +890,9 @@ class CarFinance(models.Model):
selling_price = models.DecimalField(
max_digits=14, decimal_places=2, verbose_name=_("Selling Price")
)
marked_price = models.DecimalField(
max_digits=14, decimal_places=2, verbose_name=_("Marked Price")
)
discount_amount = models.DecimalField(
max_digits=14,
decimal_places=2,
@ -855,7 +925,7 @@ class CarFinance(models.Model):
@property
def vat_amount(self):
vat = VatRate.objects.filter(is_active=True).first()
vat = VatRate.objects.filter(dealer=self.car.dealer, is_active=True).first()
if vat:
return (self.total_discount * Decimal(vat.rate)).quantize(Decimal("0.01"))
return Decimal("0.00")
@ -891,6 +961,12 @@ class CarFinance(models.Model):
class Meta:
verbose_name = _("Car Financial Details")
verbose_name_plural = _("Car Financial Details")
indexes = [
models.Index(fields=['car'], name='car_finance_car_idx'),
models.Index(fields=['cost_price'], name='car_finance_cost_price_idx'),
models.Index(fields=['selling_price'], name='car_finance_selling_price_idx'),
models.Index(fields=['discount_amount'], name='car_finance_discount_idx'),
]
class ExteriorColors(models.Model, LocalizedNameMixin):
@ -901,6 +977,10 @@ class ExteriorColors(models.Model, LocalizedNameMixin):
class Meta:
verbose_name = _("Exterior Colors")
verbose_name_plural = _("Exterior Colors")
indexes = [
models.Index(fields=['name'], name='exterior_color_name_idx'),
models.Index(fields=['arabic_name'], name='exterior_color_arabic_name_idx'),
]
def __str__(self):
return f"{self.name} ({self.rgb})"
@ -914,6 +994,10 @@ class InteriorColors(models.Model, LocalizedNameMixin):
class Meta:
verbose_name = _("Interior Colors")
verbose_name_plural = _("Interior Colors")
indexes = [
models.Index(fields=['name'], name='interior_color_name_idx'),
models.Index(fields=['arabic_name'], name='interior_color_arabic_name_idx'),
]
def __str__(self):
return f"{self.name} ({self.rgb})"
@ -932,6 +1016,11 @@ class CarColors(models.Model):
verbose_name = _("Color")
verbose_name_plural = _("Colors")
unique_together = ("car", "exterior", "interior")
indexes = [
models.Index(fields=['exterior'], name='car_colors_exterior_idx'),
models.Index(fields=['interior'], name='car_colors_interior_idx'),
models.Index(fields=['exterior', 'interior'], name='car_colors_ext_int_combo_idx'),
]
def __str__(self):
return f"{self.car} ({self.exterior.name}) ({self.interior.name})"
@ -1105,9 +1194,13 @@ class Dealer(models.Model, LocalizedNameMixin):
return True
return False
@property
def vat_rate(self):
return VatRate.objects.get(dealer=self,is_active=True).rate
class Meta:
verbose_name = _("Dealer")
verbose_name_plural = _("Dealers")
indexes = [models.Index(fields=["name"])]
# permissions = [
# ('change_dealer_type', 'Can change dealer type'),
# ]
@ -1137,6 +1230,12 @@ class Staff(models.Model, LocalizedNameMixin):
staff_type = models.CharField(
choices=StaffTypes.choices, max_length=255, verbose_name=_("Staff Type")
)
address = models.CharField(
max_length=200, blank=True, null=True, verbose_name=_("Address")
)
image = models.ImageField(
upload_to="staff/", blank=True, null=True, verbose_name=_("Image")
)
active = models.BooleanField(default=True, verbose_name=_("Active"))
created = models.DateTimeField(auto_now_add=True, verbose_name=_("Created"))
updated = models.DateTimeField(auto_now=True, verbose_name=_("Updated"))
@ -1188,7 +1287,7 @@ class Staff(models.Model, LocalizedNameMixin):
@property
def groups(self):
return CustomGroup.objects.filter(pk__in=[x.customgroup.pk for x in self.user.groups.all()])
return CustomGroup.objects.select_related("group").filter(pk__in=[x.customgroup.pk for x in self.user.groups.all()])
def clear_groups(self):
self.remove_superuser_permission()
@ -1199,21 +1298,28 @@ class Staff(models.Model, LocalizedNameMixin):
self.clear_groups()
try:
self.user.groups.add(group)
if "accountant" in group.name.lower() or "manager" in group.name.lower():
if "accountant" in group.name.lower():
self.add_superuser_permission()
except Exception as e:
print(e)
def add_superuser_permission(self):
pass
# self.dealer.entity.managers.add(self.user)
entity = self.dealer.entity
if entity.managers.count() == 0:
entity.managers.add(self.user)
def remove_superuser_permission(self):
pass
# self.dealer.entity.managers.remove(self.user)
entity = self.dealer.entity
if self.user in entity.managers.all():
entity.managers.remove(self.user)
class Meta:
verbose_name = _("Staff")
verbose_name_plural = _("Staff")
indexes = [
models.Index(fields=["name"]),
models.Index(fields=["staff_type"]),
]
permissions = []
def __str__(self):
@ -1369,6 +1475,13 @@ class Customer(models.Model):
class Meta:
verbose_name = _("Customer")
verbose_name_plural = _("Customers")
indexes = [
models.Index(fields=["title"]),
models.Index(fields=["first_name"]),
models.Index(fields=["last_name"]),
models.Index(fields=["email"]),
models.Index(fields=["phone_number"]),
]
def __str__(self):
# middle = f" {self.middle_name}" if self.middle_name else ""
@ -1507,6 +1620,11 @@ class Organization(models.Model, LocalizedNameMixin):
class Meta:
verbose_name = _("Organization")
verbose_name_plural = _("Organizations")
indexes = [
models.Index(fields=["name"]),
models.Index(fields=["email"]),
models.Index(fields=["phone_number"]),
]
def __str__(self):
return self.name
@ -1696,6 +1814,17 @@ class Lead(models.Model):
class Meta:
verbose_name = _("Lead")
verbose_name_plural = _("Leads")
indexes = [
models.Index(fields=["dealer"]),
models.Index(fields=["customer"]),
models.Index(fields=["organization"]),
models.Index(fields=["staff"]),
models.Index(fields=["first_name"]),
models.Index(fields=["last_name"]),
models.Index(fields=["email"]),
models.Index(fields=["phone_number"]),
models.Index(fields=["created"]),
]
def __str__(self):
return f"{self.first_name} {self.last_name}"
@ -1870,6 +1999,14 @@ class Schedule(models.Model):
class Meta:
ordering = ["-scheduled_at"]
verbose_name = _("Schedule")
verbose_name_plural = _("Schedules")
indexes = [
models.Index(fields=["dealer"]),
models.Index(fields=["customer"]),
models.Index(fields=["content_type", "object_id"]),
models.Index(fields=["scheduled_at"]),
]
class LeadStatusHistory(models.Model):
@ -2042,6 +2179,14 @@ class Opportunity(models.Model):
class Meta:
verbose_name = _("Opportunity")
verbose_name_plural = _("Opportunities")
indexes = [
models.Index(fields=["dealer"]),
models.Index(fields=["customer"]),
models.Index(fields=["car"]),
models.Index(fields=["lead"]),
models.Index(fields=["organization"]),
models.Index(fields=["created"]),
]
def __str__(self):
if self.customer:
@ -2066,6 +2211,19 @@ class Notes(models.Model):
class Meta:
verbose_name = _("Note")
verbose_name_plural = _("Notes")
indexes = [
models.Index(fields=['dealer'], name='note_dealer_idx'),
models.Index(fields=['created_by'], name='note_created_by_idx'),
models.Index(fields=['content_type'], name='note_content_type_idx'),
models.Index(fields=['content_type', 'object_id'], name='note_content_object_idx'),
models.Index(fields=['created'], name='note_created_date_idx'),
models.Index(fields=['updated'], name='note_updated_date_idx'),
models.Index(fields=['dealer', 'created'], name='note_dealer_created_idx'),
models.Index(fields=['content_type', 'object_id', 'created'],
name='note_content_obj_created_idx'),
]
def __str__(self):
return f"Note by {self.created_by.first_name} on {self.content_object}"
@ -2096,6 +2254,19 @@ class Tasks(models.Model):
class Meta:
verbose_name = _("Task")
verbose_name_plural = _("Tasks")
indexes = [
models.Index(fields=['dealer'], name='task_dealer_idx'),
models.Index(fields=['created_by'], name='task_created_by_idx'),
models.Index(fields=['content_type'], name='task_content_type_idx'),
models.Index(fields=['content_type', 'object_id'], name='task_content_object_idx'),
models.Index(fields=['created'], name='task_created_date_idx'),
models.Index(fields=['updated'], name='task_updated_date_idx'),
models.Index(fields=['dealer', 'created'], name='task_dealer_created_idx'),
models.Index(fields=['content_type', 'object_id', 'created'],
name='task_content_obj_created_idx'),
]
def __str__(self):
return f"Task by {self.created_by.email} on {self.content_object}"
@ -2124,6 +2295,17 @@ class Email(models.Model):
class Meta:
verbose_name = _("Email")
verbose_name_plural = _("Emails")
indexes = [
models.Index(fields=['created_by'], name='email_created_by_idx'),
models.Index(fields=['content_type'], name='email_content_type_idx'),
models.Index(fields=['content_type', 'object_id'], name='email_content_object_idx'),
models.Index(fields=['created'], name='email_created_date_idx'),
models.Index(fields=['updated'], name='email_updated_date_idx'),
models.Index(fields=['content_type', 'object_id', 'created'],
name='email_content_obj_created_idx'),
]
def __str__(self):
return f"Email by {self.created_by.first_name} on {self.content_object}"
@ -2149,6 +2331,17 @@ class Activity(models.Model):
class Meta:
verbose_name = _("Activity")
verbose_name_plural = _("Activities")
indexes = [
models.Index(fields=['created_by'], name='activity_created_by_idx'),
models.Index(fields=['content_type'], name='activity_content_type_idx'),
models.Index(fields=['content_type', 'object_id'], name='activity_content_object_idx'),
models.Index(fields=['created'], name='activity_created_date_idx'),
models.Index(fields=['updated'], name='activity_updated_date_idx'),
models.Index(fields=['content_type', 'object_id', 'created'],
name='a_content_obj_created_idx'),
]
def __str__(self):
return f"{self.get_activity_type_display()} by {self.created_by.get_full_name} on {self.content_object}"
@ -2167,6 +2360,12 @@ class Notification(models.Model):
verbose_name_plural = _("Notifications")
ordering = ["-created"]
indexes = [
models.Index(fields=['user'], name='notification_user_idx'),
models.Index(fields=['is_read'], name='notification_is_read_idx'),
models.Index(fields=['created'], name='notification_created_date_idx'),
]
def __str__(self):
return self.message
@ -2230,6 +2429,12 @@ class Vendor(models.Model, LocalizedNameMixin):
class Meta:
verbose_name = _("Vendor")
verbose_name_plural = _("Vendors")
indexes = [
models.Index(fields=['slug'], name='vendor_slug_idx'),
models.Index(fields=['active'], name='vendor_active_idx'),
models.Index(fields=['crn'], name='vendor_crn_idx'),
models.Index(fields=['vrn'], name='vendor_vrn_idx'),
]
def __str__(self):
return self.name
@ -2422,7 +2627,7 @@ class SaleOrder(models.Model):
blank=True,
)
comments = models.TextField(blank=True, null=True)
formatted_order_id = models.CharField(max_length=10, unique=True, editable=False)
formatted_order_id = models.CharField(max_length=20, unique=True, editable=False)
# Status and Dates
status = models.CharField(
@ -2471,9 +2676,21 @@ class SaleOrder(models.Model):
)
class Meta:
verbose_name = "Sales Order"
verbose_name_plural = "Sales Orders"
verbose_name = _("Sales Order")
verbose_name_plural = _("Sales Orders")
ordering = ["-order_date"] # Order by most recent first
indexes = [
models.Index(fields=["dealer"]),
models.Index(fields=["estimate"]),
models.Index(fields=["invoice"]),
models.Index(fields=["opportunity"]),
models.Index(fields=["customer"]),
models.Index(fields=["status"]),
models.Index(fields=["order_date"]),
models.Index(fields=["expected_delivery_date"]),
models.Index(fields=["actual_delivery_date"]),
models.Index(fields=["cancelled_date"]),
]
def save(self, *args, **kwargs):
if not self.formatted_order_id:
@ -2531,6 +2748,14 @@ class CustomGroup(models.Model):
group = models.OneToOneField(
"auth.Group", verbose_name=_("Group"), on_delete=models.CASCADE
)
class Meta:
verbose_name = _("Custom Group")
verbose_name_plural = _("Custom Groups")
indexes = [
models.Index(fields=["name"]),
models.Index(fields=["dealer"]),
models.Index(fields=["group"]),
]
@property
def entity(self):
@ -2666,7 +2891,7 @@ class CustomGroup(models.Model):
app="inventory",
allowed_models=[
"saleorder",
"payment",
# "payment",
"staff",
"schedule",
"activity",
@ -2708,7 +2933,8 @@ class CustomGroup(models.Model):
"tasks",
"activity",
"payment",
'vendor'],
"vendor",
],
other_perms=[
"view_car",
"view_carlocation",
@ -2731,7 +2957,6 @@ class CustomGroup(models.Model):
"itemmodel",
"invoicemodel",
"vendormodel",
"journalentrymodel",
"purchaseordermodel",
"estimatemodel",
@ -2935,6 +3160,13 @@ class PoItemsUploaded(models.Model):
created_at = models.DateTimeField(auto_now_add=True)
updated_at = models.DateTimeField(auto_now=True)
class Meta:
verbose_name = _("PO Items")
verbose_name_plural = _("PO Items")
indexes = [
models.Index(fields=["po"]),
models.Index(fields=["item"]),
]
def get_name(self):
return self.item.item.name.split('||')
class ExtraInfo(models.Model):
@ -2944,7 +3176,13 @@ class ExtraInfo(models.Model):
- JSON data storage
- Tracking fields
"""
# Primary GenericForeignKey (main linked object)
dealer = models.ForeignKey(
Dealer,
on_delete=models.SET_NULL,
null=True,
blank=True,
related_name="extra_info"
)
content_type = models.ForeignKey(
ContentType,
on_delete=models.CASCADE,
@ -2986,7 +3224,9 @@ class ExtraInfo(models.Model):
models.Index(fields=['content_type', 'object_id']),
models.Index(fields=['related_content_type', 'related_object_id']),
]
verbose_name_plural = "Extra Info"
verbose_name_plural = _("Extra Info")
verbose_name = _("Extra Info")
def __str__(self):
return f"ExtraInfo for {self.content_object} ({self.content_type})"
@ -3012,8 +3252,8 @@ class ExtraInfo(models.Model):
related_content_type=related_content_type,
related_object_id=staff.pk
)
return [x.content_object.sale_orders.first() for x in qs if x.content_object.sale_orders.first()]
# qs = qs.select_related("customer","estimate","invoice")
return [x.content_object.sale_orders.select_related("customer","estimate","invoice").first() for x in qs if x.content_object.sale_orders.first()]
@classmethod
def get_invoices(cls, staff=None, is_dealer=False):
if not staff and not is_dealer:

View File

@ -1,4 +1,5 @@
import logging
from .models import Dealer
from django.core.exceptions import ImproperlyConfigured,ValidationError
from django.contrib.auth.mixins import LoginRequiredMixin,PermissionRequiredMixin
from django_ledger.forms.bill import (
@ -30,7 +31,8 @@ from django_ledger.models import PurchaseOrderModel,EstimateModel,BillModel
from django.views.generic.detail import SingleObjectMixin
from django.views.generic.edit import UpdateView
from django.views.generic.base import RedirectView
from .models import Dealer
from django.views.generic.list import ListView
from django.utils.translation import gettext_lazy as _
logger = logging.getLogger(__name__)
@ -213,7 +215,7 @@ class PurchaseOrderModelUpdateView(LoginRequiredMixin,
if form.has_changed():
po_items_qs = ItemTransactionModel.objects.for_po(
entity_slug=self.kwargs['entity_slug'],
user_model=dealer.entity.admin,
user_model=self.request.admin,
po_pk=po_model.uuid,
).select_related('bill_model')
@ -314,12 +316,23 @@ class BasePurchaseOrderActionActionView(LoginRequiredMixin,
)
except ValidationError as e:
# --- Single-line log for ValidationError ---
print(f"User {user_username} encountered a validation error "
f"while performing action '{self.action_name}' on Purchase Order ID: {po_model.pk}. "
f"Error: {e}")
logger.warning(
f"User {user_username} encountered a validation error "
f"while performing action '{self.action_name}' on Purchase Order ID: {po_model.pk}. "
f"Error: {e}"
)
print(e)
except AttributeError as e:
print(f"User {user_username} encountered an AttributeError "
f"while performing action '{self.action_name}' on Purchase Order ID: {po_model.pk}. "
f"Error: {e}")
logger.warning(
f"User {user_username} encountered an AttributeError "
f"while performing action '{self.action_name}' on Purchase Order ID: {po_model.pk}. "
f"Error: {e}"
)
return response
class BillModelDetailView(LoginRequiredMixin, PermissionRequiredMixin, DetailView):
@ -406,19 +419,23 @@ class BillModelUpdateView(LoginRequiredMixin, PermissionRequiredMixin, UpdateVie
def get_form(self, form_class=None):
form_class = self.get_form_class()
dealer = get_object_or_404(Dealer,slug=self.kwargs['dealer_slug'])
entity_model = dealer.entity
entity_model = self.request.dealer.entity
if self.request.method == 'POST' and self.action_update_items:
return form_class(
entity_model=entity_model,
user_model=dealer.entity.admin,
user_model=self.request.admin,
instance=self.object
)
return form_class(
form = form_class(
entity_model=entity_model,
user_model=dealer.entity.admin,
user_model=self.request.admin,
**self.get_form_kwargs()
)
try:
form.initial['amount_paid'] = self.object.get_itemtxs_data()[1]["total_amount__sum"]
except Exception as e:
print(e)
return form
def get_form_class(self):
bill_model: BillModel = self.object
@ -642,3 +659,41 @@ class BaseBillActionView(LoginRequiredMixin,PermissionRequiredMixin, RedirectVie
level=messages.ERROR,
extra_tags='is-danger')
return response
class InventoryListView(LoginRequiredMixin, PermissionRequiredMixin, ListView):
template_name = 'django_ledger/inventory/inventory_list.html'
context_object_name = 'inventory_list'
http_method_names = ['get']
def get_context_data(self, *, object_list=None, **kwargs):
context = super(InventoryListView, self).get_context_data(**kwargs)
qs = self.get_queryset()
# evaluates the queryset...
context['qs_count'] = qs.count()
# ordered inventory...
ordered_qs = qs.is_ordered()
context['inventory_ordered'] = ordered_qs
# in transit inventory...
in_transit_qs = qs.in_transit()
context['inventory_in_transit'] = in_transit_qs
# on hand inventory...
received_qs = qs.is_received()
context['inventory_received'] = received_qs
context['page_title'] = _('Inventory')
context['header_title'] = _('Inventory Status')
context['header_subtitle'] = _('Ordered/In Transit/On Hand')
context['header_subtitle_icon'] = 'ic:round-inventory'
return context
def get_queryset(self):
if self.queryset is None:
self.queryset = ItemTransactionModel.objects.inventory_pipeline_aggregate(
entity_slug=self.kwargs['entity_slug'],
)
return super().get_queryset()

View File

@ -1,10 +1,10 @@
from decimal import Decimal
from django.urls import reverse
from inventory.tasks import create_coa_accounts, create_make_accounts
from django.contrib.auth.models import Group
from django.db.models.signals import post_save, post_delete
from django.dispatch import receiver
from appointment.models import Service
from django.utils.translation import gettext_lazy as _
from django.contrib.contenttypes.models import ContentType
from django.contrib.auth import get_user_model
@ -672,6 +672,7 @@ def create_dealer_settings(sender, instance, created, **kwargs):
:return: None
"""
if created:
models.VatRate.objects.create(dealer=instance)
models.DealerSettings.objects.create(
dealer=instance,
invoice_cash_account=instance.entity.get_all_accounts()
@ -941,6 +942,11 @@ def create_po_item_upload(sender,instance,created,**kwargs):
dealer = models.Dealer.objects.get(entity=instance.entity)
models.PoItemsUploaded.objects.create(dealer=dealer,po=instance, item=item, status="fulfilled")
@receiver(post_save, sender=models.Staff)
def add_service_to_staff(sender,instance,created,**kwargs):
if created:
for service in Service.objects.all():
instance.staff_member.services_offered.add(service)
##########################################################
######################Notification########################
@ -1092,6 +1098,6 @@ def bill_model_after_approve_notification(sender, instance, created, **kwargs):
message=f"""
Bill {instance.bill_number} has been approved.
<a href="{reverse('bill-detail', kwargs={'dealer_slug': dealer.slug, 'entity_slug':dealer.entity.slug, 'bill_pk': instance.pk})}" target="_blank">View</a>.
please comlete the bill payment.
please complete the bill payment.
"""
)

File diff suppressed because it is too large Load Diff

View File

@ -51,6 +51,11 @@ urlpatterns = [
views.DealerUpdateView.as_view(),
name="dealer_update",
),
path(
"dealers/<slug:slug>/dealer_vat_rate_update/",
views.dealer_vat_rate_update,
name="dealer_vat_rate_update",
),
# path('dealers/<int:pk>/delete/', views.DealerDeleteView.as_view(), name='dealer_delete'),
# CRM URLs
path(
@ -683,6 +688,17 @@ urlpatterns = [
views.preview_sale_order,
name="preview_sale_order",
),
path(
"<slug:dealer_slug>/sales/estimates/<uuid:pk>/update_estimate_discount/",
views.update_estimate_discount,
name="update_estimate_discount",
),
path(
"<slug:dealer_slug>/sales/estimates/<uuid:pk>/update_estimate_additionals/",
views.update_estimate_additionals,
name="update_estimate_additionals",
),
###############################################
# Invoice
###############################################

View File

@ -22,6 +22,7 @@ from django_ledger.models.items import ItemModel
from django.utils.translation import get_language
from django.core.exceptions import ObjectDoesNotExist
from django.utils.translation import gettext_lazy as _
from django.contrib.contenttypes.models import ContentType
from django_ledger.models.transactions import TransactionModel
from django_ledger.models.journal_entry import JournalEntryModel
@ -473,7 +474,7 @@ def set_invoice_payment(dealer, entity, invoice, amount, payment_method):
calculator = CarFinanceCalculator(invoice)
finance_data = calculator.get_finance_data()
# handle_account_process(invoice, amount, finance_data)
handle_account_process(invoice, amount, finance_data)
invoice.make_payment(amount)
invoice.save()
@ -996,13 +997,15 @@ class CarFinanceCalculator:
ADDITIONAL_SERVICES_KEY = "additional_services"
def __init__(self, model):
self.dealer = models.Dealer.objects.get(entity=model.entity)
self.model = model
self.vat_rate = self._get_vat_rate()
self.item_transactions = self._get_item_transactions()
self.additional_services = self._get_additional_services()
self.extra_info = models.ExtraInfo.objects.get(dealer=self.dealer,content_type=ContentType.objects.get_for_model(model),object_id=model.pk)
def _get_vat_rate(self):
vat = models.VatRate.objects.filter(is_active=True).first()
vat = models.VatRate.objects.filter(dealer=self.dealer,is_active=True).first()
if not vat:
raise ObjectDoesNotExist("No active VAT rate found")
return vat.rate
@ -1025,7 +1028,6 @@ class CarFinanceCalculator:
car_finance = self._get_nested_value(item, self.CAR_FINANCE_KEY)
car_info = self._get_nested_value(item, self.CAR_INFO_KEY)
unit_price = Decimal(car_finance.get("selling_price", 0))
print(item.item_model.car.finances)
return {
"item_number": item.item_model.item_number,
"vin": car_info.get("vin"),
@ -1037,6 +1039,7 @@ class CarFinanceCalculator:
"mileage": car_info.get("mileage"),
"cost_price": car_finance.get("cost_price"),
"selling_price": car_finance.get("selling_price"),
"marked_price": car_finance.get("marked_price"),
"discount": car_finance.get("discount_amount"),
"quantity": quantity,
"unit_price": unit_price,
@ -1070,13 +1073,15 @@ class CarFinanceCalculator:
Decimal(x.get("price_")) for x in self._get_additional_services()
)
total_discount = sum(
Decimal(
self._get_nested_value(item, self.CAR_FINANCE_KEY, "discount_amount")
)
for item in self.item_transactions
)
total_price_discounted = total_price - total_discount
total_discount = self.extra_info.data.get("discount")
# total_discount = sum(
# Decimal(
# self._get_nested_value(item, self.CAR_FINANCE_KEY, "discount_amount")
# )
# for item in self.item_transactions
# )
total_price_discounted = total_price - Decimal(total_discount)
total_vat_amount = total_price_discounted * self.vat_rate
return {
@ -1085,7 +1090,7 @@ class CarFinanceCalculator:
), # total_price_before_discount,
"total_price": round(total_price_discounted, 2), # total_price_discounted,
"total_vat_amount": round(total_vat_amount, 2), # total_vat_amount,
"total_discount": round(total_discount, 2),
"total_discount": round(Decimal(total_discount)),
"total_additionals": round(total_additionals, 2), # total_additionals,
"grand_total": round(
total_price_discounted + total_vat_amount + total_additionals, 2
@ -1139,7 +1144,6 @@ def get_item_transactions(txs):
data["customer"] = tx.invoice_model.customer
if bool(data):
transactions.append(data)
print(data)
return transactions
@ -1291,7 +1295,7 @@ def handle_account_process(invoice, amount, finance_data):
exc_info=True
)
print(e)
car.finances.is_sold = True
car.finances.save()
car.item_model.save()
@ -1428,7 +1432,7 @@ def handle_payment(request, order):
print("Failed to process payment:", data)
#
data = response.json()
print(data)
# order.status = AbstractOrder.STATUS.NEW
order.save()
#

View File

@ -88,7 +88,6 @@ from django_ledger.views import (
LedgerModelCreateView as LedgerModelCreateViewBase,
)
from django_ledger.forms.account import AccountModelCreateForm, AccountModelUpdateForm
from django_ledger.views.inventory import InventoryListView as InventoryListViewBase
from django_ledger.views.entity import (
EntityModelDetailBaseView,
EntityModelDetailHandlerView,
@ -143,6 +142,7 @@ from .override import (
BillModelDetailView as BillModelDetailViewBase,
BillModelUpdateView as BillModelUpdateViewBase,
BaseBillActionView as BaseBillActionViewBase,
InventoryListView as InventoryListViewBase,
)
from django_ledger.models import (
@ -440,8 +440,16 @@ class ManagerDashboard(LoginRequiredMixin, TemplateView):
transfer_cars = models.Car.objects.filter(
dealer=dealer, status=models.CarStatusChoices.TRANSFER
).count()
reserved_percentage = reserved_cars / total_cars * 100
sold_percentage = sold_cars / total_cars * 100
try:
reserved_percentage = reserved_cars / total_cars * 100
except ZeroDivisionError as e:
print(f"error: {e}")
try:
sold_percentage = sold_cars / total_cars * 100
except ZeroDivisionError as e:
print(f"error: {e}")
qs = (
models.Car.objects.values("id_car_make__name")
.annotate(count=Count("id"))
@ -1585,7 +1593,6 @@ class CarUpdateView(
def get_form(self, form_class=None):
form = super().get_form(form_class)
dealer = get_user_type(self.request)
print(dealer.get_vendors())
form.fields["vendor"].queryset = dealer.vendors.all()
return form
@ -2124,12 +2131,16 @@ class DealerDetailView(LoginRequiredMixin, PermissionRequiredMixin,DetailView):
context["cars_count"] = cars_count
context["allowed_users"] = dealer.user_quota
context["allowed_cars"] = dealer.car_quota
context["vatform"] = forms.VatRateForm(initial={"rate": dealer.vat_rate})
context["quota_display"] = (
f"{staff_count}/{dealer.user_quota}" if dealer.user_quota else "0"
)
return context
def dealer_vat_rate_update(request,slug):
dealer = get_object_or_404(models.Dealer,slug=slug)
models.VatRate.objects.filter(dealer=dealer).update(rate=request.POST.get("rate"))
return redirect("dealer_detail", slug=slug)
class DealerUpdateView(LoginRequiredMixin,PermissionRequiredMixin, SuccessMessageMixin, UpdateView):
"""
@ -2516,6 +2527,8 @@ def vendorDetailView(request, dealer_slug,slug):
return render(
request, template_name="vendors/view_vendor.html", context={"vendor": vendor}
)
class VendorCreateView(
@ -4298,11 +4311,14 @@ class EstimateListView(LoginRequiredMixin, PermissionRequiredMixin, ListView):
if any([self.request.is_dealer ,self.request.is_manager ,self.request.is_accountant]):
qs = models.ExtraInfo.objects.filter(
dealer=dealer,
content_type=ContentType.objects.get_for_model(EstimateModel),
related_content_type=ContentType.objects.get_for_model(models.Staff),
)
print(qs)
elif self.request.is_staff and self.request.is_sales:
qs = models.ExtraInfo.objects.filter(
dealer=dealer,
content_type=ContentType.objects.get_for_model(EstimateModel),
related_content_type=ContentType.objects.get_for_model(models.Staff),
related_object_id=staff.pk,
@ -4499,15 +4515,18 @@ def create_estimate(request, dealer_slug, slug=None):
if staff:=getattr(request.user.staffmember,'staff',None):
models.ExtraInfo.objects.create(
dealer=dealer,
content_object=estimate,
related_object=staff,
created_by=request.user,
)
else:
models.ExtraInfo.objects.create(
dealer=dealer,
content_object=estimate,
related_object=request.user,
created_by=request.user,
data={"vat_rate": 0.15,"discount": 0},
)
url = reverse(
@ -4612,14 +4631,22 @@ class EstimateDetailView(LoginRequiredMixin, PermissionRequiredMixin, DetailView
permission_required = ["django_ledger.view_estimatemodel"]
def get_context_data(self, **kwargs):
dealer = get_object_or_404(models.Dealer, slug=self.kwargs["dealer_slug"])
estimate = kwargs.get("object")
if estimate.get_itemtxs_data():
calculator = CarFinanceCalculator(estimate)
finance_data = calculator.get_finance_data()
print(finance_data)
invoice_obj = InvoiceModel.objects.all().filter(ce_model=estimate).first()
kwargs["data"] = finance_data
kwargs["invoice"] = invoice_obj
try:
cf = estimate.get_itemtxs_data()[0].first().item_model.car.finances
selected_items = cf.additional_services.filter(dealer=dealer)
form = forms.AdditionalFinancesForm()
form.initial["additional_finances"] = selected_items
kwargs["additionals_form"] = form
except Exception as e:
logger.error(e)
return super().get_context_data(**kwargs)
@ -4699,6 +4726,31 @@ def create_sale_order(request, dealer_slug, pk):
)
@login_required
@require_POST
def update_estimate_discount(request,dealer_slug,pk):
dealer = get_object_or_404(models.Dealer, slug=dealer_slug)
estimate = get_object_or_404(EstimateModel, pk=pk)
extra_info = models.ExtraInfo.objects.get(dealer=dealer,content_type=ContentType.objects.get_for_model(EstimateModel),object_id=estimate.pk)
discount_amount = request.POST.get("discount_amount",0)
extra_info.data.update({"discount":Decimal(discount_amount)})
extra_info.save()
return redirect("estimate_detail", dealer_slug=dealer_slug, pk=pk)
@login_required
@require_POST
def update_estimate_additionals(request,dealer_slug,pk):
dealer = get_object_or_404(models.Dealer, slug=dealer_slug)
form = forms.AdditionalFinancesForm(request.POST)
if request.method == "POST":
if form.is_valid():
estimate = get_object_or_404(EstimateModel, pk=pk)
car = estimate.get_itemtxs_data()[0].first().item_model.car
car.finances.additional_services.set(form.cleaned_data["additional_finances"])
car.finances.save()
return redirect("estimate_detail", dealer_slug=dealer_slug, pk=pk)
class SaleOrderDetail(LoginRequiredMixin,PermissionRequiredMixin,DetailView):
model = models.SaleOrder
template_name = "sales/orders/order_details.html"
@ -5331,6 +5383,7 @@ def PaymentCreateView(request, dealer_slug, pk):
model = invoice if invoice else bill
entity = dealer.entity
form = forms.PaymentForm()
breakpoint()
if request.method == "POST":
form = forms.PaymentForm(request.POST)
@ -5590,19 +5643,18 @@ class LeadListView(LoginRequiredMixin, PermissionRequiredMixin, ListView):
qs = models.Lead.objects.filter(dealer=dealer).exclude(status="converted")
if query:
qs = qs.filter(Q(first_name__icontains=query)
| Q(last_name__icontains=query)
| Q(id_car_make__name__icontains=query)
| Q(id_car_model__name__icontains=query)
| Q(email__icontains=query)
| Q(phone_number__icontains=query)
| Q(next_action__icontains=query)
| Q(staff__name__icontains=query))
if self.request.is_dealer:
| Q(last_name__icontains=query)
| Q(id_car_make__name__icontains=query)
| Q(id_car_model__name__icontains=query)
| Q(email__icontains=query)
| Q(phone_number__icontains=query)
| Q(next_action__icontains=query)
| Q(staff__name__icontains=query))
if self.request.is_dealer :#or self.request.is_manager:
return qs
if self.request.user.is_staff:
staff = getattr(self.request.user.staffmember, "staff", None)
return qs.filter(staff=staff)
if self.request.is_staff:
return qs.filter(staff=self.request.staff)
return models.Lead.objects.none()
@ -5659,7 +5711,7 @@ class LeadDetailView(LoginRequiredMixin, PermissionRequiredMixin, DetailView):
context["transfer_form"] = forms.LeadTransferForm()
context["transfer_form"].fields[
"transfer_to"
].queryset = models.Staff.objects.filter(
].queryset = models.Staff.objects.select_related("staff_member","staff_member__user").filter(
dealer=dealer,staff_member__user__groups__permissions__codename__contains="can_reassign_lead").exclude(staff_member__user=self.request.user).distinct()
context["activity_form"] = forms.ActivityForm()
@ -5779,15 +5831,12 @@ def lead_create(request,dealer_slug):
qs = form.fields["id_car_make"].queryset.filter(
is_sa_import=True, pk__in=dealer_make_list
)
form.fields["staff"].queryset = form.fields["staff"].queryset.filter(
dealer=dealer,staff_member__user__groups__permissions__codename__contains="add_lead").distinct()
form.fields["staff"].queryset = form.fields["staff"].queryset.select_related("staff_member","staff_member__user").filter(dealer=dealer,staff_member__user__groups__permissions__codename__contains="add_lead").distinct()
if hasattr(request.user.staffmember, "staff"):
staff = request.user.staffmember.staff
form.initial["staff"] = staff
if request.is_staff:
form.initial["staff"] = request.staff
form.fields["staff"].widget.attrs.update({"readonly":"true","required":"true"})
form.fields["staff"].queryset = models.Staff.objects.filter(dealer=dealer,pk=staff.pk)
form.fields["staff"].queryset = models.Staff.objects.filter(dealer=dealer,pk=request.staff.pk)
form.fields["id_car_make"].queryset = qs
form.fields["id_car_make"].choices = [
(obj.id_car_make, obj.get_local_name()) for obj in qs
@ -5801,7 +5850,7 @@ def lead_create(request,dealer_slug):
@permission_required("inventory.view_lead", raise_exception=True)
def lead_tracking(request,dealer_slug):
dealer = get_object_or_404(models.Dealer,slug=dealer_slug)
staff = models.Staff.objects.filter(dealer=dealer, staff_member__user=request.user).first()
staff = models.Staff.objects.select_related("staff_member","staff_member__user").filter(dealer=dealer, staff_member__user=request.user).first()
if staff:
qs = models.Lead.objects.filter(dealer=dealer,staff=staff)
@ -5945,7 +5994,7 @@ class LeadUpdateView(LoginRequiredMixin, PermissionRequiredMixin, UpdateView):
form.fields[
"id_car_model"
].queryset = form.instance.id_car_make.carmodel_set.all()
form.fields["staff"].queryset = form.fields["staff"].queryset.filter(
form.fields["staff"].queryset = form.fields["staff"].queryset.select_related("staff_member","staff_member__user").filter(
dealer=dealer,staff_member__user__groups__permissions__codename__contains="add_lead").distinct()
return form
@ -6880,8 +6929,8 @@ class ItemServiceCreateView(
permission_required = ["inventory.add_additionalservices"]
def form_valid(self, form):
vat = models.VatRate.objects.get(is_active=True)
dealer = get_user_type(self.request)
vat = models.VatRate.objects.get(dealer=dealer,is_active=True)
form.instance.dealer = dealer
if form.instance.taxable:
form.instance.price = (form.instance.price * vat.rate) + form.instance.price
@ -6927,8 +6976,8 @@ class ItemServiceUpdateView(
def form_valid(self, form):
vat = models.VatRate.objects.get(is_active=True)
dealer = get_user_type(self.request)
vat = models.VatRate.objects.get(dealer=dealer,is_active=True)
form.instance.dealer = dealer
if form.instance.taxable:
form.instance.price = (form.instance.price * vat.rate) + form.instance.price
@ -7575,7 +7624,7 @@ class OrderListView(LoginRequiredMixin, PermissionRequiredMixin, ListView):
def get_queryset(self):
dealer = get_user_type(self.request)
qs = super().get_queryset()
qs = super().get_queryset().select_related("customer","estimate","invoice")
return qs.filter(estimate__entity=dealer.entity)
@ -8676,12 +8725,13 @@ class LedgerModelListView(LoginRequiredMixin,PermissionRequiredMixin, ListView,
show_visible = False
allow_empty = True
paginate_by = 30
permission_required = "django_ledger.view_ledgermodel"
def get_queryset(self):
qs = super().get_queryset()
dealer = get_user_type(self.request)
qs = qs.filter(entity=dealer.entity)
# dealer = get_object_or_404(models.Dealer, slug=self.kwargs["dealer_slug"])
# dealer = get_user_type(self.request)
qs = qs.filter(entity=self.request.entity)
qs = qs.select_related("billmodel", "invoicemodel")
qs = qs.order_by("-created")
if self.show_all:
@ -8694,8 +8744,7 @@ class LedgerModelListView(LoginRequiredMixin,PermissionRequiredMixin, ListView,
def get_context_data(self, **kwargs):
context = super().get_context_data(**kwargs)
dealer = get_user_type(self.request)
context["entity_slug"] = dealer.entity.slug
context["entity_slug"] = self.request.dealer.entity.slug
return context
@ -8742,16 +8791,14 @@ class LedgerModelCreateView(LedgerModelCreateViewBase):
permission_required = ["django_ledger.add_ledgermodel"]
def get_form(self, form_class=None):
dealer = get_user_type(self.request)
return LedgerModelCreateForm(
entity_slug=dealer.entity.slug,
user_model=dealer.entity.admin,
entity_slug=self.request.dealer.entity.slug,
user_model=self.request.entity.admin,
**self.get_form_kwargs(),
)
def form_valid(self, form):
dealer = get_user_type(self.request)
form.field["entity"] = dealer.entity
form.field["entity"] = self.request.dealer.entity
return super().form_valid(form)
def get_success_url(self):
@ -8995,7 +9042,7 @@ class JournalEntryModelTXSDetailView(JournalEntryModelTXSDetailViewBase):
"""
template_name = "ledger/journal_entry/journal_entry_txs.html"
@login_required
@permission_required("django_ledger.change_ledgermodel", raise_exception=True)
@ -10253,9 +10300,11 @@ def upload_cars(request, dealer_slug, pk=None):
car_make = get_make(manufacturer_name)
car_model = get_model(model_name, car_make)
if (
not all([car_make, car_model])
not all([car_make])
or (make.pk != car_make.pk)
or (model.pk != car_model.pk)
# not all([car_make, car_model])
# or (make.pk != car_make.pk)
# or (model.pk != car_model.pk)
):
logger.warning(
f"User {user_username} uploaded CSV with VIN '{row['vin']}' "
@ -10283,12 +10332,20 @@ def upload_cars(request, dealer_slug, pk=None):
vendor=vendor,
receiving_date=receiving_date,
)
if po_item:
models.CarFinance.objects.create(
car=car,
cost_price=po_item.item.unit_cost,
marked_price=0,
selling_price=0
)
car.add_colors(exterior=exterior, interior=interior)
cars_created += 1
logger.debug(
f"User {user_username} created Car ID: {car.pk} (VIN: {car.vin}). "
f"Count: {cars_created}."
)
if po_item:
po_item.status = "uploaded"
po_item.save()
@ -10368,12 +10425,3 @@ def bulk_update_car_price(request):
class InventoryListView(InventoryListViewBase):
template_name = "inventory/list.html"
permission_required = ["django_ledger.view_purchaseordermodel"]
def get_queryset(self):
dealer = get_user_type(self.request)
if self.queryset is None:
self.queryset = ItemTransactionModel.objects.inventory_pipeline_aggregate(
entity_slug=dealer.entity.slug,
)
return super().get_queryset()

View File

@ -1,126 +1,126 @@
annotated-types==0.7.0
anyio==4.9.0
arrow==1.3.0
asgiref==3.8.1
attrs==25.3.0
Babel==2.15.0
beautifulsoup4==4.13.4
blessed==1.21.0
cattrs==24.1.3
certifi==2025.1.31
cffi==1.17.1
charset-normalizer==3.4.1
click==8.2.1
colorama==0.4.6
crispy-bootstrap5==2024.10
cryptography==44.0.2
cssbeautifier==1.15.4
defusedxml==0.7.1
diff-match-patch==20241021
distro==1.9.0
Django==5.2.3
django-allauth==65.6.0
django-appointment==3.8.0
django-background-tasks==1.2.8
django-bootstrap5==25.1
django-ckeditor==6.7.2
django-cors-headers==4.7.0
django-countries==7.6.1
django-crispy-forms==2.3
django-easy-audit==1.3.7
django-extensions==3.2.3
django-filter==25.1
django-import-export==4.3.7
django-js-asset==3.1.2
django-ledger==0.7.7
django-manager-utils==3.1.5
django-next-url-mixin==0.4.0
django-ordered-model==3.7.4
django-phonenumber-field==8.0.0
django-picklefield==3.3
django-plans==2.0.0
django-q2==1.8.0
django-query-builder==3.2.0
django-schema-graph==3.1.0
django-sequences==3.0
django-tables2==2.7.5
django-treebeard==4.7.1
django-widget-tweaks==1.5.0
djangorestframework==3.15.2
djhtml==3.0.7
djlint==1.36.4
docopt==0.6.2
EditorConfig==0.17.0
Faker==37.3.0
fleming==0.7.0
fonttools==4.57.0
fpdf==1.7.2
fpdf2==2.8.3
greenlet==3.2.2
h11==0.14.0
httpcore==1.0.7
httpx==0.28.1
icalendar==6.1.2
idna==3.10
jiter==0.9.0
jsbeautifier==1.15.4
json5==0.12.0
jsonpatch==1.33
jsonpointer==3.0.0
jwt==1.3.1
langchain==0.3.25
langchain-core==0.3.61
langchain-ollama==0.3.3
langchain-text-splitters==0.3.8
langsmith==0.3.42
luhnchecker==0.0.12
Markdown==3.8
markdown-it-py==3.0.0
mdurl==0.1.2
num2words==0.5.14
numpy==2.2.4
ofxtools==0.9.5
ollama==0.4.8
openai==1.68.2
opencv-python==4.11.0.86
orjson==3.10.18
packaging==24.2
pandas==2.2.3
pathspec==0.12.1
phonenumbers==8.13.42
pillow==11.2.1
pycparser==2.22
pydantic==2.10.6
pydantic_core==2.27.2
Pygments==2.19.1
python-dateutil==2.9.0.post0
python-slugify==8.0.4
python-stdnum==1.20
pytz==2025.2
pyvin==0.0.2
PyYAML==6.0.2
pyzbar==0.1.9
redis==3.5.3
regex==2024.11.6
requests==2.32.3
requests-toolbelt==1.0.0
rich==14.0.0
ruff==0.11.10
setuptools==80.3.0
six==1.17.0
sniffio==1.3.1
soupsieve==2.7
SQLAlchemy==2.0.41
sqlparse==0.5.3
suds==1.2.0
swapper==1.3.0
tablib==3.8.0
tenacity==9.1.2
text-unidecode==1.3
tqdm==4.67.1
types-python-dateutil==2.9.0.20250516
typing_extensions==4.13.0
tzdata==2025.2
urllib3==2.3.0
wcwidth==0.2.13
zstandard==0.23.0
annotated-types
anyio
arrow
asgiref
attrs
Babel
beautifulsoup4
blessed
cattrs
certifi
cffi
charset-normalizer
click
colorama
crispy-bootstrap5
cryptography
cssbeautifier
defusedxml
diff-match-patch
distro
Django
django-allauth
django-appointment
django-background-tasks
django-bootstrap5
django-ckeditor
django-cors-headers
django-countries
django-crispy-forms
django-easy-audit
django-extensions
django-filter
django-import-export
django-js-asset
django-ledger
django-manager-utils
django-next-url-mixin
django-ordered-model
django-phonenumber-field
django-picklefield
django-plans
django-q2
django-query-builder
django-schema-graph
django-sequences
django-tables2
django-treebeard
django-widget-tweaks
djangorestframework
djhtml
djlint
docopt
EditorConfig
Faker
fleming
fonttools
fpdf
fpdf2
greenlet
h11
httpcore
httpx
icalendar
idna
jiter
jsbeautifier
json5
jsonpatch
jsonpointer
jwt
langchain
langchain-core
langchain-ollama
langchain-text-splitters
langsmith
luhnchecker
Markdown
markdown-it-py
mdurl
num2words
numpy
ofxtools
ollama
openai
opencv-python
orjson
packaging
pandas
pathspec
phonenumbers
pillow
pycparser
pydantic
pydantic_core
Pygments
python-dateutil
python-slugify
python-stdnum
pytz
pyvin
PyYAML
pyzbar
redis
regex
requests
requests-toolbelt
rich
ruff
setuptools
six
sniffio
soupsieve
SQLAlchemy
sqlparse
suds
swapper
tablib
tenacity
text-unidecode
tqdm
types-python-dateutil
typing_extensions
tzdata
urllib3
wcwidth
zstandard

9
static/css/all.min.css vendored Normal file

File diff suppressed because one or more lines are too long

View File

@ -45,7 +45,7 @@
}
.form-control, .form-select {
text-align: center;
/* text-align: center; */
display: flex;
align-items: center;
justify-content: center;
@ -130,4 +130,5 @@ html[dir="rtl"] .form-icon-container .form-control {
@keyframes spin {
to { transform: rotate(360deg); }
}
}

6
static/js/fontawesome.min.js vendored Normal file

File diff suppressed because one or more lines are too long

2
t1.py
View File

@ -19,4 +19,4 @@ def get_models_for_make():
models = get_models_for_make()
for model in models:
print(model["Model_Name"])
print(model["Model_Name"])

View File

@ -26,19 +26,19 @@
<meta name="msapplication-TileImage" content="{% static 'images/logos/logo-d.png' %}">
<meta name="theme-color" content="#ffffff">
<script src="{% static 'vendors/simplebar/simplebar.min.js' %}"></script>
{% comment %} <script src="{% static 'vendors/simplebar/simplebar.min.js' %}"></script> {% endcomment %}
<script src="{% static 'js/config.js' %}"></script>
<script src="{% static 'js/sweetalert2.all.min.js' %}"></script>
<link href="{% static 'vendors/mapbox-gl/mapbox-gl.css' %}" rel="stylesheet">
<link href="{% static 'vendors/swiper/swiper-bundle.min.css' %}" rel="stylesheet">
{% comment %} <link href="{% static 'vendors/mapbox-gl/mapbox-gl.css' %}" rel="stylesheet"> {% endcomment %}
{% comment %} <link href="{% static 'vendors/swiper/swiper-bundle.min.css' %}" rel="stylesheet"> {% endcomment %}
<link rel="preconnect" href="https://fonts.googleapis.com">
<link rel="preconnect" href="https://fonts.gstatic.com" crossorigin="">
<link href="https://fonts.googleapis.com/css2?family=Nunito+Sans:wght@300;400;600;700;800;900&amp;display=swap" rel="stylesheet">
<link href="{% static 'vendors/simplebar/simplebar.min.css' %}" rel="stylesheet">
{% comment %} <link href="{% static 'vendors/simplebar/simplebar.min.css' %}" rel="stylesheet"> {% endcomment %}
<link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/@emran-alhaddad/saudi-riyal-font/index.css">
<link href="{% static 'vendors/flatpickr/flatpickr.min.css' %}" rel="stylesheet">
{% comment %} <link href="{% static 'vendors/flatpickr/flatpickr.min.css' %}" rel="stylesheet"> {% endcomment %}
<link href="{% static 'css/custom.css' %}" rel="stylesheet">
<link rel="stylesheet" href="https://unicons.iconscout.com/release/v4.0.8/css/line.css">
{% comment %} <link rel="stylesheet" href="https://unicons.iconscout.com/release/v4.0.8/css/line.css"> {% endcomment %}
{% if LANGUAGE_CODE == 'ar' %}
<link href="{% static 'css/theme-rtl.min.css' %}" type="text/css" rel="stylesheet" id="style-rtl">
<link href="{% static 'css/user-rtl.min.css' %}" type="text/css" rel="stylesheet" id="user-style-rtl">
@ -46,9 +46,9 @@
<link href="{% static 'css/theme.min.css' %}" type="text/css" rel="stylesheet" id="style-default">
<link href="{% static 'css/user.min.css' %}" type="text/css" rel="stylesheet" id="user-style-default">
{% endif %}
<script src="{% static 'js/main.js' %}"></script>
<script src="{% static 'js/jquery.min.js' %}"></script>
<script src="{% static 'js/echarts.js' %}"></script>
{% comment %} <script src="{% static 'js/main.js' %}"></script> {% endcomment %}
{% comment %} <script src="{% static 'js/jquery.min.js' %}"></script> {% endcomment %}
{% comment %} <script src="{% static 'js/echarts.js' %}"></script> {% endcomment %}
{% block customCSS %}
@ -79,9 +79,9 @@
{% include 'footer.html' %}
</div>
</main>
<script src="{% static 'js/djetler.bundle.js' %}"></script>
<script src="{% static 'js/js-utils.js' %}"></script>
<script src="{% static 'js/modal/show_modal.js' %}"></script>
{% comment %} <script src="{% static 'js/djetler.bundle.js' %}"></script>
<script src="{% static 'js/js-utils.js' %}"></script> {% endcomment %}
{% comment %} <script src="{% static 'js/modal/show_modal.js' %}"></script> {% endcomment %}
<!-- ===============================================-->
<!-- JavaScripts-->
@ -90,31 +90,30 @@
<!--1-->
<script src="{% static 'vendors/bootstrap/bootstrap.min.js' %}"></script>
<script src="{% static 'vendors/anchorjs/anchor.min.js' %}"></script>
<script src="{% static 'vendors/is/is.min.js' %}"></script>
{% comment %} <script src="{% static 'vendors/anchorjs/anchor.min.js' %}"></script>
<script src="{% static 'vendors/is/is.min.js' %}"></script> {% endcomment %}
<!--2-->
<script src="{% static 'vendors/fontawesome/all.min.js' %}"></script>
<script src="{% static 'vendors/lodash/lodash.min.js' %}"></script>
<script src="{% static 'vendors/list.js/list.min.js' %}"></script>
{% comment %} <script src="{% static 'vendors/list.js/list.min.js' %}"></script> {% endcomment %}
<script src="{% static 'vendors/feather-icons/feather.min.js' %}"></script>
<script src="{% static 'vendors/dayjs/dayjs.min.js' %}"></script>
{% comment %} <script src="{% static 'vendors/dayjs/dayjs.min.js' %}"></script> {% endcomment %}
<script src="{% static 'js/phoenix.js' %}"></script>
<script src="{% static 'js/apexcharts.js' %}"></script>
<script src="{% static 'vendors/echarts/echarts.min.js' %}"></script>
<script src="{% static 'js/crm-analytics.js' %}"></script>
<script src="{% static 'js/travel-agency-dashboard.js' %}"></script>
{% comment %} <script src="{% static 'js/apexcharts.js' %}"></script> {% endcomment %}
{% comment %} <script src="{% static 'vendors/echarts/echarts.min.js' %}"></script> {% endcomment %}
{% comment %} <script src="{% static 'js/crm-analytics.js' %}"></script> {% endcomment %}
{% comment %} <script src="{% static 'js/travel-agency-dashboard.js' %}"></script>
<script src="{% static 'js/crm-dashboard.js' %}"></script>
<script src="{% static 'js/projectmanagement-dashboard.js' %}"></script>
<script src="{% static 'js/projectmanagement-dashboard.js' %}"></script> {% endcomment %}
<script src="{% static 'vendors/mapbox-gl/mapbox-gl.js' %}"></script>
<script src="{% static 'vendors/turf.min.js' %}"></script>
{% comment %} <script src="{% static 'vendors/mapbox-gl/mapbox-gl.js' %}"></script> {% endcomment %}
{% comment %} <script src="{% static 'vendors/turf.min.js' %}"></script> {% endcomment %}
<script src="{% static 'vendors/htmx.min.js' %}"></script>
<script src="{% static 'vendors/swiper/swiper-bundle.min.js' %}"></script>
<script src="{% static 'vendors/flatpickr/flatpickr.min.js' %}"></script>
{% comment %} <script src="{% static 'vendors/swiper/swiper-bundle.min.js' %}"></script>
<script src="{% static 'vendors/flatpickr/flatpickr.min.js' %}"></script> {% endcomment %}
<script>
{% if entity_slug %}
let entitySlug = "{{ view.kwargs.entity_slug }}"
{% endif %}

View File

@ -5,7 +5,7 @@
{% load crispy_forms_filters %}
{% block content %}
<div class="row justify-content-center">
{% comment %} <div class="row justify-content-center">
<div class="col-lg-6">
<div class="card shadow-sm">
<div class="card-header bg-light py-3">
@ -46,5 +46,63 @@
</form>
</div>
</div>
</div>
</div> {% endcomment %}
<!---->
<div class="row justify-content-center mt-5 mb-3">
<div class="col-lg-8 col-md-10">
<div class="card shadow-sm border-0 rounded-3">
<div class="card-header bg-gray-200 py-3 border-0 rounded-top-3">
<h3 class="mb-0 fs-4 text-center text-white">
{% trans 'Create Bill' %}
</h3>
</div>
<div class="card-body bg-light-subtle">
<form action="{{ form_action_url }}" method="post" id="djl-bill-model-create-form-id">
<div class="card-body">
{% csrf_token %}
{% if po_model %}
<div class="text-center mb-4">
<h3 class="h5">{% trans 'Bill for' %} {{ po_model.po_number }}</h3>
<p class="text-muted mb-3">{% trans 'Bill for' %} {{ po_model.po_title }}</p>
<div class="d-flex flex-column gap-2">
{% for itemtxs in po_itemtxs_qs %}
<span class="badge bg-secondary">{{ itemtxs }}</span>
{% endfor %}
</div>
</div>
{% endif %}
<div class="mb-4">
{{ form|crispy }}
</div>
</div>
<div class="card-footer bg-transparent">
<div class="d-grid gap-2">
<button type="submit"
id="djl-bill-create-button"
class="btn btn-phoenix-primary btn-lg">{% trans 'Create' %}
</button>
<a href="{{request.META.HTTP_REFERER}}"
id="djl-bill-create-back-button"
class="btn btn-phoenix-secondary">{% trans 'Cancel' %}</a>
</div>
</div>
</form>
</div>
</div>
</div>
</div>
<!---->
{% endblock %}

View File

@ -44,12 +44,7 @@
<div class="card shadow-sm">
<div class="card-body">
{% include 'bill/includes/card_bill.html' with dealer_slug=request.dealer.slug bill=bill entity_slug=view.kwargs.entity_slug style='bill-detail' %}
<div class="d-grid mt-4">
<a href="{% url 'bill_list' request.dealer.slug %}" class="btn btn-phoenix-primary">
<i class="fas fa-arrow-left me-1"></i> {% trans 'Bill List' %}
</a>
</div>
</div>
</div>
</div>

View File

@ -9,7 +9,6 @@
<div class="container py-4">
<div class="row g-2">
<!-- Bill Form -->
<div class="col-12">
@ -18,11 +17,7 @@
<div class="card mb-2">
<div class="card-body">
{% include 'bill/includes/card_bill.html' with dealer_slug=request.dealer.slug bill=bill_model style='bill-detail' entity_slug=view.kwargs.entity_slug %}
<a href="{% url 'bill-detail' dealer_slug=request.dealer.slug entity_slug=view.kwargs.entity_slug bill_pk=bill_model.uuid %}"
class="btn btn-phoenix-secondary w-100 mb-2">
<i class="fas fa-arrow-left me-2"></i>{% trans 'Back to Bill Detail' %}
</a>
<form action="{% url 'bill-update' dealer_slug=request.dealer.slug entity_slug=view.kwargs.entity_slug bill_pk=bill_model.uuid %}" method="post">
{% csrf_token %}
@ -30,20 +25,21 @@
{{ form|crispy }}
</div>
<button type="submit" class="btn btn-phoenix-primary w-100 mb-2">
<button type="submit" class="btn btn-phoenix-primary mb-2 me-2">
<i class="fas fa-save me-2"></i>{% trans 'Save Bill' %}
</button>
<a href="{% url 'bill-detail' dealer_slug=request.dealer.slug entity_slug=view.kwargs.entity_slug bill_pk=bill_model.uuid %}"
class="btn btn-phoenix-secondary mb-2">
<i class="fas fa-arrow-left me-2"></i>{% trans 'Back to Bill Detail' %}
</a>
</form>
<a href="{% url 'bill_list' request.dealer.slug %}"
class="btn btn-phoenix-info w-100 mb-2">
<i class="fas fa-list me-2"></i>{% trans 'Bill List' %}
</a>
</div>
</div>
</div>
</div>
</div>
@ -51,7 +47,6 @@
</div>
<!-- Bill Item Formset -->
<div class="col-12">
{% bill_item_formset_table itemtxs_formset %}

View File

@ -1,20 +1,22 @@
{% load django_ledger %}
{% load i18n %}
<div id="djl-bill-card-widget" class="">
{% if not create_bill %}
{% if style == 'dashboard' %}
<!-- Dashboard Style Card -->
<div class="">
<div class="card-body ">
<div class="d-flex justify-content-between align-items-center mb-3">
<h6 class="text-uppercase text-secondary mb-0">
<div class="card-body">
<div class="d-flex justify-content-between align-items-center mb-3 text-primary">
<h6 class="text-uppercase text-primary mb-0">
<i class="fas fa-file-invoice me-2"></i>{% trans 'Bill' %}
</h6>
<span class="badge bg-{{ bill.get_status_badge_color }}">{{ bill.get_bill_status_display }}</span>
<span class="badge bg-{{ bill.get_status_badge_color }}">{{ bill.get_bill_status_display }}</span>
</div>
<h4 class="card-title">{{ bill.vendor.vendor_name }}</h4>
<p class="text-sm text-muted mb-4">{{ bill.vendor.address_1 }}</p>
{% if not bill.is_past_due %}
<p class="text-info mb-2">
<i class="fas fa-clock me-2"></i>{% trans 'Due in' %}: {{ bill.date_due | timeuntil }}
@ -91,12 +93,20 @@
<!-- Detail Style Card -->
<div class="">
<div class="card-header p-2 bg-{{ bill.get_status_badge_color }}">
<div class="d-flex align-items-center">
<i class="fas fa-file-invoice me-3 text-white"></i>
<h2 class="mb-0 text-white">
<div class="d-flex align-items-center justify-content-center mb-2 text-primary">
<i class="fas fa-file-invoice me-3 "></i>
<h4 class="mb-0 text-primary me-2">
{% trans 'Bill' %} {{ bill.bill_number }}
</h2>
</h4>
</div>
<a href="{% url 'bill_list' request.dealer.slug %}"
class="btn btn-phoenix-primary mb-2">
<i class="fas fa-long-arrow-alt-left me-2"></i>{% trans 'Back to Bill List' %}
</a>
</div>
<div class="card-body p-2 text-center">
{% if bill.is_draft %}
@ -221,12 +231,15 @@
<div class="d-flex flex-wrap gap-2 mt-2">
<!-- Update Button -->
{% if perms.django_ledger.change_billmodel%}
<a href="{% url 'bill-update' dealer_slug=dealer_slug entity_slug=entity_slug bill_pk=bill.uuid %}" class="btn btn-phoenix-primary">
<i class="fas fa-edit me-2"></i>{% trans 'Update' %}
</a>
<button class="btn btn-phoenix-primary" {% if not request.is_accountant %} disabled {% endif %}>
<a href="{% url 'bill-update' dealer_slug=dealer_slug entity_slug=entity_slug bill_pk=bill.uuid %}">
<i class="fas fa-edit me-2"></i>{% trans 'Update' %}
</a>
</button>
<!-- Mark as Draft -->
{% if bill.can_draft %}
<button class="btn btn-phoenix-success"
{% if not request.is_accountant %} disabled {% endif %}
onclick="showPOModal('Mark as Draft', '{% url 'bill-action-mark-as-draft' dealer_slug=request.dealer.slug entity_slug=entity_slug bill_pk=bill.pk %}', 'Mark as Draft')">
<i class="fas fa-check-circle me-2"></i>{% trans 'Mark as Draft' %}
</button>
@ -234,6 +247,7 @@
<!-- Mark as Review -->
{% if bill.can_review %}
<button class="btn btn-phoenix-warning"
{% if not request.is_accountant %} disabled {% endif %}
onclick="showPOModal('Mark as Review', '{% url 'bill-action-mark-as-review' dealer_slug=request.dealer.slug entity_slug=entity_slug bill_pk=bill.pk %}', 'Mark as Review')">
<i class="fas fa-check-circle me-2"></i>{% trans 'Mark as Review' %}
</button>
@ -245,6 +259,11 @@
<i class="fas fa-check-circle me-2"></i>{% trans 'Mark as Approved' %}
</button>
{% endif %}
{% if bill.can_approve and not request.is_manager %}
<button class="btn btn-phoenix-warning" disabled>
<i class="fas fa-hourglass-start me-2"></i><span class="text-warning">{% trans 'Waiting for Manager Approval' %}</span>
</button>
{% endif %}
<!-- Mark as Paid -->
{% if bill.can_pay %}
<button class="btn btn-phoenix-success"
@ -262,6 +281,7 @@
<!-- Cancel Button -->
{% if bill.can_cancel %}
<button class="btn btn-phoenix-danger"
{% if not request.is_accountant %} disabled {% endif %}
onclick="showPOModal('Mark as Canceled', '{% url 'bill-action-mark-as-canceled' dealer_slug=request.dealer.slug entity_slug=entity_slug bill_pk=bill.pk %}', 'Mark as Canceled')">
<i class="fas fa-check-circle me-2"></i>{% trans 'Mark as Canceled' %}
</button>
@ -277,6 +297,7 @@
<!-- Create Bill Card -->
{% if perms.django_ledger.add_billmodel%}
<div class=" bg-light">
<div class="card-body text-center p-5">
<a href="{% url 'django_ledger:bill-create' entity_slug=entity_slug %}"
class="text-primary">

View File

@ -3,15 +3,7 @@
{% if style == 'card_1' %}
<div class="card h-100" style="height: 25rem;">
<div class="card-header">
<h5 class="card-title fs-3 fw-light mb-0">
{% if title %}
{{ title }}
{% else %}
{% trans 'Notes' %}
{% endif %}
</h5>
</div>
<div class="card-body overflow-auto">
{% if notes_html %}
{{ notes_html|safe }}

View File

@ -46,7 +46,7 @@
<tr class="align-middle">
<!-- Item Column -->
<td>
<div class="d-flex flex-column">
<div class="d-flex flex-column ms-2">
{% for hidden_field in f.hidden_fields %}
{{ hidden_field }}
{% endfor %}

View File

@ -137,6 +137,29 @@
<div class="d-flex align-items-center mb-1"><span class="me-2 uil uil-file-check-alt"></span>
<h5 class="text-body-highlight fw-bold mb-0">{{ _("Lead Source")}}</h5>
</div>
{% if lead.source == 'REFERRALS' %}
<span class="ms-2 fa fa-users"></span>
{% elif lead.source == 'WHATSAPP' %}
<span class="ms-2 fa fa-whatsapp"></span>
{% elif lead.source == 'SHOWROOM' %}
<span class="ms-2 fa fa-building"></span>
{% elif lead.source == 'TIKTOK' %}
<span class="ms-2 fa fa-tiktok"></span>
{% elif lead.source == 'INSTAGRAM' %}
<span class="ms-2 fa fa-instagram"></span>
{% elif lead.source == 'X' %}
<span class="ms-2 fa fa-times-circle"></span>
{% elif lead.source == 'FACEBOOK' %}
<span class="ms-2 fa fa-facebook-f"></span>
{% elif lead.source == 'MOTORY' %}
<span class="ms-2 fa fa-car-side"></span>
{% elif lead.source == 'INFLUENCERS' %}
<span class="ms-2 fa fa-user-check"></span>
{% elif lead.source == 'YOUTUBE' %}
<span class="ms-2 fa fa-youtube"></span>
{% elif lead.source == 'CAMPAIGN' %}
<span class="ms-2 fa fa-bullhorn"></span>
{% endif %}
<span class="text-body-secondary">{{ lead.source|upper }}</span>
</div>
<div class="mb-3">
@ -490,6 +513,7 @@
<th class="sort align-middle pe-3 text-uppercase" scope="col" data-sort="sent" style="width:15%; min-width:130px">Assigned to</th>
<th class="sort align-middle text-start text-uppercase" scope="col" data-sort="date" style="min-width:165px">Due Date</th>
<th class="sort align-middle text-start text-uppercase" scope="col" data-sort="date" style="min-width:165px">Completed</th>
<th class="sort align-middle text-start text-uppercase" scope="col" data-sort="date" style="min-width:165px"></th>
</tr>
</thead>
<tbody class="list" id="all-tasks-table-body">

View File

@ -1,13 +1,14 @@
{% extends 'base.html' %}
{% load i18n static crispy_forms_filters %}
{% block title %}
{# Check if an 'object' exists in the context #}
{% if object %}
{% trans 'Update Lead'%}
{% trans 'Update Lead' %}
{% else %}
{% trans 'Add New Lead'%}
{% trans 'Add New Lead' %}
{% endif %}
{% endblock %}
{% block customcss %}
<style>
.htmx-indicator{
@ -17,49 +18,68 @@
.htmx-request .htmx-indicator{
opacity:1;
}
.htmx-request.htmx-indicator{
.htmx-request.htmx-indicator{ /* For elements with htmx-indicator itself becoming the target */
opacity:1;
}
/* Style for the inline spinner if needed */
.inline-spinner {
width: 1.5rem; /* Adjust size */
height: 1.5rem; /* Adjust size */
vertical-align: middle;
margin-left: 0.5rem; /* Space from the field */
}
</style>
{% endblock customcss %}
{% block content %}
<div class="container-fluid">
<h1>{% if object %}{{ _("Update Lead") }}{% else %}{{ _("Create New Lead") }}{% endif %}</h1>
<div class="row mb-3">
<div class="col-sm-6 col-md-8">
<div class="row justify-content-center mt-5 mb-3">
<div class="col-lg-8 col-md-10">
<div class="card shadow-sm border-0 rounded-3">
<div class="card-header bg-gray-300 py-3 border-0 rounded-top-3">
<h3 class="mb-0 fs-4 text-center">
{% if object %}
{{ _("Update Lead") }}
{% else %}
{{ _("Create New Lead") }}
{% endif %}
</h3>
</div>
<div class="card-body bg-light-subtle">
<form class="form" method="post">
{% csrf_token %}
{{ form|crispy }}
<form class="form" method="post">
{% csrf_token %}
{{ form|crispy }}
<div class="d-flex justify-content-start">
<button class="btn btn-sm btn-phoenix-success me-2" type="submit"><i class="fa-solid fa-floppy-disk me-1"></i>
<!--<i class="bi bi-save"></i> -->
{{ _("Save") }}
</button>
<a href="{{request.META.HTTP_REFERER}}" class="btn btn-sm btn-phoenix-danger"><i class="fa-solid fa-ban me-1"></i>{% trans "Cancel" %}</a>
</div>
</form>
<hr class="my-2">
<div class="d-grid gap-2 d-md-flex justify-content-md-center mt-3">
<button class="btn btn-phoenix-success btn-lg me-md-2" type="submit">
<i class="fa-solid fa-floppy-disk me-1"></i>
{{ _("Save") }}
</button>
<a href="{{ request.META.HTTP_REFERER }}" class="btn btn-phoenix-danger btn-lg">
<i class="fa-solid fa-ban me-1"></i>
{% trans "Cancel" %}
</a>
</div>
</form>
</div>
</div>
</div>
</div>
<script>
document.addEventListener('DOMContentLoaded', function() {
// First, create the spinner div (or use the existing one)
const spinner = document.createElement('div');
spinner.id = 'spinner';
spinner.className = 'htmx-indicator spinner-border inline-spinner';
spinner.setAttribute('role', 'status');
spinner.className = 'htmx-indicator spinner-border text-primary inline-spinner';
spinner.innerHTML = '<span class="visually-hidden">Loading...</span>';
const targetFieldDiv = document.getElementById('div_id_id_car_model');
// Find the form field you want to place it next to
// Replace 'id_your_field_name' with the actual ID of your form field
const targetField = document.getElementById('div_id_id_car_model');
if (targetField) {
// Insert the spinner right after the target field
targetField.parentNode.insertBefore(spinner, targetField.nextSibling);
if (targetFieldDiv) {
targetFieldDiv.parentNode.insertBefore(spinner, targetFieldDiv.nextSibling);
}
});
</script>

View File

@ -4,7 +4,7 @@
{% block content %}
<div class="row g-3 mt-4 mb-4">
<h2 class="mb-2">{{ _("Leads")|capfirst }}</h2>
<h2 class="mb-2">{{ _("Leads")|capfirst }}<li class="fas fa-bullhorn text-primary ms-2"></li></h2>
<!-- Action Tracking Modal -->
{% include "crm/leads/partials/update_action.html" %}

View File

@ -68,7 +68,7 @@
<div class="row justify-content-center">
<div class="col">
<div class="d-flex justify-content-between mb-3">
<h3>{{ _("Lead Tracking")}}</h3>
<h3>{{ _("Lead Tracking")}}<li class="fas fa-bullhorn text-primary ms-2"></li></h3>
</div>
<div class="row g-3">

View File

@ -5,7 +5,7 @@
{% block content %}
<div class="row g-3 mt-4">
<div class="col-12">
<h2 class="mb-3">{{ _("Opportunities") }}</h2>
<h2 class="mb-3">{{ _("Opportunities") }} <li class="fas fas fa-rocket text-primary ms-2"></li></h2>
</div>
<div class="col-12">
<div class="d-flex flex-column flex-md-row justify-content-between align-items-md-center gap-3 mb-4">

View File

@ -9,38 +9,38 @@
{% trans 'Add New Customer'%}
{% endif %}
{% endblock %}
{% block content %}
<link rel="stylesheet" href="{% static 'flags/sprite.css' %}">
<div class="row">
<div class="row mb-3">
<div class="col-sm-6 col-md-8">
<div class="d-sm-flex justify-content-between">
<h3 class="mb-3">
<div class="row justify-content-center mt-5 mb-3">
<div class="col-lg-8 col-md-10">
<div class="card shadow-sm border-o rounded-3">
<div class="card-header bg-gray-200 py-3 border-0 rounded-top-3">
<h3 class="mb-0 fs-4 text-center">
{% if customer.created %}
<i class="fa-solid fa-user"></i> {{ _("Edit Customer") }}
{% else %}
<i class="fa-solid fa-user"></i> {{ _("Add Customer") }}
{% endif %}
</h3>
</div>
</div>
</div>
<div class="row mb-3">
<div class="col-sm-6 col-md-8">
<div class="card-body bg-light-subtle">
<form method="post" class="form row g-3 needs-validation" enctype="multipart/form-data" novalidate>
{% csrf_token %}
{{ form|crispy }}
<div class="col-12">
<button class="btn btn-sm btn-phoenix-success me-2" type="submit"><i class="fa-solid fa-floppy-disk me-1"></i>
<!--<i class="bi bi-save"></i> -->
{{ _("Save") }}
</button>
<a href="{{request.META.HTTP_REFERER}}" class="btn btn-sm btn-phoenix-danger"><i class="fa-solid fa-ban me-1"></i>{% trans "Cancel" %}</a>
</div>
</form>
</div>
</div>
</div>
{% endblock %}
<hr class="my-2">
<div class="d-grid gap-2 d-md-flex justify-content-md-center mt-3">
<button class="btn btn-phoenix-success btn-lg me-md-2" type="submit"><i class="fa-solid fa-floppy-disk me-1"></i>{{ _("Save") }}</button>
<a href="{{request.META.HTTP_REFERER}}" class="btn btn-lg btn-phoenix-danger"><i class="fa-solid fa-ban me-1"></i>{% trans "Cancel" %}</a>
</div>
</form>
</div>
</div>
</div>
{%endblock %}

View File

@ -6,7 +6,7 @@
{% block content %}
<div class="row g-3 mt-4">
<h2 class="mb-2">{{ _("Customers")|capfirst }}</h2>
<h2 class="mb-2">{{ _("Customers")|capfirst }} <li class="fas fa-people-group text-primary ms-2"></li></h2>
<div class="row g-3 justify-content-between mb-4">
<div class="col-auto">
<div class="d-md-flex justify-content-between">

View File

@ -1,5 +1,5 @@
{% extends 'base.html' %}
{% load i18n static custom_filters%}
{% load i18n static custom_filters crispy_forms_filters %}
{%block title%}{%trans 'Profile'%} {%endblock%}
{% block content %}
<div class="container-fluid">
@ -180,6 +180,26 @@
</div>
</div>
</div>
<div class="col-12 col-lg-6">
<div class="card h-100">
<div class="bg-holder" style="background-image:url({% static 'images/bg/bg-left-20.png' %});background-position:left bottom;background-size:auto;"></div>
<div class="card-body d-flex flex-column justify-content-center position-relative">
<h4 class="mb-3">{{ _("VAT") }}</h4>
<div class="d-flex justify-content-center ">
<div class="text-center me-3">
<div class="row">
<form action="{% url 'dealer_vat_rate_update' request.dealer.slug %}" method="post">
{% csrf_token %}
{{vatform|crispy}}
<button class="btn btn-sm btn-phoenix-primary" type="submit">{% trans 'Update' %}</button>
</form>
</div>
</div>
</div>
</div>
</div>
</div>
</div>
</div>

View File

@ -64,6 +64,13 @@
</div>
</a>
</li>
<li class="nav-item">
<a class="nav-link" href="{% url 'inventort_list' request.dealer.slug request.dealer.entity.slug %}">
<div class="d-flex align-items-center">
<span class="nav-link-icon"><span class="fas fa-boxes"></span></span><span class="nav-link-text">{% trans "Inventory List"|capfirst %}</span>
</div>
</a>
</li>
{% endif %}
</ul>

View File

@ -28,16 +28,27 @@
aria-label="Close"></button>
</div>
{% endif %}
<div class="container-fluid m-0 {% if not vendor_exists %}disabled{% endif %}">
<form method="post" id="carForm" class="form needs-validation" novalidate>
{% csrf_token %}
{% include 'partials/form_errors.html' %}
<!---->
<div class="row justify-content-center mt-5 mb-3 {% if not vendor_exists %}disabled{% endif %}">
<div class="col-lg-8 col-md-10">
<div class="card shadow-sm border-0 rounded-3">
<div class="card-header bg-gray-200 py-3 border-0 rounded-top-3">
<h3 class="mb-0 fs-4 text-center text-white">
<h3 class="mb-3">{% trans 'Add Car' %} <li class="fas fa-car-on text-primary ms-2"></li></h3>
</h3>
</div>
<div class="card-body bg-light-subtle">
<form method="post" id="carForm" class="form needs-validation" novalidate>
{% csrf_token %}
{% include 'partials/form_errors.html' %}
<div class="d-flex flex-column">
<div class="d-flex flex-column flex-sm-grow-1 p-0">
<div class="row g-4">
<h3 class="mb-3">{% trans 'Add Car' %}</h3>
<!-- VIN Section -->
<div class="col-lg-12 col-xl-6">
<div class="col-lg-12 ">
<div class="card bg-body mb-3 shadow-sm">
<div class="card-body">
<!-- Improved VIN input with integrated buttons -->
@ -144,7 +155,7 @@
</div>
</div>
<div class="row g-3">
<div class="col-xl-6">
<div class="col-lg-12">
<div class="row">
<!--Vendor Field-->
<div class="col-lg-4 col-xl-4">
@ -202,28 +213,39 @@
</div>
</div>
</div>
<!--Save Buttons-->
<div class="btn-group mt-3">
<button type="submit"
<hr class="my-2">
<!--Save Buttons-->
<div class="d-grid gap-2 d-md-flex justify-content-md-center mt-3">
<button type="submit"
name="add_another"
value="true"
class="btn btn-phoenix-success me-1">
class="btn btn-lg btn-phoenix-success md-me-2">
{% trans "Save and Add Another" %}
</button>
<button type="submit"
name="go_to_stats"
value="true"
class="btn btn-phoenix-primary">
class="btn btn-lg btn-phoenix-primary">
{% trans "Save and Go to Inventory" %}
</button>
</div>
</div>
</div>
</div>
</div>
</div>
</form>
</form>
<!-- Modal sections remain largely unchanged -->
</div>
</div>
<!-- Modal sections remain largely unchanged -->
<!--Specification Modal-->
<div class="modal fade"
id="specificationsModal"
@ -312,7 +334,11 @@
</div>
</div>
</div>
</div>
</div>
</div>
<!---->
<script>
function getCookie(name) {
let cookieValue = null;

View File

@ -28,7 +28,7 @@
<div class="container-fluid" id="projectSummary">
<div class="row g-3 justify-content-between align-items-end mb-4">
<div class="col-12 col-sm-auto">
<h2 class="text-body-emphasis fw-bold mb-0">{{ _("Inventory") }}</h2>
<h2 class="text-body-emphasis fw-bold mb-0">{{ _("Inventory") }}<li class="fas fa-store text-primary ms-2"></li></h2>
</div>
</div>
<div class="row g-3 justify-content-between align-items-end mb-2">

View File

@ -14,12 +14,11 @@
<tbody>
{% for i in inventory_list %}
<tr class="hover-actions-trigger">
<td class="ps-2 fw-medium">{{ i.item_model__name }}</td>
<td class="text-center">{{ i.item_model__uom__name }}</td>
<td class="text-end pe-3">{{ i.total_quantity | floatformat:3 }}</td>
<td class="text-end pe-3 fw-bold text-primary">
<tr class="hover-actions-trigger">
<td class="ps-2 fw-medium">{{ i.item_model__name }}</td>
<td class="text-center">{{ i.item_model__uom__name }}</td>
<td class="text-end pe-3">{{ i.total_quantity | floatformat:3 }}</td>
<td class="text-end pe-3 fw-bold text-primary">
<span class="currency">{{CURRENCY}}</span>{{ i.total_value | currency_format }}
</td>
</tr>
@ -30,7 +29,7 @@
<td colspan="2"></td>
<td class="text-end pe-3 fw-bold">{% trans "Total Value" %}</td>
<td class="text-end pe-3 fw-bold text-success">
<span class="currency">{{CURRENCY}}</span>{{ inventory_total_value | currency_format }}
<span class="currency">{{CURRENCY}}</span>{{ inventory_total_value | currency_format }}
</td>
</tr>
</tfoot>

View File

@ -4,7 +4,7 @@
{% load i18n %}
{% block title %}{{ _("Add New Expense") }}{% endblock title %}
{% block content %}
<div class="row">
{% comment %} <div class="row">
<div class="row justify-content-center">
<div class="col-md-8">
<div class="card">
@ -13,9 +13,7 @@
<form method="post" action="">
{% csrf_token %}
{{ form|crispy }}
<!--<button type="submit" class="btn btn-primary">{% trans 'Save' %}</button> -->
{% comment %} <button class="btn btn-sm btn-success me-1" type="submit"><i class="fa-solid fa-floppy-disk"></i>{{ _("Save") }}</button> {% endcomment %}
<div class="d-flex justify-content-start">
<button class="btn btn-sm btn-phoenix-success me-2" type="submit"><i class="fa-solid fa-floppy-disk me-1"></i>{{ _("Save") }}</button>
<a href="{{request.META.HTTP_REFERER}}" class="btn btn-sm btn-phoenix-danger"><i class="fa-solid fa-ban me-1"></i>{% trans "Cancel" %}</a>
@ -26,6 +24,43 @@
</div>
</div>
</div>
</div>
</div> {% endcomment %}
<!---->
<div class="row justify-content-center mt-5 mb-3">
<div class="col-lg-8 col-md-10">
<div class="card shadow-sm border-0 rounded-3">
<div class="card-header bg-gray-200 py-3 border-0 rounded-top-3">
<h3 class="mb-0 fs-4 text-center text-white">
{{ _("Add Expense") }}
</h3>
</div>
<div class="card-body bg-light-subtle">
<form method="post" action="">
{% csrf_token %}
{{ form|crispy }}
<hr class="my-2">
<div class="d-grid gap-2 d-md-flex justify-content-md-center mt-3">
<button class="btn btn-lg btn-phoenix-success md-me-2" type="submit"><i class="fa-solid fa-floppy-disk me-1"></i>{{ _("Save") }}</button>
<a href="{{request.META.HTTP_REFERER}}" class="btn btn-lg btn-phoenix-danger"><i class="fa-solid fa-ban me-1"></i>{% trans "Cancel" %}</a>
</div>
</form>
</div>
</div>
</div>
</div>
<!---->
{% endblock content %}

View File

@ -4,7 +4,7 @@
{% load i18n %}
{% block title %}{{ _("Update Expense") }}{% endblock title %}
{% block content %}
<div class="row">
{% comment %} <div class="row">
<div class="row justify-content-center">
<div class="col-md-8">
<div class="card">
@ -19,6 +19,40 @@
</div>
</div>
</div>
</div>
</div> {% endcomment %}
<!---->
<div class="row justify-content-center mt-5 mb-3">
<div class="col-lg-8 col-md-10">
<div class="card shadow-sm border-0 rounded-3">
<div class="card-header bg-gray-200 py-3 border-0 rounded-top-3">
<h3 class="mb-0 fs-4 text-center text-white">
{{ _("Update Expense") }}
</h3>
</div>
<div class="card-body bg-light-subtle">
<form method="post" action="">
{% csrf_token %}
{{ form|crispy }}
<hr class="my-2">
<div class="d-grid gap-2 d-md-flex justify-content-md-center mt-3">
<button class="btn btn-lg btn-phoenix-success md-me-2" type="submit"><i class="fa-solid fa-floppy-disk me-1"></i>{{ _("Save") }}</button>
<a href="{{request.META.HTTP_REFERER}}" class="btn btn-lg btn-phoenix-danger"><i class="fa-solid fa-ban me-1"></i>{% trans "Cancel" %}</a>
</div>
</form>
</div>
</div>
</div>
</div>
<!---->
{% endblock content %}

View File

@ -13,7 +13,7 @@
{% endblock %}
{% block content %}
<div class="container">
{% comment %} <div class="container">
<div class="row justify-content-center">
<div class="col-md-8">
<div class="card bg-body">
@ -38,6 +38,45 @@
</div>
</div>
</div>
</div>
</div> {% endcomment %}
<!---->
<div class="row justify-content-center mt-5 mb-3">
<div class="col-lg-8 col-md-10">
<div class="card shadow-sm border-0 rounded-3">
<div class="card-header bg-gray-200 py-3 border-0 rounded-top-3">
<h3 class="mb-0 fs-4 text-center text-white">
{% if service.pk %}
{{ _("Update Service") }}
{% else %}
{{ _("Add Service") }}
{% endif %}
</h3>
</div>
<div class="card-body bg-light-subtle">
<form method="post" action="">
{% csrf_token %}
{{ form|crispy }}
<hr class="my-2">
<div class="d-grid gap-2 d-md-flex justify-content-md-center mt-3">
<button class="btn btn-lg btn-phoenix-success md-me-2" type="submit"><i class="fa-solid fa-floppy-disk me-1"></i>{{ _("Save") }}</button>
<a href="{{request.META.HTTP_REFERER}}" class="btn btn-lg btn-phoenix-danger"><i class="fa-solid fa-ban me-1"></i>{% trans "Cancel" %}</a>
</div>
</form>
</div>
</div>
</div>
</div>
<!---->
{% endblock content %}

View File

@ -12,7 +12,8 @@
{% block content %}
<div class="container">
{% comment %} <div class="container">
<div class="row justify-content-center">
<div class="col-8">
<div class="card shadow rounded bg-body">
@ -46,5 +47,48 @@
</div>
</div>
</div>
</div>
</div> {% endcomment %}
<!---->
<div class="row justify-content-center mt-5 mb-3">
<div class="col-lg-8 col-md-10">
<div class="card shadow-sm border-0 rounded-3">
<div class="card-header bg-gray-200 py-3 border-0 rounded-top-3">
<h3 class="mb-0 fs-4 text-center text-white">
{% if customer.created %}
{{ _("Edit Bank Account") }}
{% else %}
{{ _("Add Bank Account") }}
{% endif %}
</h3>
</div>
<div class="card-body bg-light-subtle">
<form method="post" class="form" novalidate>
{% csrf_token %}
{{ form|crispy }}
{% for error in form.errors %}
<div class="text-danger">{{ error }}</div>
{% endfor %}
<hr class="my-2">
<div class="d-grid gap-2 d-md-flex justify-content-md-center mt-3">
<button class="btn btn-lg btn-phoenix-success md-me-2" type="submit"><i class="fa-solid fa-floppy-disk me-1"></i>{{ _("Save") }}</button>
<a href="{{request.META.HTTP_REFERER}}" class="btn btn-lg btn-phoenix-danger"><i class="fa-solid fa-ban me-1"></i>{% trans "Cancel" %}</a>
</div>
</form>
</div>
</div>
</div>
</div>
<!---->
{% endblock %}

View File

@ -12,6 +12,7 @@
{% endblock %}
{% block content %}
{% comment %}
<div class="row my-5">
<!-- Display Form Errors -->
<div class="card shadow rounded bg-body">
@ -46,5 +47,46 @@
</form>
</div>
</div>
</div>
</div> {% endcomment %}
<!---->
<div class="row justify-content-center mt-5 mb-3">
<div class="col-lg-8 col-md-10">
<div class="card shadow-sm border-0 rounded-3">
<div class="card-header bg-gray-200 py-3 border-0 rounded-top-3">
<h3 class="mb-0 fs-4 text-center text-white">
{% if account.created %}
<i class="fa-solid fa-book"></i> {{ _("Edit Account") }}
{% else %}
<i class="fa-solid fa-book"></i> {{ _("Add Account") }}
{% endif %}
</h3>
</div>
<div class="card-body bg-light-subtle">
<form method="post" class="form" novalidate>
{% csrf_token %}
{{ form|crispy }}
{% for error in form.errors %}
<div class="text-danger">{{ error }}</div>
{% endfor %}
<hr class="my-2">
<div class="d-grid gap-2 d-md-flex justify-content-md-center mt-3">
<button class="btn btn-lg btn-phoenix-success md-me-2" type="submit"><i class="fa-solid fa-floppy-disk me-1"></i>{{ _("Save") }}</button>
<a href="{{request.META.HTTP_REFERER}}" class="btn btn-lg btn-phoenix-danger"><i class="fa-solid fa-ban me-1"></i>{% trans "Cancel" %}</a>
</div>
</form>
</div>
</div>
</div>
</div>
<!---->
{% endblock %}

View File

@ -5,7 +5,7 @@
{% block title %}{{ _("Create Journal Entry") }}{% endblock title %}
{% block content %}
<div class="row mt-4">
{% comment %} <div class="row mt-4">
<h3 class="text-center">{% trans "Create Journal Entry" %}</h3>
<form id="mainForm" method="post" class="needs-validation">
{% csrf_token %}
@ -17,5 +17,40 @@
<a href="{% url 'journalentry_list' request.dealer.slug ledger.pk %}" class="btn btn-phoenix-secondary"><i class="fa-solid fa-ban me-1"></i> {% trans "Cancel" %}</a>
</div>
</form>
</div>
</div> {% endcomment %}
<!---->
<div class="row justify-content-center mt-5 mb-3">
<div class="col-lg-8 col-md-10">
<div class="card shadow-sm border-0 rounded-3">
<div class="card-header bg-gray-200 py-3 border-0 rounded-top-3">
<h3 class="mb-0 fs-4 text-center text-white">
{% trans "Create Journal Entry" %}
</h3>
</div>
<div class="card-body bg-light-subtle">
<form id="mainForm" method="post" class="needs-validation">
{% csrf_token %}
<div class="row g-3">
{{ form|crispy }}
</div>
<hr class="my-2">
<div class="d-grid gap-2 d-md-flex justify-content-md-center mt-3">
<button class="btn btn-lg btn-phoenix-success md-me-2" type="submit"><i class="fa-solid fa-floppy-disk me-1"></i>{{ _("Save") }}</button>
<a href="{% url 'journalentry_list' request.dealer.slug ledger.pk %}" class="btn btn-lg btn-phoenix-secondary"><i class="fa-solid fa-ban me-1"></i> {% trans "Cancel" %}</a>
</div>
</form>
</div>
</div>
</div>
</div>
<!---->
{% endblock content %}

View File

@ -5,17 +5,13 @@
{% block title %}{{ _("Create Ledger") }}{% endblock title %}
{% block content %}
<div class="row mt-4">
{% comment %} <div class="row mt-4">
<h3 class="text-center">{% trans "Create Ledger" %}</h3>
<form id="mainForm" method="post" class="needs-validation">
{% csrf_token %}
<div class="row g-3">
{{ form|crispy }}
</div>
{% comment %} <div class="mt-5 text-center">
<button type="submit" class="btn btn-success me-2"><i class="fa-solid fa-floppy-disk"></i>{% trans "Save" %}</button>
<a href="{% url 'ledger_list' %}" class="btn btn-danger"><i class="fa-solid fa-ban"></i> {% trans "Cancel" %}</a>
</div> {% endcomment %}
<div class="d-flex justify-content-center">
<button class="btn btn-sm btn-phoenix-success me-2" type="submit"><i class="fa-solid fa-floppy-disk me-1"></i>{{ _("Save") }}</button>
@ -24,5 +20,44 @@
</form>
</div>
</div> {% endcomment %}
<!---->
<div class="row justify-content-center mt-5 mb-3">
<div class="col-lg-8 col-md-10">
<div class="card shadow-sm border-0 rounded-3">
<div class="card-header bg-gray-200 py-3 border-0 rounded-top-3">
<h3 class="mb-0 fs-4 text-center text-white">
{% trans "Create Ledger" %}
</h3>
</div>
<div class="card-body bg-light-subtle">
<form id="mainForm" method="post" class="needs-validation">
{% csrf_token %}
<div class="row g-3">
{{ form|crispy }}
</div>
<hr class="my-2">
<div class="d-grid gap-2 d-md-flex justify-content-md-center mt-3">
<button class="btn btn-lg btn-phoenix-success md-me-2" type="submit"><i class="fa-solid fa-floppy-disk me-1"></i>{{ _("Save") }}</button>
<a href="{{request.META.HTTP_REFERER}}" class="btn btn-lg btn-phoenix-danger"><i class="fa-solid fa-ban me-1"></i>{% trans "Cancel" %}</a>
</div>
</form>
</div>
</div>
</div>
</div>
<!---->
{% endblock content %}

View File

@ -87,7 +87,7 @@
<span class="fas fa-ellipsis-h fs-10"></span>
</button>
<div class="dropdown-menu dropdown-menu-end py-2">
{% if perms.django_ledger.change_ledgermodel%}
{% if perms.django_ledger.change_ledgermodel %}
{% if ledger.can_lock %}
<a href="{% url 'ledger-action-lock' dealer_slug=request.dealer.slug entity_slug=entity_slug ledger_pk=ledger.uuid %}"
class="dropdown-item has-text-info has-text-weight-bold">{% trans 'Lock' %}</a>

View File

@ -1,3 +1,19 @@
<style>
.fade-out {
animation: fadeOut 1s ease-out forwards;
}
@keyframes fadeOut {
from {
opacity: 1;
}
to {
opacity: 0;
}
}
</style>
<li class="nav-item dropdown">
<!-- Notification counter -->
<div class="notification-count">
@ -236,6 +252,10 @@
notificationCard.classList.remove('unread');
notificationCard.classList.add('read');
updateCounter('decrement');
notificationCard.closest('.notification-card').classList.add('fade-out');
setTimeout(() => {
notificationCard.closest('.notification-card').remove();
}, 200);
}
}
});

View File

@ -9,29 +9,45 @@
{% trans 'Add New Organization'%}
{% endif %}
{% endblock %}
{% block content %}
<div class="container-fluid mt-4">
<!--Heading-->
<h3 class="mb-3">
{% if object %}
{% trans 'Update Organization'%}
{% else %}
{% trans 'Add New Organization'%}
{% endif %}
</h3>
<!--form body-->
<div class="row justify-content-center mt-5 mb-3">
<div class="row mb-3">
<div class="col-sm-6 col-md-8">
<form class="form" method="post" enctype="multipart/form-data">
<div class="col-lg-8 col-md-10">
<div class="card shadow-sm border-0 rounded-3">
<div class="card-header bg-gray-200 py-3 border-0 rounded-top-3">
<h3 class="mb-0 fs-4 text-center text-white">
{% if object %}
{% trans 'Update Organization'%}
{% else %}
{% trans 'Add New Organization'%}
{% endif %}
</h3>
</div>
<div class="card-body bg-light-subtle">
<form class="form" method="post" enctype="multipart/form-data">
{% csrf_token %}
{{ redirect_field }}
{{ form|crispy }}
<button class="btn btn-sm btn-phoenix-success me-2" type="submit"><i class="fa-solid fa-floppy-disk me-1"></i>{{ _("Save") }}</button>
<a href="{{request.META.HTTP_REFERER}}" class="btn btn-sm btn-phoenix-danger"><i class="fa-solid fa-ban me-1"></i>{% trans "Cancel" %}</a>
<hr class="my-2">
<div class="d-grid gap-2 d-md-flex justify-content-md-center mt-3">
<button class="btn btn-lg btn-phoenix-success md-me-2" type="submit"><i class="fa-solid fa-floppy-disk me-1"></i>{{ _("Save") }}</button>
<a href="{{request.META.HTTP_REFERER}}" class="btn btn-lg btn-phoenix-danger"><i class="fa-solid fa-ban me-1"></i>{% trans "Cancel" %}</a>
</div>
</form>
</div>
</div>
</div>
{% endblock %}
</div>
</div>
{% endblock%}

View File

@ -11,7 +11,7 @@
{% block content %}
<section class="pt-5 pb-9 ">
<div class="row overflow-x-auto whitespace-nowrap -mx-2 sm:mx-0">
<h2 class="mb-4">{% trans 'Organizations' %}</h2>
<h2 class="mb-4">{% trans 'Organizations' %} <li class="fas fa-city text-primary ms-2"></li></h2>
<div class="row g-3 justify-content-between mb-4">
<div class="col-auto">

View File

@ -1,7 +1,7 @@
<div class="search-box me-2">
<form class="position-relative show" id="search-form">
<input name="q" id="search-input" class="form-control form-control-sm search-input search" type="search"
aria-label="Search" placeholder="{{ _('Search') }}" value="{{ request.GET.q }}" />
aria-label="Search" placeholder="{{ _('Search...') }}" value="{{ request.GET.q }}" />
<span class="fa fa-magnifying-glass search-box-icon"></span>
{% if request.GET.q %}
<button type="button" class="btn-close position-absolute end-0 top-50 translate-middle cursor-pointer shadow-none"

View File

@ -12,13 +12,13 @@
}
.pricing-card .card.selected {
border-color: #0d6efd;
border-color:rgb(20, 108, 241);
box-shadow: 0 0 0 0.25rem rgba(13, 110, 253, 0.4);
}
.btn-check:checked + .btn .card {
/* fallback if JS fails */
border-color: #0d6efd;
border-color:rgb(13, 91, 207);
box-shadow: 0 0 0 0.25rem rgba(13, 110, 253, 0.25);
}
@ -50,29 +50,37 @@
{% block content %}
<div class="container py-5">
<h1 class="text-center mb-5">{{ _("Choose Your Plan")}}</h1>
<h1 class="text-center mb-5 text-primary">{{ _("Choose Your Plan")}}</h1>
<form method="POST" action="{% url 'submit_plan' request.dealer.slug %}" id="wizardForm">
{% csrf_token %}
<!-- Step 1: Plan Selection -->
<div class="step" id="step1">
<h4 class="mb-4">1. {{ _("Select a Plan")}}</h4>
<div class="row g-4">
{% for pp in plan_list %}
<div class="col-md-6 col-lg-3">
<!--step1: Choose Plans-->
<div class="step row justify-content-center mt-5 mb-3" id="step1">
<div class="col-lg-8 col-md-10">
<div class="card shadow-sm border-0 rounded-3">
<div class="card-header bg-gray-200 py-3 border-0 rounded-top-3">
<h4 class="mb-0 fs-4 text-center text-white">
1. {{ _("Select a Plan")}}
</h4>
</div>
<div class="card-body bg-light-subtle">
{% for pp in plan_list %}
<div class="col-md-12 mb-3">
<input type="radio" class="btn-check" name="selected_plan" id="plan_{{ forloop.counter }}" value="{{ pp.id }}"
data-name="{{ pp.plan.name }}" data-price="{{ pp.price }}" autocomplete="off" {% if forloop.first %}checked{% endif %}>
<label class="btn w-100 p-0 pricing-card" for="plan_{{ forloop.counter }}">
<div class="card h-100 border border-2 rounded-4">
<div class="card-body p-4">
<h4 class="mb-3">{{ pp.plan.name }}</h4>
<h4 class="mb-3">{{ pp.plan.name|capfirst }}</h4>
<h5 class="mb-4">{{ pp.price }} <span class="icon-saudi_riyal"></span><span class="fs-6 fw-normal">/ {{ pp.pricing.period }}</span> {% trans "days" %}</h5>
<h6>{{ _("Included") }}</h6>
<h5>{{_("Include Haikal's")}}</h5>
<ul class="fa-ul ps-3">
{% if pp.plan.description %}
{% for line in pp.plan.description|splitlines %}
<li class="mb-2">
<span class="fa-li"><i class="fas fa-check text-primary"></i></span>
{{ line }}
{{ line|capfirst}}
</li>
{% endfor %}
{% endif %}
@ -82,124 +90,156 @@
</label>
</div>
{% endfor %}
</div>
</div>
</div>
<!-- Step 2: User Info -->
<div class="step d-none" id="step2">
<h4 class="mb-4">2. {{ _("Enter Your Information")}}</h4>
<div class="row g-3">
<div class="col-md-6">
<label class="form-label" for="first_name">{{ _("First Name")}}</label>
<div class="input-group">
<span class="input-group-text"><i class="fas fa-user"></i></span>
<input type="text" name="first_name" id="first_name" class="form-control form-control-sm" required placeholder="{{ _("First Name")}}" value="{{ request.user.first_name }}">
</div>
</div>
<!--step2: Information-->
<div class="step d-none row justify-content-center mt-5 mb-3" id="step2">
<div class="col-lg-8 col-md-10">
<div class="card shadow-sm border-0 rounded-3">
<div class="card-header bg-gray-200 py-3 border-0 rounded-top-3">
<h3 class="mb-0 fs-4 text-center text-white">
2. {{ _("Enter Your Information")}}
</h3>
</div>
</div>
<div class="col-md-6">
<label class="form-label" for="last_name">{{ _("Last Name")}}</label>
<div class="input-group">
<span class="input-group-text"><i class="fas fa-user"></i></span>
<input type="text" name="last_name" id="last_name" class="form-control form-control-sm" required placeholder="{{ _("Last Name")}}" value="{{ request.user.last_name }}">
<div class="card-body bg-light-subtle">
<label class="form-label" for="first_name">{{ _("First Name")}}</label>
<div class="input-group">
<span class="input-group-text"><i class="fas fa-user"></i></span>
<input type="text" name="first_name" id="first_name" class="form-control form-control-sm" required placeholder="{{ _("First Name")}}" value="{{ request.user.first_name }}">
</div>
<label class="form-label" for="last_name">{{ _("Last Name")}}</label>
<div class="input-group">
<span class="input-group-text"><i class="fas fa-user"></i></span>
<input type="text" name="last_name" id="last_name" class="form-control form-control-sm" required placeholder="{{ _("Last Name")}}" value="{{ request.user.last_name }}">
</div>
<label class="form-label" for="email">{{ _("Email Address")}}</label>
<div class="input-group">
<span class="input-group-text"><i class="fas fa-envelope"></i></span>
<input type="email" name="email" id="email" class="form-control form-control-sm" required placeholder="email@example.com" value="{{ request.user.email }}">
</div>
<label class="form-label" for="phone">{{ _("Phone Number")}}</label>
<div class="input-group">
<span class="input-group-text"><i class="fas fa-phone"></i></span>
<input type="text" name="phone" id="phone" class="form-control form-control-sm" dir="ltr" placeholder="{{ _("Phone Number")}}" value="{{ request.dealer.phone_number.raw_input }}" required>
</div>
<label class="form-label" for="company">{{ _("Company") }}</label>
<div class="input-group">
<span class="input-group-text"><i class="fas fa-building"></i></span>
<input type="text" name="company" id="company" class="form-control form-control-sm" placeholder="{{ _("Company") }}" value="{{ request.dealer.get_local_name }}">
</div>
</div>
</div>
<div class="col-md-6">
<label class="form-label" for="email">{{ _("Email Address")}}</label>
<div class="input-group">
<span class="input-group-text"><i class="fas fa-envelope"></i></span>
<input type="email" name="email" id="email" class="form-control form-control-sm" required placeholder="email@example.com" value="{{ request.user.email }}">
</div>
</div>
<div class="col-md-6">
<label class="form-label" for="phone">{{ _("Phone Number")}}</label>
<div class="input-group">
<span class="input-group-text"><i class="fas fa-phone"></i></span>
<input type="text" name="phone" id="phone" class="form-control form-control-sm" dir="ltr" placeholder="{{ _("Phone Number")}}" value="{{ request.dealer.phone_number.raw_input }}" required>
</div>
</div>
<div class="col-md-6">
<label class="form-label" for="company">{{ _("Company") }}</label>
<div class="input-group">
<span class="input-group-text"><i class="fas fa-building"></i></span>
<input type="text" name="company" id="company" class="form-control form-control-sm" placeholder="{{ _("Company") }}" value="{{ request.dealer.get_local_name }}">
</div>
</div>
</div>
</div>
<!-- Step 3: Payment -->
<div class="step d-none" id="step3">
<h4 class="mb-4">3. {{ _("Payment Information")}}</h4>
<div class="row g-3">
<div class="col-md-6">
<label class="form-label" for="card_name">{{ _("Cardholder Name")}}</label>
<div class="input-group">
<span class="input-group-text"><i class="fas fa-user"></i></span>
<input type="text" name="card_name" id="card_name" class="form-control form-control-sm" placeholder="{{ _("Cardholder Name")}}" required>
</div>
</div>
<!--step3: Payment-->
<div class="step d-none row justify-content-center mt-5 mb-3" id="step3">
<div class="col-lg-8 col-md-10">
<div class="card shadow-sm border-0 rounded-3">
<div class="card-header bg-gray-200 py-3 border-0 rounded-top-3">
<h3 class="mb-0 fs-4 text-center text-white">
3. {{ _("Payment Information")}}
</h3>
</div>
</div>
<div class="card-body bg-light-subtle">
<label class="form-label" for="card_name">{{ _("Cardholder Name")}}</label>
<div class="input-group">
<span class="input-group-text"><i class="fas fa-user"></i></span>
<input type="text" name="card_name" id="card_name" class="form-control form-control-sm" placeholder="{{ _("Cardholder Name")}}" required>
</div>
<div class="col-md-6">
<label class="form-label" for="card_number">{{ _("Card Number")}}</label>
<div class="input-group">
<span class="input-group-text"><i class="fas fa-credit-card"></i></span>
<input type="text" name="card_number" id="card_number" class="form-control form-control-sm" placeholder="{{ _("Card Number")}}"
maxlength="19" pattern="^\d{4}\s\d{4}\s\d{4}\s\d{4}$"
inputmode="numeric" required title="Enter a 16-digit card number">
</div>
</div>
<label class="form-label" for="card_number">{{ _("Card Number")}}</label>
<div class="input-group">
<span class="input-group-text"><i class="fas fa-credit-card"></i></span>
<input type="text" name="card_number" id="card_number" class="form-control form-control-sm" placeholder="{{ _("Card Number")}}"
maxlength="19" pattern="^\d{4}\s\d{4}\s\d{4}\s\d{4}$"
inputmode="numeric" required title="Enter a 16-digit card number">
</div>
<div class="col-md-4">
<label class="form-label" for="card_expiry">{{ _("Expiry Date")}} (MM/YY)</label>
<div class="input-group">
<span class="input-group-text"><i class="far fa-calendar-alt"></i></span>
<input type="text" name="card_expiry" id="card_expiry" class="form-control form-control-sm" placeholder="{{ _("Expiry Date")}}"
<label class="form-label" for="card_expiry">{{ _("Expiry Date")}} (MM/YY)</label>
<div class="input-group">
<span class="input-group-text"><i class="far fa-calendar-alt"></i></span>
<input type="text" name="card_expiry" id="card_expiry" class="form-control form-control-sm" placeholder="{{ _("Expiry Date")}}"
maxlength="5" pattern="^(0[1-9]|1[0-2])\/\d{2}$"
inputmode="numeric" required title="Enter expiry in MM/YY format">
</div>
</div>
</div>
<div class="col-md-2">
<label class="form-label" for="card_cvv">{{ _("CVV") }}</label>
<div class="input-group">
<span class="input-group-text"><i class="fas fa-lock"></i></span>
<input type="text" name="card_cvv" id="card_cvv" class="form-control form-control-sm" placeholder="{{ _("CVV") }}"
maxlength="3" pattern="^\d{3}$"
inputmode="numeric" required title="Enter 3-digit CVV">
<label class="form-label" for="card_cvv">{{ _("CVV") }}</label>
<div class="input-group">
<span class="input-group-text"><i class="fas fa-lock"></i></span>
<input type="text" name="card_cvv" id="card_cvv" class="form-control form-control-sm" placeholder="{{ _("CVV") }}"
maxlength="3" pattern="^\d{3}$"
inputmode="numeric" required title="Enter 3-digit CVV">
</div>
</div>
</div>
</div>
</div>
<!-- Step 4: Confirmation -->
<div class="step d-none" id="step4">
<h4 class="mb-4">4. {{ _("Confirm Your Information")}}</h4>
<div class="summary-box">
<h5><i class="fas fa-file-invoice-dollar me-2"></i>{{ _("Order Summary")}}</h5>
<div class="summary-item"><i class="fas fa-box"></i><strong>{{ _("Plan") }}:</strong> <span id="summary_plan"></span></div>
<div class="summary-item"><i class="fas fa-tag"></i><strong>{{ _("Price") }}:</strong> <span id="summary_price"></span></div>
<div class="summary-item"><i class="fas fa-receipt"></i><strong>{{ _("VAT") }} (15%):</strong> <span id="summary-tax">0.00</span> <span class="icon-saudi_riyal"></span></div>
</div>
</div>
<!--Step4 confirmation-->
<div class="step d-none row justify-content-center mt-5 mb-3" id="step4">
<div class="col-lg-8 col-md-10">
<div class="card shadow-sm border-0 rounded-3">
<div class="card-header bg-gray-200 py-3 border-0 rounded-top-3">
4. {{ _("Confirm Your Information")}}
</div>
<div class="card-body bg-light-subtle me-2">
<h5 class="text-center"><i class="fas fa-file-invoice-dollar me-2"></i>{{ _("Order Summary")}}</h5>
<div class="summary-item"><i class="fas fa-box me-2"></i><strong>{{ _("Plan") }}:</strong> <span id="summary_plan"></span></div>
<div class="summary-item"><i class="fas fa-tag me-2"></i><strong>{{ _("Price") }}:</strong> <span id="summary_price"></span></div>
<div class="summary-item"><i class="fas fa-receipt me-2"></i><strong>{{ _("VAT") }} (15%):</strong> <span id="summary-tax">0.00</span> <span class="icon-saudi_riyal"></span></div>
<div class="summary-item"><i class="fas fa-hand-holding-usd me-2"></i><strong>{{ _("Total") }}:</strong> <span id="summary-total">0.00</span> <span class="icon-saudi_riyal"></span></div>
<hr>
<div class="summary-item"><i class="fas fa-hand-holding-usd"></i><strong>{{ _("Total") }}:</strong> <span id="summary-total">0.00</span> <span class="icon-saudi_riyal"></span></div>
<h5 class="mt-4"><i class="fas fa-user me-2"></i>{{ _("User Information")}}</h5>
<div class="summary-item"><i class="fas fa-signature"></i><strong>{{ _("Name") }}:</strong> <span id="summary_name"></span></div>
<div class="summary-item"><i class="fas fa-envelope"></i><strong>{{ _("Email") }}:</strong> <span id="summary_email"></span></div>
<div class="summary-item"><i class="fas fa-building"></i><strong>{{ _("Company") }}:</strong> <span id="summary_company"></span></div>
<div class="summary-item"><i class="fas fa-phone"></i><strong>{{ _("Phone") }}:</strong> <span id="summary_phone"></span></div>
<h5 class="mt-4 text-center"><i class="fas fa-user me-2"></i>{{ _("User Information")}}</h5>
<div class="summary-item"><i class="fas fa-signature me-2"></i><strong>{{ _("Name") }}:</strong> <span id="summary_name"></span></div>
<div class="summary-item"><i class="fas fa-envelope me-2"></i><strong>{{ _("Email") }}:</strong> <span id="summary_email"></span></div>
<div class="summary-item"><i class="fas fa-building me-2"></i><strong>{{ _("Company") }}:</strong> <span id="summary_company"></span></div>
<div class="summary-item"><i class="fas fa-phone me-2"></i><strong>{{ _("Phone") }}:</strong> <span id="summary_phone"></span></div>
<hr>
<h5 class="mt-4"><i class="fas fa-credit-card me-2"></i>{{ _("Payment") }}</h5>
<div class="summary-item"><i class="fas fa-user"></i><strong>{{ _("Cardholder") }}:</strong> <span id="summary_card_name"></span></div>
<div class="summary-item"><i class="fas fa-credit-card"></i><strong>{{ _("Card Number")}}:</strong> <span id="summary_card_number"></span></div>
<div class="summary-item"><i class="far fa-calendar-alt"></i><strong>{{ _("Expiry") }}:</strong> <span id="summary_card_expiry"></span></div>
<h5 class="mt-4 text-center"><i class="fas fa-credit-card me-2"></i>{{ _("Payment") }}</h5>
<div class="summary-item"><i class="fas fa-user me-2"></i><strong>{{ _("Cardholder") }}:</strong> <span id="summary_card_name"></span></div>
<div class="summary-item"><i class="fas fa-credit-card me-2"></i><strong>{{ _("Card Number")}}:</strong> <span id="summary_card_number"></span></div>
<div class="summary-item"><i class="far fa-calendar-alt me-2"></i><strong>{{ _("Expiry") }}:</strong> <span id="summary_card_expiry"></span></div>
</div>
</div>
</div>
</div>
</div>
<!---->
<!-- Navigation -->
<div class="d-flex justify-content-between mt-4">
<button type="button" class="btn btn-phoenix-secondary" id="prevBtn" disabled>{{ _("Previous") }}</button>
<button type="button" class="btn btn-phoenix-primary" id="nextBtn">{{ _("Next") }}</button>
<button type="submit" class="btn btn-phoenix-success d-none" id="submitBtn">{{ _("Confirm") }}</button>
<button type="button" class="btn btn-lg btn-phoenix-secondary" id="prevBtn" disabled>{{ _("Previous") }}</button>
<button type="button" class="btn btn-lg btn-phoenix-primary" id="nextBtn">{{ _("Next") }}</button>
<button type="submit" class="btn btn-lg btn-phoenix-primary d-none" id="submitBtn">{{ _("Confirm") }}</button>
</div>
</form>
</div>

View File

@ -32,15 +32,26 @@
{% if not create_po %}
{% if style == 'po-detail' %}
<div class="card shadow-sm border-0 mb-2">
<div class="card-header bg-light">
<div class="d-flex align-items-center">
<div class="card-header bg-light ">
<div class="d-flex align-items-center mb-2 ">
<span class="me-3 text-primary">
{% icon 'uil:bill' 36 %}
</span>
<h2 class="h3 mb-0">
<h2 class="h3 mb-0 text-primary me-4">
{{ po_model.po_number }}
</h2>
</div>
<p>
<a class="btn btn-phoenix-primary"
href="{% url 'purchase_order_list' request.dealer.slug request.dealer.entity.slug %}"
title="Click to view the complete list of Purchase Orders"
role="button">
<i class="fas fa-list me-2"></i>{% trans 'PO List' %}
</a>
</p>
</div>
<div class="card-body">
@ -52,6 +63,7 @@
{{ po_model.get_po_status_display }}
</span>
</h3>
</div>
@ -99,14 +111,7 @@
</div>
<div class='col-12 col-md-12 col-lg-2 d-grid align-content-end'>
<a class="btn btn-phoenix-primary py-2"
href="{% url 'purchase_order_list' request.dealer.slug request.dealer.entity.slug %}"
title="Click to view the complete list of Purchase Orders"
role="button">
<i class="fas fa-list me-2"></i>{% trans 'PO List' %}
</a>
</div>
</div>

View File

@ -10,44 +10,42 @@
{% endif %}
{% endblock %}
{% block content %}
<div class="row mt-4">
<div class="row">
<div class="col-xl-9">
<div class="d-sm-flex justify-content-between">
<!---->
<div class="row justify-content-center mt-5 mb-3">
<h3 class="mb-3">
{% if vendor.created %}
<!--<i class="bi bi-pencil-square"></i>-->
{{ _("Edit Purchase Order") }}
{% else %}
<!--<i class="bi bi-person-plus"></i> -->
{{ _("Add New Purchase Order") }}
{% endif %}
</h3>
<div class="col-lg-8 col-md-10 ">
<div class="card shadow-sm border-0 rounded-3">
<div class="card-header bg-gray-200 py-3 border-0 rounded-top-3">
<h3 class="mb-0 fs-4 text-center text-white">
{{ _("Add New Purchase Order") }}
</h3>
</div>
<div class="card-body bg-light-subtle">
<form class="row g-3 mb-9" method="post" class="form" enctype="multipart/form-data" novalidate >
{% csrf_token %}
{{ redirect_field }}
{{ form|crispy }}
{% for error in form.errors %}
<div class="text-danger">{{ error }}</div>
{% endfor %}
<hr class="my-2">
<div class="d-grid gap-2 d-md-flex justify-content-md-center mt-3">
<button class="btn btn-lg btn-phoenix-success md-me-2" type="submit"><i class="fa-solid fa-floppy-disk me-1"></i>{{ _("Save") }}</button>
<a href="{{request.META.HTTP_REFERER}}" class="btn btn-lg btn-phoenix-danger"><i class="fa-solid fa-ban me-1"></i>{% trans "Cancel" %}</a>
</div>
</form>
</div>
</div>
</div>
</div>
<div class="row">
<div class="col-xl-9">
</div>
<form class="row g-3 mb-9" method="post" class="form" enctype="multipart/form-data" novalidate >
{% csrf_token %}
{{ redirect_field }}
{{ form|crispy }}
{% for error in form.errors %}
<div class="text-danger">{{ error }}</div>
{% endfor %}
<div class="d-flex justify-content-start">
<button class="btn btn-sm btn-phoenix-success me-2" type="submit"><i class="fa-solid fa-floppy-disk me-1"></i>
<!--<i class="bi bi-save"></i> -->
{{ _("Save") }}
</button>
<a href="{{request.META.HTTP_REFERER}}" class="btn btn-sm btn-phoenix-danger"><i class="fa-solid fa-ban me-1"></i>{% trans "Cancel" %}</a>
</div>
</div>
</form>
</div>
</div>
</div>
<!---->
{% endblock %}

View File

@ -13,21 +13,26 @@
<div class="alert alert-success">{{ message }}</div>
{% endfor %}
{% endif %}
<!-- Add New PO Button -->
<div class="d-flex justify-content-between mb-2">
<h3 class="">
{{ _("Purchase Orders") |capfirst }}
<h2 class="">
{{ _("Purchase Orders") |capfirst }} <li class="fas fa-file-invoice text-primary ms-2"></li>
</h2>
{% if perms.django_ledger.add_purchaseordermodel%}
<div>
{% if perms.django_ledger.add_purchaseordermodel %}
<div class="row g-3 justify-content-between mb-4">
<div class="col-auto">
<div class="d-md-flex justify-content-between">
{% if perms.django_ledger.add_purchaseordermodel %}
<a href="{% url 'purchase_order_create' request.dealer.slug request.dealer.entity.slug %}"
class="btn btn-md btn-phoenix-primary"><i class="fa fa-plus me-2"></i>{{ _("Create New PO") }}</a>
{% endif %}
</div>
{% endif %}
{% endif %}
</div>
</div>
{% include "partials/search_box.html" %}
<div class="col-auto">
<div class="d-flex">
{% include 'partials/search_box.html' %}
</div>
</div>
</div>
<div class="table-responsive px-1 scrollbar mt-3">
<table class= "table align-items-center table-flush table-hover">
@ -74,7 +79,7 @@
{% endif %}
{% if po.po_status == 'fulfilled' %}
{% if perms.inventory.add_car %}
<a href="{% url 'view_items_inventory' dealer_slug=request.dealer.slug entity_slug=entity_slug po_pk=po.pk %}" class="dropdown-item text-success-dark">{% trans 'Add Inventory Items' %}</a>
<a href="{% url 'view_items_inventory' dealer_slug=request.dealer.slug entity_slug=entity_slug po_pk=po.pk %}" class="dropdown-item text-success-dark">{% trans 'Inventory Items' %}</a>
{% endif %}
{% else %}
<button class="dropdown-item text-warning-dark" disabled><span class="fas fa-exclamation-triangle me-1"></span> Fulfill the PO Before Viewing Inventory</button>

View File

@ -24,13 +24,11 @@
{% csrf_token %}
{{ form|crispy }}
<button type="submit"
class="btn btn-phoenix-success w-100 my-2">{% trans 'Save PO' %}
class="btn btn-phoenix-success my-2 me-2">{% trans 'Save PO' %}
</button>
<a href="{% url 'purchase_order_detail' request.dealer.slug request.dealer.entity.slug po_model.uuid %}"
class="btn btn-phoenix-secondary w-100 my-2">{% trans 'Back to PO Detail' %}</a>
<a href="{% url 'purchase_order_list' request.dealer.slug request.dealer.entity.slug %}"
class="btn btn-phoenix-info
info w-100 my-2">{% trans 'PO List' %}</a>
class="btn btn-phoenix-secondary my-2">{% trans 'Back to PO Detail' %}</a>
</div>
</div>
</form>

View File

@ -1,5 +1,6 @@
{% extends "base.html" %}
{% load i18n custom_filters%}
{% load crispy_forms_filters %}
{% block title %}{{ _("View Quotation") }}{% endblock title %}
@ -83,7 +84,12 @@
{% if perms.django_ledger.can_approve_estimatemodel %}
<button id="accept_estimate" onclick="setFormAction('approved')" class="btn btn-phoenix-secondary" data-bs-toggle="modal" data-bs-target="#confirmModal"><span class="d-none d-sm-inline-block"><i class="fa-solid fa-check-double"></i> {% trans 'Mark As Approved' %}</span></button>
{% endif %}
{% elif estimate.status == 'approved' %}
{% if estimate.can_approve and not request.is_manager %}
<button class="btn btn-phoenix-warning" disabled>
<i class="fas fa-hourglass-start me-2"></i><span class="text-warning">{% trans 'Waiting for Manager Approval' %}</span>
</button>
{% endif %}
{% elif estimate.status == 'approved' %}
{% if perms.django_ledger.change_estimatemodel %}
<a href="{% url 'send_email' request.dealer.slug estimate.pk %}" class="btn btn-phoenix-primary me-2"><span class="fa-regular fa-paper-plane me-sm-2"></span><span class="d-none d-sm-inline-block">{% trans 'Send Quotation' %}</span></a>
{% endif %}
@ -186,15 +192,22 @@
</tr>
{% endfor %}
<tr class="bg-body-secondary total-sum">
<td class="align-middle ps-4 fw-semibold text-body-highlight" colspan="7">{% trans "Vat" %} ({{data.vat|percentage}})</td>
<td class="align-middle ps-4 fw-semibold text-body-highlight" colspan="7">{% trans "Vat" %} ({{data.vat}})</td>
<td class="align-middle text-start fw-semibold">
<span id="grand-total">+ {{data.total_vat_amount|floatformat}}<span class="icon-saudi_riyal"></span></span>
</td>
</tr>
<tr class="bg-body-secondary total-sum">
<td class="align-middle ps-4 fw-semibold text-body-highlight" colspan="7">{% trans "Discount Amount" %}</td>
<td class="align-middle text-start text-danger fw-semibold ">
<span id="grand-total">- {{data.total_discount|floatformat}}<span class="icon-saudi_riyal"></span></span>
<td class="align-middle text-start text-danger fw-semibold">
<form action="{% url 'update_estimate_discount' request.dealer.slug estimate.pk %}" method="post">
{% csrf_token %}
<div class="input-group input-group-sm">
<input type="number" class="form-control" name="discount_amount" value="{{data.total_discount}}" step="0.01" style="width: 1px;">
<span class="input-group-text"><span class="icon-saudi_riyal"></span></span>
<button type="submit" class="btn btn-sm btn-phoenix-primary ms-n2">{% trans "Update" %}</button>
</div>
</form>
</td>
</tr>
<tr class="bg-body-secondary total-sum">
@ -203,6 +216,7 @@
{% for service in data.additionals %}
<small><span class="fw-semibold">+ {{service.name}} - {{service.price_|floatformat}}<span class="icon-saudi_riyal"></span></span></small><br>
{% endfor %}
<button class="btn btn-phoenix-primary btn-xs ms-auto" type="button" data-bs-toggle="modal" data-bs-target="#additionalModal"><span class="fas fa-plus me-1"></span>{{ _("Add") }}</button>
</td>
</tr>
<tr class="bg-body-secondary total-sum">
@ -221,6 +235,27 @@
</section>
<!-- <section> close ============================-->
<!-- ============================================-->
<!-- add update Modal -->
<div class="modal fade" id="additionalModal" tabindex="-1" aria-labelledby="additionalModalLabel" aria-hidden="true">
<div class="modal-dialog modal-md">
<div class="modal-content">
<div class="modal-header justify-content-between align-items-start gap-5 px-4 pt-4 pb-3 border-0">
<h4 class="modal-title" id="additionalModalLabel">{% trans 'Additional Services' %}</h4>
<button class="btn p-0 text-body-quaternary fs-6" data-bs-dismiss="modal" aria-label="Close">
<span class="fas fa-times"></span>
</button>
</div>
<div class="modal-body">
<form action="{% url 'update_estimate_additionals' request.dealer.slug estimate.pk %}" method="post">
{% csrf_token %}
{{additionals_form|crispy}}
<button type="submit" class="btn btn-phoenix-primary">{% trans 'Update' %}</button>
</form>
</div>
</div>
</div>
</div>
{% endblock %}
{% block customJS %}

View File

@ -120,8 +120,89 @@
}
</style>
{% endblock customCSS %}
<!---->
{% block content%}
<div class="row justify-content-center mt-5 mb-3">
{% if not items %}
<div class="alert alert-outline-warning d-flex align-items-center" role="alert">
<i class="fa-solid fa-circle-info fs-6"></i>
<p class="mb-0 flex-1">{{ _("Please add at least one car before creating a quotation.") }}<a class="ms-3 text-body-primary fs-9" href="{% url 'car_add' request.dealer.slug %}"> {{ _("Add Car") }} </a></p>
<button class="btn-close" type="button" data-bs-dismiss="alert" aria-label="Close"></button>
</div>
{% endif %}
{% if not customer_count %}
<div class="alert alert-outline-warning d-flex align-items-center" role="alert">
<i class="fa-solid fa-circle-info fs-6"></i>&nbsp;&nbsp;
<p class="mb-0 flex-1"> {{ _("Please add at least one customer before creating a quotation.") }}<a class="ms-3 text-body-primary fs-9" href="{% url 'customer_create' request.dealer.slug %}"> {{ _("Add Customer") }} </a></p>
<button class="btn-close" type="button" data-bs-dismiss="alert" aria-label="Close"></button>
</div>
{% endif %}
<div class="col-lg-8 col-md-10">
<div class="card shadow-sm border-0 rounded-3">
<div class="card-header bg-gray-200 py-3 border-0 rounded-top-3">
<h3 class="mb-0 fs-4 text-center text-white">
<i class="fa-regular fa-file-lines"></i> {% trans "Create Quotation" %}
</h3>
</div>
<div class="card-body bg-light-subtle">
<form id="mainForm" method="post" class="needs-validation {% if not items or not customer_count %}disabled{% endif %}">
{% csrf_token %}
<div class="row g-3 col-12">
{{ form|crispy }}
<div class="custom-select">
<!-- Hidden native select for form submission -->
<select class="native-select" name="item" required tabindex="-1">
<option value="">Select a car</option>
{% for item in items %}
<option value="{{ item.hash }}"></option>
{% endfor %}
</select>
<!-- Custom select UI -->
<div class="select-trigger">
<div class="selected-value">
<span>Select a car</span>
</div>
<i class="fas fa-chevron-down dropdown-icon"></i>
</div>
<div class="options-container">
{% for item in items %}
<div class="option" data-value="{{ item.hash }}" data-image="{{item.logo}}">
<img src="{{item.logo}}" alt="{{item.model}}">
<span>{{item.make}} {{item.model}} {{item.serie}} {{item.trim}} {{item.color_name}}</span>
<div class="color-box" style="background-color: rgb({{ item.exterior_color }});"></div>
<div class="color-box" style="background-color: rgb({{ item.interior_color }});"></div>
<span style="color:gray;">({{item.hash_count}} in stock)</span>
</div>
{% endfor %}
</div>
</div>
</div>
<hr class="my-2">
<div class="d-grid gap-2 d-md-flex justify-content-md-center mt-3">
<button class="btn btn-lg btn-phoenix-success md-me-2" type="submit"><i class="fa-solid fa-floppy-disk me-1"></i>{{ _("Save") }}</button>
<a href="{{request.META.HTTP_REFERER}}" class="btn btn-lg btn-phoenix-danger"><i class="fa-solid fa-ban me-1"></i>{% trans "Cancel" %}</a>
</div>
</form>
</div>
</div>
</div>
</div>
{% endblock %}
<!---->
{% comment %} {% block content %}
{% block content %}
<div class="row mt-4">
{% if not items %}
<div class="alert alert-outline-warning d-flex align-items-center" role="alert">
@ -184,7 +265,7 @@
</div>
</form>
</div>
{% endblock content %}
{% endblock content %} {% endcomment %}
{% block customJS %}
<script>

View File

@ -4,7 +4,7 @@
{% load i18n %}
{% block title %}{{ _("Invoice") }}{% endblock title %}
{% block content %}
<div class="row paid">
{% comment %} <div class="row paid">
<div class="row justify-content-center">
<div class="col-md-8">
<div class="card">
@ -19,6 +19,42 @@
</div>
</div>
</div>
</div>
</div> {% endcomment %}
<!---->
<div class="row paid justify-content-center mt-5 mb-3">
<div class="col-lg-8 col-md-10">
<div class="card shadow-sm border-0 rounded-3">
<div class="card-header bg-gray-200 py-3 border-0 rounded-top-3">
<h3 class="mb-0 fs-4 text-center text-white">
<i class="fa-solid fa-receipt"></i> {{ _("Add Invoice") }}
</h3>
</div>
<div class="card-body bg-light-subtle">
<form method="post" action="">
{% csrf_token %}
{{ form|crispy }}
<hr class="my-2">
<div class="d-grid gap-2 d-md-flex justify-content-md-center mt-3">
<button class="btn btn-lg btn-phoenix-success md-me-2" type="submit"><i class="fa-solid fa-floppy-disk me-1"></i>{{ _("Save") }}</button>
<a href="{{request.META.HTTP_REFERER}}" class="btn btn-lg btn-phoenix-danger"><i class="fa-solid fa-ban me-1"></i>{% trans "Cancel" %}</a>
</div>
</form>
</div>
</div>
</div>
</div>
<!---->
{% endblock content %}

View File

@ -82,7 +82,7 @@
</div>
</div>
<div class="d-flex align-items-center gap-2">
{% if perms.django_ledger.add_payment%}
{% if perms.inventory.add_payment%}
{% if invoice.invoice_status == 'in_review' %}
<button id="accept_invoice" class="btn btn-phoenix-secondary" data-bs-toggle="modal" data-bs-target="#confirmModal"><span class="d-none d-sm-inline-block"><i class="fa-solid fa-check-double"></i> {% trans 'Accept' %}</span></button>
{% endif %}

View File

@ -39,6 +39,8 @@
{{ form.arabic_name|as_crispy_field }}
{{ form.email|as_crispy_field }}
{{ form.phone_number|as_crispy_field }}
{{ form.address|as_crispy_field }}
{{ form.image|as_crispy_field }}
{{ form.group|as_crispy_field }}
{% for error in form.errors %}
<div class="text-danger">{{ error }}</div>

View File

@ -13,7 +13,7 @@
{% block content %}
{% comment %}
<div class="row">
<div class="row">
<div class="col-xl-9">
@ -54,4 +54,43 @@
</div>
</div>
</div>
{% endcomment %}
<!---->
<div class="row justify-content-center mt-5 mb-3">
<div class="col-lg-8 col-md-10">
<div class="card shadow-sm border-0 rounded-3">
<div class="card-header bg-gray-200 py-3 border-0 rounded-top-3">
<h3 class="mb-0 fs-4 text-center text-white">
{% if vendor.created %}
{{ _("Edit Vendor") }}
{% else %}
{{ _("Add Vendor") }}
{% endif %}
</h3>
</div>
<div class="card-body bg-light-subtle">
<form class="row g-3 mb-9" method="post" class="form" enctype="multipart/form-data" novalidate >
{% csrf_token %}
{{ redirect_field }}
{{ form|crispy }}
{% for error in form.errors %}
<div class="text-danger">{{ error }}</div>
{% endfor %}
<hr class="my-2">
<div class="d-grid gap-2 d-md-flex justify-content-md-center mt-3">
<button class="btn btn-lg btn-phoenix-success md-me-2" type="submit"><i class="fa-solid fa-floppy-disk me-1"></i>{{ _("Save") }}</button>
<a href="{{request.META.HTTP_REFERER}}" class="btn btn-lg btn-phoenix-danger"><i class="fa-solid fa-ban me-1"></i>{% trans "Cancel" %}</a>
</div>
</form>
</div>
</div>
</div>
</div>
<!---->
{% endblock %}

View File

@ -28,21 +28,21 @@
<link rel="preconnect" href="https://fonts.googleapis.com">
<link rel="preconnect" href="https://fonts.gstatic.com" crossorigin="">
<link href="https://fonts.googleapis.com/css2?family=Nunito+Sans:wght@300;400;600;700;800;900&display=swap" rel="stylesheet">
<link href="{% static 'vendors/simplebar/simplebar.min.css' %}" rel="stylesheet">
<link href="{% static 'css/sweetalert2.min.css' %}" rel="stylesheet">
<link rel="stylesheet" href="https://unicons.iconscout.com/release/v4.0.8/css/line.css">
{% comment %} <link href="{% static 'vendors/simplebar/simplebar.min.css' %}" rel="stylesheet"> {% endcomment %}
{% comment %} <link href="{% static 'css/sweetalert2.min.css' %}" rel="stylesheet"> {% endcomment %}
{% comment %} <link rel="stylesheet" href="https://unicons.iconscout.com/release/v4.0.8/css/line.css"> {% endcomment %}
{% if LANGUAGE_CODE == 'en' %}
<link href="{% static 'css/theme.min.css' %}" type="text/css" rel="stylesheet" id="style-default">
<link href="{% static 'css/user.min.css' %}" type="text/css" rel="stylesheet" id="user-style-default">
{% else %}
<link href="{% static 'css/theme-rtl.min.css' %}" type="text/css" rel="stylesheet" id="style-rtl">
<link href="{% static 'css/user-rtl.min.css' %}" type="text/css" rel="stylesheet" id="user-style-rtl">
{% endif %}
{% if LANGUAGE_CODE == 'ar' %}
<link href="{% static 'css/theme-rtl.min.css' %}" type="text/css" rel="stylesheet" id="style-rtl">
<link href="{% static 'css/user-rtl.min.css' %}" type="text/css" rel="stylesheet" id="user-style-rtl">
{% else %}
<link href="{% static 'css/theme.min.css' %}" type="text/css" rel="stylesheet" id="style-default">
<link href="{% static 'css/user.min.css' %}" type="text/css" rel="stylesheet" id="user-style-default">
{% endif %}
<script src="{% static 'vendors/simplebar/simplebar.min.js' %}"></script>
<script src="{% static 'js/config.js' %}"></script>
<script src="{% static 'js/sweetalert2.all.min.js' %}"></script>
{% comment %} <script src="{% static 'js/sweetalert2.all.min.js' %}"></script> {% endcomment %}
<link href="{% static 'css/custom.css' %}" rel="stylesheet">
</head>
@ -52,9 +52,27 @@
{% block content %}
{% endblock content %}
{% comment %}
<script src="{% static 'vendors/anchorjs/anchor.min.js' %}"></script>
<script src="{% static 'vendors/is/is.min.js' %}"></script>
<script src="{% static 'vendors/list.js/list.min.js' %}"></script>
<script src="{% static 'vendors/dayjs/dayjs.min.js' %}"></script>
<script src="{% static 'vendors/echarts/echarts.min.js' %}"></script>
<script src="{% static 'js/travel-agency-dashboard.js' %}"></script>
<script src="{% static 'vendors/mapbox-gl/mapbox-gl.js' %}"></script>
<script src="https://unpkg.com/@turf/turf@6/turf.min.js"></script>
<script src="{% static 'vendors/swiper/swiper-bundle.min.js' %}"></script> {% endcomment %}
<script src="{% static 'vendors/popper/popper.min.js' %}"></script>
<script src="{% static 'vendors/bootstrap/bootstrap.min.js' %}"></script>
<script src="{% static 'vendors/fontawesome/all.min.js' %}"></script>
<script src="{% static 'vendors/lodash/lodash.min.js' %}"></script>
<script src="{% static 'vendors/feather-icons/feather.min.js' %}"></script>
<script src="{% static 'vendors/typed.js/typed.umd.js' %}"></script>
<script src="{% static 'js/phoenix.js' %}"></script>
{% comment %} <script src="{% static 'vendors/popper/popper.min.js' %}"></script>
<script src="{% static 'vendors/bootstrap/bootstrap.min.js' %}"></script>
<script src="{% static 'vendors/anchorjs/anchor.min.js' %}"></script>
<script src="{% static 'vendors/is/is.min.js' %}"></script>
<script src="{% static 'vendors/fontawesome/all.min.js' %}"></script>
@ -68,7 +86,7 @@
<script src="{% static 'vendors/mapbox-gl/mapbox-gl.js' %}"></script>
<script src="https://unpkg.com/@turf/turf@6/turf.min.js"></script>
<script src="{% static 'vendors/swiper/swiper-bundle.min.js' %}"></script>
<script src="{% static 'vendors/typed.js/typed.umd.js' %}"></script>
<script src="{% static 'vendors/typed.js/typed.umd.js' %}"></script> {% endcomment %}
{% block customJS %}
{% endblock customJS %}