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 .idea/**/dbnavigator.xml
**/migrations/** **/migrations/**
**haikalbot/migrations/**
# Gradle # Gradle
.idea/**/gradle.xml .idea/**/gradle.xml
.idea/**/libraries .idea/**/libraries

2
.vscode/launch.json vendored
View File

@ -14,7 +14,7 @@
], ],
"django": true, "django": true,
"autoStartBrowser": false, "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 import settings
from django.conf.urls.i18n import i18n_patterns from django.conf.urls.i18n import i18n_patterns
from inventory import views from inventory import views
# from debug_toolbar.toolbar import debug_toolbar_urls
# import debug_toolbar # import debug_toolbar
from schema_graph.views import Schema from schema_graph.views import Schema
@ -15,7 +17,7 @@ urlpatterns = [
path("api-auth/", include("rest_framework.urls")), path("api-auth/", include("rest_framework.urls")),
path("api/", include("api.urls")), path("api/", include("api.urls")),
# path('dj-rest-auth/', include('dj_rest_auth.urls')), # path('dj-rest-auth/', include('dj_rest_auth.urls')),
] ]# + debug_toolbar_urls()
urlpatterns += i18n_patterns( urlpatterns += i18n_patterns(
path("admin/", admin.site.urls), path("admin/", admin.site.urls),
path("switch_language/", views.switch_language, name="switch_language"), 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) 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.Notes)
admin.site.register(models.UserActivityLog) admin.site.register(models.UserActivityLog)
admin.site.register(models.DealersMake) admin.site.register(models.DealersMake)
admin.site.register(models.ExtraInfo)
@admin.register(models.Car) @admin.register(models.Car)

View File

@ -40,3 +40,46 @@ def breadcrumbs(request):
url = "/" + "/".join(path[: i + 1]) + "/" url = "/" + "/".join(path[: i + 1]) + "/"
breadcrumbs.append({"name": path[i].capitalize(), "url": url}) breadcrumbs.append({"name": path[i].capitalize(), "url": url})
return {"breadcrumbs": breadcrumbs} 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, Vendor,
Schedule, Schedule,
Car, Car,
VatRate,
CarTransfer, CarTransfer,
CarFinance, CarFinance,
CustomCard, CustomCard,
@ -143,7 +144,7 @@ class StaffForm(forms.ModelForm):
) )
class Meta: class Meta:
model = Staff model = Staff
fields = ["name", "arabic_name", "phone_number", "group"] fields = ["name", "arabic_name", "phone_number", "address","image","group"]
# Dealer Form # Dealer Form
@ -2091,3 +2092,16 @@ class CSVUploadForm(forms.Form):
# Reset file pointer for later processing # Reset file pointer for later processing
csv_file.file.seek(0) csv_file.file.seek(0)
return csv_file 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 import logging
from django.http import Http404, HttpResponseForbidden # from django.http import Http404, HttpResponseForbidden
from django.shortcuts import redirect # from django.shortcuts import redirect
from inventory import models # from inventory import models
from django.utils import timezone # from django.utils import timezone
from inventory.utils import get_user_type from inventory.utils import get_user_type
@ -102,17 +102,24 @@ class InjectDealerMiddleware:
request.is_inventory = False request.is_inventory = False
if hasattr(request.user, "dealer"): if hasattr(request.user, "dealer"):
request.is_dealer = True request.is_dealer = True
request.dealer = request.user.dealer
elif hasattr(request.user, "staffmember"): elif hasattr(request.user, "staffmember"):
request.is_staff = True request.is_staff = True
request.staff = request.user.staffmember.staff
request.dealer = request.staff.dealer
staff = getattr(request.user.staffmember, "staff") 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 request.is_accountant = True
if "Manager" in staff.groups.values_list("name", flat=True): elif "Manager" in staff_groups:
request.is_manager = True request.is_manager = True
if "Sales" in staff.groups.values_list("name", flat=True): elif "Sales" in staff_groups:
request.is_sales = True request.is_sales = True
if "Inventory" in staff.groups.values_list("name", flat=True): elif "Inventory" in staff_groups:
request.is_inventory = True request.is_inventory = True
request.entity = request.dealer.entity
request.admin = request.dealer.entity.admin
except Exception: except Exception:
pass pass
response = self.get_response(request) response = self.get_response(request)

View File

@ -19,7 +19,7 @@ class Migration(migrations.Migration):
initial = True initial = True
dependencies = [ dependencies = [
('appointment', '0001_initial'), ('appointment', '__first__'),
('auth', '0012_alter_user_first_name_max_length'), ('auth', '0012_alter_user_first_name_max_length'),
('contenttypes', '0002_remove_content_type_name'), ('contenttypes', '0002_remove_content_type_name'),
('django_ledger', '0021_alter_bankaccountmodel_account_model_and_more'), ('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 appointment.models import StaffMember
from plans.quota import get_user_quota from plans.quota import get_user_quota
from plans.models import UserPlan from plans.models import UserPlan
from django.db.models import Q
# from plans.models import AbstractPlan # from plans.models import AbstractPlan
# from simple_history.models import HistoricalRecords # from simple_history.models import HistoricalRecords
@ -190,6 +191,7 @@ class UnitOfMeasure(models.TextChoices):
class VatRate(models.Model): 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")) rate = models.DecimalField(max_digits=5, decimal_places=2, default=Decimal("0.15"))
is_active = models.BooleanField(default=True) is_active = models.BooleanField(default=True)
created_at = models.DateTimeField(auto_now_add=True) created_at = models.DateTimeField(auto_now_add=True)
@ -243,6 +245,11 @@ class CarMake(models.Model, LocalizedNameMixin):
class Meta: class Meta:
verbose_name = _("Make") 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): class CarModel(models.Model, LocalizedNameMixin):
@ -272,6 +279,11 @@ class CarModel(models.Model, LocalizedNameMixin):
class Meta: class Meta:
verbose_name = _("Model") 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): class CarSerie(models.Model, LocalizedNameMixin):
@ -306,6 +318,12 @@ class CarSerie(models.Model, LocalizedNameMixin):
class Meta: class Meta:
verbose_name = _("Series") 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): class CarTrim(models.Model, LocalizedNameMixin):
@ -339,6 +357,11 @@ class CarTrim(models.Model, LocalizedNameMixin):
class Meta: class Meta:
verbose_name = _("Trim") 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): class CarEquipment(models.Model, LocalizedNameMixin):
@ -359,6 +382,11 @@ class CarEquipment(models.Model, LocalizedNameMixin):
class Meta: class Meta:
verbose_name = _("Equipment") 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): class CarSpecification(models.Model, LocalizedNameMixin):
@ -390,6 +418,10 @@ class CarSpecification(models.Model, LocalizedNameMixin):
class Meta: class Meta:
verbose_name = _("Specification") 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): class CarSpecificationValue(models.Model):
@ -406,6 +438,11 @@ class CarSpecificationValue(models.Model):
class Meta: class Meta:
verbose_name = _("Specification Value") 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): class CarOption(models.Model, LocalizedNameMixin):
@ -437,6 +474,10 @@ class CarOption(models.Model, LocalizedNameMixin):
class Meta: class Meta:
verbose_name = _("Option") 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): class CarOptionValue(models.Model):
@ -456,6 +497,12 @@ class CarOptionValue(models.Model):
class Meta: class Meta:
verbose_name = _("Option Value") 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): class CarTransferStatusChoices(models.TextChoices):
@ -517,7 +564,7 @@ class AdditionalServices(models.Model, LocalizedNameMixin):
@property @property
def price_(self): def price_(self):
vat = VatRate.objects.filter(is_active=True).first() vat = VatRate.objects.filter(dealer=self.dealer,is_active=True).first()
return ( return (
Decimal(self.price + (self.price * vat.rate)) Decimal(self.price + (self.price * vat.rate))
if self.taxable if self.taxable
@ -614,6 +661,26 @@ class Car(Base):
class Meta: class Meta:
verbose_name = _("Car") verbose_name = _("Car")
verbose_name_plural = _("Cars") 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): def __str__(self):
make = self.id_car_make.name if self.id_car_make else "Unknown Make" 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( selling_price = models.DecimalField(
max_digits=14, decimal_places=2, verbose_name=_("Selling Price") 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( discount_amount = models.DecimalField(
max_digits=14, max_digits=14,
decimal_places=2, decimal_places=2,
@ -855,7 +925,7 @@ class CarFinance(models.Model):
@property @property
def vat_amount(self): 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: if vat:
return (self.total_discount * Decimal(vat.rate)).quantize(Decimal("0.01")) return (self.total_discount * Decimal(vat.rate)).quantize(Decimal("0.01"))
return Decimal("0.00") return Decimal("0.00")
@ -891,6 +961,12 @@ class CarFinance(models.Model):
class Meta: class Meta:
verbose_name = _("Car Financial Details") verbose_name = _("Car Financial Details")
verbose_name_plural = _("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): class ExteriorColors(models.Model, LocalizedNameMixin):
@ -901,6 +977,10 @@ class ExteriorColors(models.Model, LocalizedNameMixin):
class Meta: class Meta:
verbose_name = _("Exterior Colors") verbose_name = _("Exterior Colors")
verbose_name_plural = _("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): def __str__(self):
return f"{self.name} ({self.rgb})" return f"{self.name} ({self.rgb})"
@ -914,6 +994,10 @@ class InteriorColors(models.Model, LocalizedNameMixin):
class Meta: class Meta:
verbose_name = _("Interior Colors") verbose_name = _("Interior Colors")
verbose_name_plural = _("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): def __str__(self):
return f"{self.name} ({self.rgb})" return f"{self.name} ({self.rgb})"
@ -932,6 +1016,11 @@ class CarColors(models.Model):
verbose_name = _("Color") verbose_name = _("Color")
verbose_name_plural = _("Colors") verbose_name_plural = _("Colors")
unique_together = ("car", "exterior", "interior") 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): def __str__(self):
return f"{self.car} ({self.exterior.name}) ({self.interior.name})" return f"{self.car} ({self.exterior.name}) ({self.interior.name})"
@ -1105,9 +1194,13 @@ class Dealer(models.Model, LocalizedNameMixin):
return True return True
return False return False
@property
def vat_rate(self):
return VatRate.objects.get(dealer=self,is_active=True).rate
class Meta: class Meta:
verbose_name = _("Dealer") verbose_name = _("Dealer")
verbose_name_plural = _("Dealers") verbose_name_plural = _("Dealers")
indexes = [models.Index(fields=["name"])]
# permissions = [ # permissions = [
# ('change_dealer_type', 'Can change dealer type'), # ('change_dealer_type', 'Can change dealer type'),
# ] # ]
@ -1137,6 +1230,12 @@ class Staff(models.Model, LocalizedNameMixin):
staff_type = models.CharField( staff_type = models.CharField(
choices=StaffTypes.choices, max_length=255, verbose_name=_("Staff Type") 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")) active = models.BooleanField(default=True, verbose_name=_("Active"))
created = models.DateTimeField(auto_now_add=True, verbose_name=_("Created")) created = models.DateTimeField(auto_now_add=True, verbose_name=_("Created"))
updated = models.DateTimeField(auto_now=True, verbose_name=_("Updated")) updated = models.DateTimeField(auto_now=True, verbose_name=_("Updated"))
@ -1188,7 +1287,7 @@ class Staff(models.Model, LocalizedNameMixin):
@property @property
def groups(self): 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): def clear_groups(self):
self.remove_superuser_permission() self.remove_superuser_permission()
@ -1199,21 +1298,28 @@ class Staff(models.Model, LocalizedNameMixin):
self.clear_groups() self.clear_groups()
try: try:
self.user.groups.add(group) 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() self.add_superuser_permission()
except Exception as e: except Exception as e:
print(e) print(e)
def add_superuser_permission(self): def add_superuser_permission(self):
pass entity = self.dealer.entity
# self.dealer.entity.managers.add(self.user) if entity.managers.count() == 0:
entity.managers.add(self.user)
def remove_superuser_permission(self): def remove_superuser_permission(self):
pass entity = self.dealer.entity
# self.dealer.entity.managers.remove(self.user) if self.user in entity.managers.all():
entity.managers.remove(self.user)
class Meta: class Meta:
verbose_name = _("Staff") verbose_name = _("Staff")
verbose_name_plural = _("Staff") verbose_name_plural = _("Staff")
indexes = [
models.Index(fields=["name"]),
models.Index(fields=["staff_type"]),
]
permissions = [] permissions = []
def __str__(self): def __str__(self):
@ -1369,6 +1475,13 @@ class Customer(models.Model):
class Meta: class Meta:
verbose_name = _("Customer") verbose_name = _("Customer")
verbose_name_plural = _("Customers") 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): def __str__(self):
# middle = f" {self.middle_name}" if self.middle_name else "" # middle = f" {self.middle_name}" if self.middle_name else ""
@ -1507,6 +1620,11 @@ class Organization(models.Model, LocalizedNameMixin):
class Meta: class Meta:
verbose_name = _("Organization") verbose_name = _("Organization")
verbose_name_plural = _("Organizations") verbose_name_plural = _("Organizations")
indexes = [
models.Index(fields=["name"]),
models.Index(fields=["email"]),
models.Index(fields=["phone_number"]),
]
def __str__(self): def __str__(self):
return self.name return self.name
@ -1696,6 +1814,17 @@ class Lead(models.Model):
class Meta: class Meta:
verbose_name = _("Lead") verbose_name = _("Lead")
verbose_name_plural = _("Leads") 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): def __str__(self):
return f"{self.first_name} {self.last_name}" return f"{self.first_name} {self.last_name}"
@ -1870,6 +1999,14 @@ class Schedule(models.Model):
class Meta: class Meta:
ordering = ["-scheduled_at"] 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): class LeadStatusHistory(models.Model):
@ -2042,6 +2179,14 @@ class Opportunity(models.Model):
class Meta: class Meta:
verbose_name = _("Opportunity") verbose_name = _("Opportunity")
verbose_name_plural = _("Opportunities") 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): def __str__(self):
if self.customer: if self.customer:
@ -2066,6 +2211,19 @@ class Notes(models.Model):
class Meta: class Meta:
verbose_name = _("Note") verbose_name = _("Note")
verbose_name_plural = _("Notes") 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): def __str__(self):
return f"Note by {self.created_by.first_name} on {self.content_object}" return f"Note by {self.created_by.first_name} on {self.content_object}"
@ -2096,6 +2254,19 @@ class Tasks(models.Model):
class Meta: class Meta:
verbose_name = _("Task") verbose_name = _("Task")
verbose_name_plural = _("Tasks") 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): def __str__(self):
return f"Task by {self.created_by.email} on {self.content_object}" return f"Task by {self.created_by.email} on {self.content_object}"
@ -2124,6 +2295,17 @@ class Email(models.Model):
class Meta: class Meta:
verbose_name = _("Email") verbose_name = _("Email")
verbose_name_plural = _("Emails") 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): def __str__(self):
return f"Email by {self.created_by.first_name} on {self.content_object}" return f"Email by {self.created_by.first_name} on {self.content_object}"
@ -2149,6 +2331,17 @@ class Activity(models.Model):
class Meta: class Meta:
verbose_name = _("Activity") verbose_name = _("Activity")
verbose_name_plural = _("Activities") 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): def __str__(self):
return f"{self.get_activity_type_display()} by {self.created_by.get_full_name} on {self.content_object}" 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") verbose_name_plural = _("Notifications")
ordering = ["-created"] 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): def __str__(self):
return self.message return self.message
@ -2230,6 +2429,12 @@ class Vendor(models.Model, LocalizedNameMixin):
class Meta: class Meta:
verbose_name = _("Vendor") verbose_name = _("Vendor")
verbose_name_plural = _("Vendors") 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): def __str__(self):
return self.name return self.name
@ -2422,7 +2627,7 @@ class SaleOrder(models.Model):
blank=True, blank=True,
) )
comments = models.TextField(blank=True, null=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 and Dates
status = models.CharField( status = models.CharField(
@ -2471,9 +2676,21 @@ class SaleOrder(models.Model):
) )
class Meta: class Meta:
verbose_name = "Sales Order" verbose_name = _("Sales Order")
verbose_name_plural = "Sales Orders" verbose_name_plural = _("Sales Orders")
ordering = ["-order_date"] # Order by most recent first 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): def save(self, *args, **kwargs):
if not self.formatted_order_id: if not self.formatted_order_id:
@ -2531,6 +2748,14 @@ class CustomGroup(models.Model):
group = models.OneToOneField( group = models.OneToOneField(
"auth.Group", verbose_name=_("Group"), on_delete=models.CASCADE "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 @property
def entity(self): def entity(self):
@ -2666,7 +2891,7 @@ class CustomGroup(models.Model):
app="inventory", app="inventory",
allowed_models=[ allowed_models=[
"saleorder", "saleorder",
"payment", # "payment",
"staff", "staff",
"schedule", "schedule",
"activity", "activity",
@ -2708,7 +2933,8 @@ class CustomGroup(models.Model):
"tasks", "tasks",
"activity", "activity",
"payment", "payment",
'vendor'], "vendor",
],
other_perms=[ other_perms=[
"view_car", "view_car",
"view_carlocation", "view_carlocation",
@ -2731,7 +2957,6 @@ class CustomGroup(models.Model):
"itemmodel", "itemmodel",
"invoicemodel", "invoicemodel",
"vendormodel", "vendormodel",
"journalentrymodel", "journalentrymodel",
"purchaseordermodel", "purchaseordermodel",
"estimatemodel", "estimatemodel",
@ -2935,6 +3160,13 @@ class PoItemsUploaded(models.Model):
created_at = models.DateTimeField(auto_now_add=True) created_at = models.DateTimeField(auto_now_add=True)
updated_at = models.DateTimeField(auto_now=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): def get_name(self):
return self.item.item.name.split('||') return self.item.item.name.split('||')
class ExtraInfo(models.Model): class ExtraInfo(models.Model):
@ -2944,7 +3176,13 @@ class ExtraInfo(models.Model):
- JSON data storage - JSON data storage
- Tracking fields - 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( content_type = models.ForeignKey(
ContentType, ContentType,
on_delete=models.CASCADE, on_delete=models.CASCADE,
@ -2986,7 +3224,9 @@ class ExtraInfo(models.Model):
models.Index(fields=['content_type', 'object_id']), models.Index(fields=['content_type', 'object_id']),
models.Index(fields=['related_content_type', 'related_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): def __str__(self):
return f"ExtraInfo for {self.content_object} ({self.content_type})" 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_content_type=related_content_type,
related_object_id=staff.pk related_object_id=staff.pk
) )
# qs = qs.select_related("customer","estimate","invoice")
return [x.content_object.sale_orders.first() for x in qs if x.content_object.sale_orders.first()] return [x.content_object.sale_orders.select_related("customer","estimate","invoice").first() for x in qs if x.content_object.sale_orders.first()]
@classmethod @classmethod
def get_invoices(cls, staff=None, is_dealer=False): def get_invoices(cls, staff=None, is_dealer=False):
if not staff and not is_dealer: if not staff and not is_dealer:

View File

@ -1,4 +1,5 @@
import logging import logging
from .models import Dealer
from django.core.exceptions import ImproperlyConfigured,ValidationError from django.core.exceptions import ImproperlyConfigured,ValidationError
from django.contrib.auth.mixins import LoginRequiredMixin,PermissionRequiredMixin from django.contrib.auth.mixins import LoginRequiredMixin,PermissionRequiredMixin
from django_ledger.forms.bill import ( 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.detail import SingleObjectMixin
from django.views.generic.edit import UpdateView from django.views.generic.edit import UpdateView
from django.views.generic.base import RedirectView 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__) logger = logging.getLogger(__name__)
@ -213,7 +215,7 @@ class PurchaseOrderModelUpdateView(LoginRequiredMixin,
if form.has_changed(): if form.has_changed():
po_items_qs = ItemTransactionModel.objects.for_po( po_items_qs = ItemTransactionModel.objects.for_po(
entity_slug=self.kwargs['entity_slug'], entity_slug=self.kwargs['entity_slug'],
user_model=dealer.entity.admin, user_model=self.request.admin,
po_pk=po_model.uuid, po_pk=po_model.uuid,
).select_related('bill_model') ).select_related('bill_model')
@ -314,12 +316,23 @@ class BasePurchaseOrderActionActionView(LoginRequiredMixin,
) )
except ValidationError as e: except ValidationError as e:
# --- Single-line log for ValidationError --- # --- 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( logger.warning(
f"User {user_username} encountered a validation error " f"User {user_username} encountered a validation error "
f"while performing action '{self.action_name}' on Purchase Order ID: {po_model.pk}. " f"while performing action '{self.action_name}' on Purchase Order ID: {po_model.pk}. "
f"Error: {e}" 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 return response
class BillModelDetailView(LoginRequiredMixin, PermissionRequiredMixin, DetailView): class BillModelDetailView(LoginRequiredMixin, PermissionRequiredMixin, DetailView):
@ -406,19 +419,23 @@ class BillModelUpdateView(LoginRequiredMixin, PermissionRequiredMixin, UpdateVie
def get_form(self, form_class=None): def get_form(self, form_class=None):
form_class = self.get_form_class() form_class = self.get_form_class()
dealer = get_object_or_404(Dealer,slug=self.kwargs['dealer_slug']) entity_model = self.request.dealer.entity
entity_model = dealer.entity
if self.request.method == 'POST' and self.action_update_items: if self.request.method == 'POST' and self.action_update_items:
return form_class( return form_class(
entity_model=entity_model, entity_model=entity_model,
user_model=dealer.entity.admin, user_model=self.request.admin,
instance=self.object instance=self.object
) )
return form_class( form = form_class(
entity_model=entity_model, entity_model=entity_model,
user_model=dealer.entity.admin, user_model=self.request.admin,
**self.get_form_kwargs() **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): def get_form_class(self):
bill_model: BillModel = self.object bill_model: BillModel = self.object
@ -642,3 +659,41 @@ class BaseBillActionView(LoginRequiredMixin,PermissionRequiredMixin, RedirectVie
level=messages.ERROR, level=messages.ERROR,
extra_tags='is-danger') extra_tags='is-danger')
return response 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 decimal import Decimal
from django.urls import reverse from django.urls import reverse
from inventory.tasks import create_coa_accounts, create_make_accounts from inventory.tasks import create_coa_accounts, create_make_accounts
from django.contrib.auth.models import Group from django.contrib.auth.models import Group
from django.db.models.signals import post_save, post_delete from django.db.models.signals import post_save, post_delete
from django.dispatch import receiver from django.dispatch import receiver
from appointment.models import Service
from django.utils.translation import gettext_lazy as _ from django.utils.translation import gettext_lazy as _
from django.contrib.contenttypes.models import ContentType from django.contrib.contenttypes.models import ContentType
from django.contrib.auth import get_user_model from django.contrib.auth import get_user_model
@ -672,6 +672,7 @@ def create_dealer_settings(sender, instance, created, **kwargs):
:return: None :return: None
""" """
if created: if created:
models.VatRate.objects.create(dealer=instance)
models.DealerSettings.objects.create( models.DealerSettings.objects.create(
dealer=instance, dealer=instance,
invoice_cash_account=instance.entity.get_all_accounts() 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) dealer = models.Dealer.objects.get(entity=instance.entity)
models.PoItemsUploaded.objects.create(dealer=dealer,po=instance, item=item, status="fulfilled") 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######################## ######################Notification########################
@ -1092,6 +1098,6 @@ def bill_model_after_approve_notification(sender, instance, created, **kwargs):
message=f""" message=f"""
Bill {instance.bill_number} has been approved. 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>. <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(), views.DealerUpdateView.as_view(),
name="dealer_update", 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'), # path('dealers/<int:pk>/delete/', views.DealerDeleteView.as_view(), name='dealer_delete'),
# CRM URLs # CRM URLs
path( path(
@ -683,6 +688,17 @@ urlpatterns = [
views.preview_sale_order, views.preview_sale_order,
name="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 # Invoice
############################################### ###############################################

View File

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

View File

@ -88,7 +88,6 @@ from django_ledger.views import (
LedgerModelCreateView as LedgerModelCreateViewBase, LedgerModelCreateView as LedgerModelCreateViewBase,
) )
from django_ledger.forms.account import AccountModelCreateForm, AccountModelUpdateForm from django_ledger.forms.account import AccountModelCreateForm, AccountModelUpdateForm
from django_ledger.views.inventory import InventoryListView as InventoryListViewBase
from django_ledger.views.entity import ( from django_ledger.views.entity import (
EntityModelDetailBaseView, EntityModelDetailBaseView,
EntityModelDetailHandlerView, EntityModelDetailHandlerView,
@ -143,6 +142,7 @@ from .override import (
BillModelDetailView as BillModelDetailViewBase, BillModelDetailView as BillModelDetailViewBase,
BillModelUpdateView as BillModelUpdateViewBase, BillModelUpdateView as BillModelUpdateViewBase,
BaseBillActionView as BaseBillActionViewBase, BaseBillActionView as BaseBillActionViewBase,
InventoryListView as InventoryListViewBase,
) )
from django_ledger.models import ( from django_ledger.models import (
@ -440,8 +440,16 @@ class ManagerDashboard(LoginRequiredMixin, TemplateView):
transfer_cars = models.Car.objects.filter( transfer_cars = models.Car.objects.filter(
dealer=dealer, status=models.CarStatusChoices.TRANSFER dealer=dealer, status=models.CarStatusChoices.TRANSFER
).count() ).count()
reserved_percentage = reserved_cars / total_cars * 100 try:
sold_percentage = sold_cars / total_cars * 100 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 = ( qs = (
models.Car.objects.values("id_car_make__name") models.Car.objects.values("id_car_make__name")
.annotate(count=Count("id")) .annotate(count=Count("id"))
@ -1585,7 +1593,6 @@ class CarUpdateView(
def get_form(self, form_class=None): def get_form(self, form_class=None):
form = super().get_form(form_class) form = super().get_form(form_class)
dealer = get_user_type(self.request) dealer = get_user_type(self.request)
print(dealer.get_vendors())
form.fields["vendor"].queryset = dealer.vendors.all() form.fields["vendor"].queryset = dealer.vendors.all()
return form return form
@ -2124,12 +2131,16 @@ class DealerDetailView(LoginRequiredMixin, PermissionRequiredMixin,DetailView):
context["cars_count"] = cars_count context["cars_count"] = cars_count
context["allowed_users"] = dealer.user_quota context["allowed_users"] = dealer.user_quota
context["allowed_cars"] = dealer.car_quota context["allowed_cars"] = dealer.car_quota
context["vatform"] = forms.VatRateForm(initial={"rate": dealer.vat_rate})
context["quota_display"] = ( context["quota_display"] = (
f"{staff_count}/{dealer.user_quota}" if dealer.user_quota else "0" f"{staff_count}/{dealer.user_quota}" if dealer.user_quota else "0"
) )
return context 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): class DealerUpdateView(LoginRequiredMixin,PermissionRequiredMixin, SuccessMessageMixin, UpdateView):
""" """
@ -2518,6 +2529,8 @@ def vendorDetailView(request, dealer_slug,slug):
) )
class VendorCreateView( class VendorCreateView(
LoginRequiredMixin, LoginRequiredMixin,
PermissionRequiredMixin, PermissionRequiredMixin,
@ -4298,11 +4311,14 @@ class EstimateListView(LoginRequiredMixin, PermissionRequiredMixin, ListView):
if any([self.request.is_dealer ,self.request.is_manager ,self.request.is_accountant]): if any([self.request.is_dealer ,self.request.is_manager ,self.request.is_accountant]):
qs = models.ExtraInfo.objects.filter( qs = models.ExtraInfo.objects.filter(
dealer=dealer,
content_type=ContentType.objects.get_for_model(EstimateModel), content_type=ContentType.objects.get_for_model(EstimateModel),
related_content_type=ContentType.objects.get_for_model(models.Staff), related_content_type=ContentType.objects.get_for_model(models.Staff),
) )
print(qs)
elif self.request.is_staff and self.request.is_sales: elif self.request.is_staff and self.request.is_sales:
qs = models.ExtraInfo.objects.filter( qs = models.ExtraInfo.objects.filter(
dealer=dealer,
content_type=ContentType.objects.get_for_model(EstimateModel), content_type=ContentType.objects.get_for_model(EstimateModel),
related_content_type=ContentType.objects.get_for_model(models.Staff), related_content_type=ContentType.objects.get_for_model(models.Staff),
related_object_id=staff.pk, 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): if staff:=getattr(request.user.staffmember,'staff',None):
models.ExtraInfo.objects.create( models.ExtraInfo.objects.create(
dealer=dealer,
content_object=estimate, content_object=estimate,
related_object=staff, related_object=staff,
created_by=request.user, created_by=request.user,
) )
else: else:
models.ExtraInfo.objects.create( models.ExtraInfo.objects.create(
dealer=dealer,
content_object=estimate, content_object=estimate,
related_object=request.user, related_object=request.user,
created_by=request.user, created_by=request.user,
data={"vat_rate": 0.15,"discount": 0},
) )
url = reverse( url = reverse(
@ -4612,14 +4631,22 @@ class EstimateDetailView(LoginRequiredMixin, PermissionRequiredMixin, DetailView
permission_required = ["django_ledger.view_estimatemodel"] permission_required = ["django_ledger.view_estimatemodel"]
def get_context_data(self, **kwargs): def get_context_data(self, **kwargs):
dealer = get_object_or_404(models.Dealer, slug=self.kwargs["dealer_slug"])
estimate = kwargs.get("object") estimate = kwargs.get("object")
if estimate.get_itemtxs_data(): if estimate.get_itemtxs_data():
calculator = CarFinanceCalculator(estimate) calculator = CarFinanceCalculator(estimate)
finance_data = calculator.get_finance_data() finance_data = calculator.get_finance_data()
print(finance_data)
invoice_obj = InvoiceModel.objects.all().filter(ce_model=estimate).first() invoice_obj = InvoiceModel.objects.all().filter(ce_model=estimate).first()
kwargs["data"] = finance_data kwargs["data"] = finance_data
kwargs["invoice"] = invoice_obj 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) 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): class SaleOrderDetail(LoginRequiredMixin,PermissionRequiredMixin,DetailView):
model = models.SaleOrder model = models.SaleOrder
template_name = "sales/orders/order_details.html" template_name = "sales/orders/order_details.html"
@ -5331,6 +5383,7 @@ def PaymentCreateView(request, dealer_slug, pk):
model = invoice if invoice else bill model = invoice if invoice else bill
entity = dealer.entity entity = dealer.entity
form = forms.PaymentForm() form = forms.PaymentForm()
breakpoint()
if request.method == "POST": if request.method == "POST":
form = forms.PaymentForm(request.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") qs = models.Lead.objects.filter(dealer=dealer).exclude(status="converted")
if query: if query:
qs = qs.filter(Q(first_name__icontains=query) qs = qs.filter(Q(first_name__icontains=query)
| Q(last_name__icontains=query) | Q(last_name__icontains=query)
| Q(id_car_make__name__icontains=query) | Q(id_car_make__name__icontains=query)
| Q(id_car_model__name__icontains=query) | Q(id_car_model__name__icontains=query)
| Q(email__icontains=query) | Q(email__icontains=query)
| Q(phone_number__icontains=query) | Q(phone_number__icontains=query)
| Q(next_action__icontains=query) | Q(next_action__icontains=query)
| Q(staff__name__icontains=query)) | Q(staff__name__icontains=query))
if self.request.is_dealer: if self.request.is_dealer :#or self.request.is_manager:
return qs return qs
if self.request.user.is_staff: if self.request.is_staff:
staff = getattr(self.request.user.staffmember, "staff", None) return qs.filter(staff=self.request.staff)
return qs.filter(staff=staff)
return models.Lead.objects.none() return models.Lead.objects.none()
@ -5659,7 +5711,7 @@ class LeadDetailView(LoginRequiredMixin, PermissionRequiredMixin, DetailView):
context["transfer_form"] = forms.LeadTransferForm() context["transfer_form"] = forms.LeadTransferForm()
context["transfer_form"].fields[ context["transfer_form"].fields[
"transfer_to" "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() 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() context["activity_form"] = forms.ActivityForm()
@ -5779,15 +5831,12 @@ def lead_create(request,dealer_slug):
qs = form.fields["id_car_make"].queryset.filter( qs = form.fields["id_car_make"].queryset.filter(
is_sa_import=True, pk__in=dealer_make_list is_sa_import=True, pk__in=dealer_make_list
) )
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()
dealer=dealer,staff_member__user__groups__permissions__codename__contains="add_lead").distinct()
if request.is_staff:
if hasattr(request.user.staffmember, "staff"): form.initial["staff"] = request.staff
staff = request.user.staffmember.staff
form.initial["staff"] = staff
form.fields["staff"].widget.attrs.update({"readonly":"true","required":"true"}) 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"].queryset = qs
form.fields["id_car_make"].choices = [ form.fields["id_car_make"].choices = [
(obj.id_car_make, obj.get_local_name()) for obj in qs (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) @permission_required("inventory.view_lead", raise_exception=True)
def lead_tracking(request,dealer_slug): def lead_tracking(request,dealer_slug):
dealer = get_object_or_404(models.Dealer,slug=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: if staff:
qs = models.Lead.objects.filter(dealer=dealer,staff=staff) qs = models.Lead.objects.filter(dealer=dealer,staff=staff)
@ -5945,7 +5994,7 @@ class LeadUpdateView(LoginRequiredMixin, PermissionRequiredMixin, UpdateView):
form.fields[ form.fields[
"id_car_model" "id_car_model"
].queryset = form.instance.id_car_make.carmodel_set.all() ].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() dealer=dealer,staff_member__user__groups__permissions__codename__contains="add_lead").distinct()
return form return form
@ -6880,8 +6929,8 @@ class ItemServiceCreateView(
permission_required = ["inventory.add_additionalservices"] permission_required = ["inventory.add_additionalservices"]
def form_valid(self, form): def form_valid(self, form):
vat = models.VatRate.objects.get(is_active=True)
dealer = get_user_type(self.request) dealer = get_user_type(self.request)
vat = models.VatRate.objects.get(dealer=dealer,is_active=True)
form.instance.dealer = dealer form.instance.dealer = dealer
if form.instance.taxable: if form.instance.taxable:
form.instance.price = (form.instance.price * vat.rate) + form.instance.price form.instance.price = (form.instance.price * vat.rate) + form.instance.price
@ -6927,8 +6976,8 @@ class ItemServiceUpdateView(
def form_valid(self, form): def form_valid(self, form):
vat = models.VatRate.objects.get(is_active=True)
dealer = get_user_type(self.request) dealer = get_user_type(self.request)
vat = models.VatRate.objects.get(dealer=dealer,is_active=True)
form.instance.dealer = dealer form.instance.dealer = dealer
if form.instance.taxable: if form.instance.taxable:
form.instance.price = (form.instance.price * vat.rate) + form.instance.price form.instance.price = (form.instance.price * vat.rate) + form.instance.price
@ -7575,7 +7624,7 @@ class OrderListView(LoginRequiredMixin, PermissionRequiredMixin, ListView):
def get_queryset(self): def get_queryset(self):
dealer = get_user_type(self.request) 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) return qs.filter(estimate__entity=dealer.entity)
@ -8676,12 +8725,13 @@ class LedgerModelListView(LoginRequiredMixin,PermissionRequiredMixin, ListView,
show_visible = False show_visible = False
allow_empty = True allow_empty = True
paginate_by = 30 paginate_by = 30
permission_required = "django_ledger.view_ledgermodel"
def get_queryset(self): def get_queryset(self):
qs = super().get_queryset() qs = super().get_queryset()
dealer = get_user_type(self.request) # dealer = get_object_or_404(models.Dealer, slug=self.kwargs["dealer_slug"])
qs = qs.filter(entity=dealer.entity) # dealer = get_user_type(self.request)
qs = qs.filter(entity=self.request.entity)
qs = qs.select_related("billmodel", "invoicemodel") qs = qs.select_related("billmodel", "invoicemodel")
qs = qs.order_by("-created") qs = qs.order_by("-created")
if self.show_all: if self.show_all:
@ -8694,8 +8744,7 @@ class LedgerModelListView(LoginRequiredMixin,PermissionRequiredMixin, ListView,
def get_context_data(self, **kwargs): def get_context_data(self, **kwargs):
context = super().get_context_data(**kwargs) context = super().get_context_data(**kwargs)
dealer = get_user_type(self.request) context["entity_slug"] = self.request.dealer.entity.slug
context["entity_slug"] = dealer.entity.slug
return context return context
@ -8742,16 +8791,14 @@ class LedgerModelCreateView(LedgerModelCreateViewBase):
permission_required = ["django_ledger.add_ledgermodel"] permission_required = ["django_ledger.add_ledgermodel"]
def get_form(self, form_class=None): def get_form(self, form_class=None):
dealer = get_user_type(self.request)
return LedgerModelCreateForm( return LedgerModelCreateForm(
entity_slug=dealer.entity.slug, entity_slug=self.request.dealer.entity.slug,
user_model=dealer.entity.admin, user_model=self.request.entity.admin,
**self.get_form_kwargs(), **self.get_form_kwargs(),
) )
def form_valid(self, form): def form_valid(self, form):
dealer = get_user_type(self.request) form.field["entity"] = self.request.dealer.entity
form.field["entity"] = dealer.entity
return super().form_valid(form) return super().form_valid(form)
def get_success_url(self): def get_success_url(self):
@ -10253,9 +10300,11 @@ def upload_cars(request, dealer_slug, pk=None):
car_make = get_make(manufacturer_name) car_make = get_make(manufacturer_name)
car_model = get_model(model_name, car_make) car_model = get_model(model_name, car_make)
if ( if (
not all([car_make, car_model]) not all([car_make])
or (make.pk != car_make.pk) 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( logger.warning(
f"User {user_username} uploaded CSV with VIN '{row['vin']}' " f"User {user_username} uploaded CSV with VIN '{row['vin']}' "
@ -10283,12 +10332,20 @@ def upload_cars(request, dealer_slug, pk=None):
vendor=vendor, vendor=vendor,
receiving_date=receiving_date, 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) car.add_colors(exterior=exterior, interior=interior)
cars_created += 1 cars_created += 1
logger.debug( logger.debug(
f"User {user_username} created Car ID: {car.pk} (VIN: {car.vin}). " f"User {user_username} created Car ID: {car.pk} (VIN: {car.vin}). "
f"Count: {cars_created}." f"Count: {cars_created}."
) )
if po_item: if po_item:
po_item.status = "uploaded" po_item.status = "uploaded"
po_item.save() po_item.save()
@ -10368,12 +10425,3 @@ def bulk_update_car_price(request):
class InventoryListView(InventoryListViewBase): class InventoryListView(InventoryListViewBase):
template_name = "inventory/list.html" template_name = "inventory/list.html"
permission_required = ["django_ledger.view_purchaseordermodel"] 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 annotated-types
anyio==4.9.0 anyio
arrow==1.3.0 arrow
asgiref==3.8.1 asgiref
attrs==25.3.0 attrs
Babel==2.15.0 Babel
beautifulsoup4==4.13.4 beautifulsoup4
blessed==1.21.0 blessed
cattrs==24.1.3 cattrs
certifi==2025.1.31 certifi
cffi==1.17.1 cffi
charset-normalizer==3.4.1 charset-normalizer
click==8.2.1 click
colorama==0.4.6 colorama
crispy-bootstrap5==2024.10 crispy-bootstrap5
cryptography==44.0.2 cryptography
cssbeautifier==1.15.4 cssbeautifier
defusedxml==0.7.1 defusedxml
diff-match-patch==20241021 diff-match-patch
distro==1.9.0 distro
Django==5.2.3 Django
django-allauth==65.6.0 django-allauth
django-appointment==3.8.0 django-appointment
django-background-tasks==1.2.8 django-background-tasks
django-bootstrap5==25.1 django-bootstrap5
django-ckeditor==6.7.2 django-ckeditor
django-cors-headers==4.7.0 django-cors-headers
django-countries==7.6.1 django-countries
django-crispy-forms==2.3 django-crispy-forms
django-easy-audit==1.3.7 django-easy-audit
django-extensions==3.2.3 django-extensions
django-filter==25.1 django-filter
django-import-export==4.3.7 django-import-export
django-js-asset==3.1.2 django-js-asset
django-ledger==0.7.7 django-ledger
django-manager-utils==3.1.5 django-manager-utils
django-next-url-mixin==0.4.0 django-next-url-mixin
django-ordered-model==3.7.4 django-ordered-model
django-phonenumber-field==8.0.0 django-phonenumber-field
django-picklefield==3.3 django-picklefield
django-plans==2.0.0 django-plans
django-q2==1.8.0 django-q2
django-query-builder==3.2.0 django-query-builder
django-schema-graph==3.1.0 django-schema-graph
django-sequences==3.0 django-sequences
django-tables2==2.7.5 django-tables2
django-treebeard==4.7.1 django-treebeard
django-widget-tweaks==1.5.0 django-widget-tweaks
djangorestframework==3.15.2 djangorestframework
djhtml==3.0.7 djhtml
djlint==1.36.4 djlint
docopt==0.6.2 docopt
EditorConfig==0.17.0 EditorConfig
Faker==37.3.0 Faker
fleming==0.7.0 fleming
fonttools==4.57.0 fonttools
fpdf==1.7.2 fpdf
fpdf2==2.8.3 fpdf2
greenlet==3.2.2 greenlet
h11==0.14.0 h11
httpcore==1.0.7 httpcore
httpx==0.28.1 httpx
icalendar==6.1.2 icalendar
idna==3.10 idna
jiter==0.9.0 jiter
jsbeautifier==1.15.4 jsbeautifier
json5==0.12.0 json5
jsonpatch==1.33 jsonpatch
jsonpointer==3.0.0 jsonpointer
jwt==1.3.1 jwt
langchain==0.3.25 langchain
langchain-core==0.3.61 langchain-core
langchain-ollama==0.3.3 langchain-ollama
langchain-text-splitters==0.3.8 langchain-text-splitters
langsmith==0.3.42 langsmith
luhnchecker==0.0.12 luhnchecker
Markdown==3.8 Markdown
markdown-it-py==3.0.0 markdown-it-py
mdurl==0.1.2 mdurl
num2words==0.5.14 num2words
numpy==2.2.4 numpy
ofxtools==0.9.5 ofxtools
ollama==0.4.8 ollama
openai==1.68.2 openai
opencv-python==4.11.0.86 opencv-python
orjson==3.10.18 orjson
packaging==24.2 packaging
pandas==2.2.3 pandas
pathspec==0.12.1 pathspec
phonenumbers==8.13.42 phonenumbers
pillow==11.2.1 pillow
pycparser==2.22 pycparser
pydantic==2.10.6 pydantic
pydantic_core==2.27.2 pydantic_core
Pygments==2.19.1 Pygments
python-dateutil==2.9.0.post0 python-dateutil
python-slugify==8.0.4 python-slugify
python-stdnum==1.20 python-stdnum
pytz==2025.2 pytz
pyvin==0.0.2 pyvin
PyYAML==6.0.2 PyYAML
pyzbar==0.1.9 pyzbar
redis==3.5.3 redis
regex==2024.11.6 regex
requests==2.32.3 requests
requests-toolbelt==1.0.0 requests-toolbelt
rich==14.0.0 rich
ruff==0.11.10 ruff
setuptools==80.3.0 setuptools
six==1.17.0 six
sniffio==1.3.1 sniffio
soupsieve==2.7 soupsieve
SQLAlchemy==2.0.41 SQLAlchemy
sqlparse==0.5.3 sqlparse
suds==1.2.0 suds
swapper==1.3.0 swapper
tablib==3.8.0 tablib
tenacity==9.1.2 tenacity
text-unidecode==1.3 text-unidecode
tqdm==4.67.1 tqdm
types-python-dateutil==2.9.0.20250516 types-python-dateutil
typing_extensions==4.13.0 typing_extensions
tzdata==2025.2 tzdata
urllib3==2.3.0 urllib3
wcwidth==0.2.13 wcwidth
zstandard==0.23.0 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 { .form-control, .form-select {
text-align: center; /* text-align: center; */
display: flex; display: flex;
align-items: center; align-items: center;
justify-content: center; justify-content: center;
@ -131,3 +131,4 @@ html[dir="rtl"] .form-icon-container .form-control {
@keyframes spin { @keyframes spin {
to { transform: rotate(360deg); } to { transform: rotate(360deg); }
} }

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

File diff suppressed because one or more lines are too long

View File

@ -26,19 +26,19 @@
<meta name="msapplication-TileImage" content="{% static 'images/logos/logo-d.png' %}"> <meta name="msapplication-TileImage" content="{% static 'images/logos/logo-d.png' %}">
<meta name="theme-color" content="#ffffff"> <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/config.js' %}"></script>
<script src="{% static 'js/sweetalert2.all.min.js' %}"></script> <script src="{% static 'js/sweetalert2.all.min.js' %}"></script>
<link href="{% static 'vendors/mapbox-gl/mapbox-gl.css' %}" rel="stylesheet"> {% comment %} <link href="{% static 'vendors/mapbox-gl/mapbox-gl.css' %}" rel="stylesheet"> {% endcomment %}
<link href="{% static 'vendors/swiper/swiper-bundle.min.css' %}" rel="stylesheet"> {% 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.googleapis.com">
<link rel="preconnect" href="https://fonts.gstatic.com" crossorigin=""> <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="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 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 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' %} {% if LANGUAGE_CODE == 'ar' %}
<link href="{% static 'css/theme-rtl.min.css' %}" type="text/css" rel="stylesheet" id="style-rtl"> <link href="{% static 'css/theme-rtl.min.css' %}" type="text/css" rel="stylesheet" id="style-rtl">
<link href="{% static 'css/user-rtl.min.css' %}" type="text/css" rel="stylesheet" id="user-style-rtl"> <link href="{% static 'css/user-rtl.min.css' %}" type="text/css" rel="stylesheet" id="user-style-rtl">
@ -46,9 +46,9 @@
<link href="{% static 'css/theme.min.css' %}" type="text/css" rel="stylesheet" id="style-default"> <link href="{% static 'css/theme.min.css' %}" type="text/css" rel="stylesheet" id="style-default">
<link href="{% static 'css/user.min.css' %}" type="text/css" rel="stylesheet" id="user-style-default"> <link href="{% static 'css/user.min.css' %}" type="text/css" rel="stylesheet" id="user-style-default">
{% endif %} {% endif %}
<script src="{% static 'js/main.js' %}"></script> {% comment %} <script src="{% static 'js/main.js' %}"></script> {% endcomment %}
<script src="{% static 'js/jquery.min.js' %}"></script> {% comment %} <script src="{% static 'js/jquery.min.js' %}"></script> {% endcomment %}
<script src="{% static 'js/echarts.js' %}"></script> {% comment %} <script src="{% static 'js/echarts.js' %}"></script> {% endcomment %}
{% block customCSS %} {% block customCSS %}
@ -79,9 +79,9 @@
{% include 'footer.html' %} {% include 'footer.html' %}
</div> </div>
</main> </main>
<script src="{% static 'js/djetler.bundle.js' %}"></script> {% comment %} <script src="{% static 'js/djetler.bundle.js' %}"></script>
<script src="{% static 'js/js-utils.js' %}"></script> <script src="{% static 'js/js-utils.js' %}"></script> {% endcomment %}
<script src="{% static 'js/modal/show_modal.js' %}"></script> {% comment %} <script src="{% static 'js/modal/show_modal.js' %}"></script> {% endcomment %}
<!-- ===============================================--> <!-- ===============================================-->
<!-- JavaScripts--> <!-- JavaScripts-->
@ -90,31 +90,30 @@
<!--1--> <!--1-->
<script src="{% static 'vendors/bootstrap/bootstrap.min.js' %}"></script> <script src="{% static 'vendors/bootstrap/bootstrap.min.js' %}"></script>
<script src="{% static 'vendors/anchorjs/anchor.min.js' %}"></script> {% comment %} <script src="{% static 'vendors/anchorjs/anchor.min.js' %}"></script>
<script src="{% static 'vendors/is/is.min.js' %}"></script> <script src="{% static 'vendors/is/is.min.js' %}"></script> {% endcomment %}
<!--2--> <!--2-->
<script src="{% static 'vendors/fontawesome/all.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/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/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/phoenix.js' %}"></script>
<script src="{% static 'js/apexcharts.js' %}"></script> {% comment %} <script src="{% static 'js/apexcharts.js' %}"></script> {% endcomment %}
<script src="{% static 'vendors/echarts/echarts.min.js' %}"></script> {% comment %} <script src="{% static 'vendors/echarts/echarts.min.js' %}"></script> {% endcomment %}
<script src="{% static 'js/crm-analytics.js' %}"></script> {% comment %} <script src="{% static 'js/crm-analytics.js' %}"></script> {% endcomment %}
<script src="{% static 'js/travel-agency-dashboard.js' %}"></script> {% comment %} <script src="{% static 'js/travel-agency-dashboard.js' %}"></script>
<script src="{% static 'js/crm-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> {% comment %} <script src="{% static 'vendors/mapbox-gl/mapbox-gl.js' %}"></script> {% endcomment %}
<script src="{% static 'vendors/turf.min.js' %}"></script> {% comment %} <script src="{% static 'vendors/turf.min.js' %}"></script> {% endcomment %}
<script src="{% static 'vendors/htmx.min.js' %}"></script> <script src="{% static 'vendors/htmx.min.js' %}"></script>
<script src="{% static 'vendors/swiper/swiper-bundle.min.js' %}"></script> {% comment %} <script src="{% static 'vendors/swiper/swiper-bundle.min.js' %}"></script>
<script src="{% static 'vendors/flatpickr/flatpickr.min.js' %}"></script> <script src="{% static 'vendors/flatpickr/flatpickr.min.js' %}"></script> {% endcomment %}
<script> <script>
{% if entity_slug %} {% if entity_slug %}
let entitySlug = "{{ view.kwargs.entity_slug }}" let entitySlug = "{{ view.kwargs.entity_slug }}"
{% endif %} {% endif %}

View File

@ -5,7 +5,7 @@
{% load crispy_forms_filters %} {% load crispy_forms_filters %}
{% block content %} {% block content %}
<div class="row justify-content-center"> {% comment %} <div class="row justify-content-center">
<div class="col-lg-6"> <div class="col-lg-6">
<div class="card shadow-sm"> <div class="card shadow-sm">
<div class="card-header bg-light py-3"> <div class="card-header bg-light py-3">
@ -46,5 +46,63 @@
</form> </form>
</div> </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">
{% 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 %} {% endblock %}

View File

@ -45,11 +45,6 @@
<div class="card-body"> <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' %} {% 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> </div>
</div> </div>

View File

@ -9,7 +9,6 @@
<div class="container py-4"> <div class="container py-4">
<div class="row g-2"> <div class="row g-2">
<!-- Bill Form --> <!-- Bill Form -->
<div class="col-12"> <div class="col-12">
@ -18,10 +17,6 @@
<div class="card mb-2"> <div class="card mb-2">
<div class="card-body"> <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 %} {% 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"> <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 %} {% csrf_token %}
@ -30,20 +25,21 @@
{{ form|crispy }} {{ form|crispy }}
</div> </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' %} <i class="fas fa-save me-2"></i>{% trans 'Save Bill' %}
</button> </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> </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> </div>
</div> </div>
@ -51,7 +47,6 @@
</div> </div>
<!-- Bill Item Formset --> <!-- Bill Item Formset -->
<div class="col-12"> <div class="col-12">
{% bill_item_formset_table itemtxs_formset %} {% bill_item_formset_table itemtxs_formset %}

View File

@ -1,13 +1,15 @@
{% load django_ledger %} {% load django_ledger %}
{% load i18n %} {% load i18n %}
<div id="djl-bill-card-widget" class=""> <div id="djl-bill-card-widget" class="">
{% if not create_bill %} {% if not create_bill %}
{% if style == 'dashboard' %} {% if style == 'dashboard' %}
<!-- Dashboard Style Card --> <!-- Dashboard Style Card -->
<div class=""> <div class="">
<div class="card-body "> <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="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' %} <i class="fas fa-file-invoice me-2"></i>{% trans 'Bill' %}
</h6> </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>
@ -91,12 +93,20 @@
<!-- Detail Style Card --> <!-- Detail Style Card -->
<div class=""> <div class="">
<div class="card-header p-2 bg-{{ bill.get_status_badge_color }}"> <div class="card-header p-2 bg-{{ bill.get_status_badge_color }}">
<div class="d-flex align-items-center"> <div class="d-flex align-items-center justify-content-center mb-2 text-primary">
<i class="fas fa-file-invoice me-3 text-white"></i> <i class="fas fa-file-invoice me-3 "></i>
<h2 class="mb-0 text-white"> <h4 class="mb-0 text-primary me-2">
{% trans 'Bill' %} {{ bill.bill_number }} {% trans 'Bill' %} {{ bill.bill_number }}
</h2> </h4>
</div> </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>
<div class="card-body p-2 text-center"> <div class="card-body p-2 text-center">
{% if bill.is_draft %} {% if bill.is_draft %}
@ -221,12 +231,15 @@
<div class="d-flex flex-wrap gap-2 mt-2"> <div class="d-flex flex-wrap gap-2 mt-2">
<!-- Update Button --> <!-- Update Button -->
{% if perms.django_ledger.change_billmodel%} {% 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"> <button class="btn btn-phoenix-primary" {% if not request.is_accountant %} disabled {% endif %}>
<i class="fas fa-edit me-2"></i>{% trans 'Update' %} <a href="{% url 'bill-update' dealer_slug=dealer_slug entity_slug=entity_slug bill_pk=bill.uuid %}">
</a> <i class="fas fa-edit me-2"></i>{% trans 'Update' %}
</a>
</button>
<!-- Mark as Draft --> <!-- Mark as Draft -->
{% if bill.can_draft %} {% if bill.can_draft %}
<button class="btn btn-phoenix-success" <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')"> 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' %} <i class="fas fa-check-circle me-2"></i>{% trans 'Mark as Draft' %}
</button> </button>
@ -234,6 +247,7 @@
<!-- Mark as Review --> <!-- Mark as Review -->
{% if bill.can_review %} {% if bill.can_review %}
<button class="btn btn-phoenix-warning" <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')"> 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' %} <i class="fas fa-check-circle me-2"></i>{% trans 'Mark as Review' %}
</button> </button>
@ -245,6 +259,11 @@
<i class="fas fa-check-circle me-2"></i>{% trans 'Mark as Approved' %} <i class="fas fa-check-circle me-2"></i>{% trans 'Mark as Approved' %}
</button> </button>
{% endif %} {% 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 --> <!-- Mark as Paid -->
{% if bill.can_pay %} {% if bill.can_pay %}
<button class="btn btn-phoenix-success" <button class="btn btn-phoenix-success"
@ -262,6 +281,7 @@
<!-- Cancel Button --> <!-- Cancel Button -->
{% if bill.can_cancel %} {% if bill.can_cancel %}
<button class="btn btn-phoenix-danger" <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')"> 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' %} <i class="fas fa-check-circle me-2"></i>{% trans 'Mark as Canceled' %}
</button> </button>
@ -277,6 +297,7 @@
<!-- Create Bill Card --> <!-- Create Bill Card -->
{% if perms.django_ledger.add_billmodel%} {% if perms.django_ledger.add_billmodel%}
<div class=" bg-light"> <div class=" bg-light">
<div class="card-body text-center p-5"> <div class="card-body text-center p-5">
<a href="{% url 'django_ledger:bill-create' entity_slug=entity_slug %}" <a href="{% url 'django_ledger:bill-create' entity_slug=entity_slug %}"
class="text-primary"> class="text-primary">

View File

@ -3,15 +3,7 @@
{% if style == 'card_1' %} {% if style == 'card_1' %}
<div class="card h-100" style="height: 25rem;"> <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"> <div class="card-body overflow-auto">
{% if notes_html %} {% if notes_html %}
{{ notes_html|safe }} {{ notes_html|safe }}

View File

@ -46,7 +46,7 @@
<tr class="align-middle"> <tr class="align-middle">
<!-- Item Column --> <!-- Item Column -->
<td> <td>
<div class="d-flex flex-column"> <div class="d-flex flex-column ms-2">
{% for hidden_field in f.hidden_fields %} {% for hidden_field in f.hidden_fields %}
{{ hidden_field }} {{ hidden_field }}
{% endfor %} {% 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> <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> <h5 class="text-body-highlight fw-bold mb-0">{{ _("Lead Source")}}</h5>
</div> </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> <span class="text-body-secondary">{{ lead.source|upper }}</span>
</div> </div>
<div class="mb-3"> <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 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">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">Completed</th>
<th class="sort align-middle text-start text-uppercase" scope="col" data-sort="date" style="min-width:165px"></th>
</tr> </tr>
</thead> </thead>
<tbody class="list" id="all-tasks-table-body"> <tbody class="list" id="all-tasks-table-body">

View File

@ -1,13 +1,14 @@
{% extends 'base.html' %} {% extends 'base.html' %}
{% load i18n static crispy_forms_filters %} {% load i18n static crispy_forms_filters %}
{% block title %} {% block title %}
{# Check if an 'object' exists in the context #}
{% if object %} {% if object %}
{% trans 'Update Lead'%} {% trans 'Update Lead' %}
{% else %} {% else %}
{% trans 'Add New Lead'%} {% trans 'Add New Lead' %}
{% endif %} {% endif %}
{% endblock %} {% endblock %}
{% block customcss %} {% block customcss %}
<style> <style>
.htmx-indicator{ .htmx-indicator{
@ -17,49 +18,68 @@
.htmx-request .htmx-indicator{ .htmx-request .htmx-indicator{
opacity:1; opacity:1;
} }
.htmx-request.htmx-indicator{ .htmx-request.htmx-indicator{ /* For elements with htmx-indicator itself becoming the target */
opacity:1; 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> </style>
{% endblock customcss %} {% endblock customcss %}
{% block content %} {% block content %}
<div class="container-fluid"> <div class="row justify-content-center mt-5 mb-3">
<h1>{% if object %}{{ _("Update Lead") }}{% else %}{{ _("Create New Lead") }}{% endif %}</h1> <div class="col-lg-8 col-md-10">
<div class="row mb-3"> <div class="card shadow-sm border-0 rounded-3">
<div class="col-sm-6 col-md-8"> <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 }}
<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>
<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>
</div> </div>
</div> </div>
</div> </div>
<script> <script>
document.addEventListener('DOMContentLoaded', function() { document.addEventListener('DOMContentLoaded', function() {
// First, create the spinner div (or use the existing one)
const spinner = document.createElement('div'); const spinner = document.createElement('div');
spinner.id = 'spinner'; spinner.id = 'spinner';
spinner.className = 'htmx-indicator spinner-border inline-spinner'; spinner.className = 'htmx-indicator spinner-border text-primary inline-spinner';
spinner.setAttribute('role', 'status');
spinner.innerHTML = '<span class="visually-hidden">Loading...</span>'; 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 if (targetFieldDiv) {
// Replace 'id_your_field_name' with the actual ID of your form field targetFieldDiv.parentNode.insertBefore(spinner, targetFieldDiv.nextSibling);
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);
} }
}); });
</script> </script>

View File

@ -4,7 +4,7 @@
{% block content %} {% block content %}
<div class="row g-3 mt-4 mb-4"> <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 --> <!-- Action Tracking Modal -->
{% include "crm/leads/partials/update_action.html" %} {% include "crm/leads/partials/update_action.html" %}

View File

@ -68,7 +68,7 @@
<div class="row justify-content-center"> <div class="row justify-content-center">
<div class="col"> <div class="col">
<div class="d-flex justify-content-between mb-3"> <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>
<div class="row g-3"> <div class="row g-3">

View File

@ -5,7 +5,7 @@
{% block content %} {% block content %}
<div class="row g-3 mt-4"> <div class="row g-3 mt-4">
<div class="col-12"> <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>
<div class="col-12"> <div class="col-12">
<div class="d-flex flex-column flex-md-row justify-content-between align-items-md-center gap-3 mb-4"> <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'%} {% trans 'Add New Customer'%}
{% endif %} {% endif %}
{% endblock %} {% endblock %}
{% block content %} {% block content %}
<link rel="stylesheet" href="{% static 'flags/sprite.css' %}"> <div class="row justify-content-center mt-5 mb-3">
<div class="row">
<div class="row mb-3"> <div class="col-lg-8 col-md-10">
<div class="col-sm-6 col-md-8"> <div class="card shadow-sm border-o rounded-3">
<div class="d-sm-flex justify-content-between"> <div class="card-header bg-gray-200 py-3 border-0 rounded-top-3">
<h3 class="mb-3"> <h3 class="mb-0 fs-4 text-center">
{% if customer.created %} {% if customer.created %}
<i class="fa-solid fa-user"></i> {{ _("Edit Customer") }} <i class="fa-solid fa-user"></i> {{ _("Edit Customer") }}
{% else %} {% else %}
<i class="fa-solid fa-user"></i> {{ _("Add Customer") }} <i class="fa-solid fa-user"></i> {{ _("Add Customer") }}
{% endif %} {% endif %}
</h3> </h3>
</div>
</div> </div>
</div> <div class="card-body bg-light-subtle">
<div class="row mb-3">
<div class="col-sm-6 col-md-8">
<form method="post" class="form row g-3 needs-validation" enctype="multipart/form-data" novalidate> <form method="post" class="form row g-3 needs-validation" enctype="multipart/form-data" novalidate>
{% csrf_token %} {% csrf_token %}
{{ form|crispy }} {{ form|crispy }}
<div class="col-12"> <hr class="my-2">
<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 %}
<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 %} {% block content %}
<div class="row g-3 mt-4"> <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="row g-3 justify-content-between mb-4">
<div class="col-auto"> <div class="col-auto">
<div class="d-md-flex justify-content-between"> <div class="d-md-flex justify-content-between">

View File

@ -1,5 +1,5 @@
{% extends 'base.html' %} {% extends 'base.html' %}
{% load i18n static custom_filters%} {% load i18n static custom_filters crispy_forms_filters %}
{%block title%}{%trans 'Profile'%} {%endblock%} {%block title%}{%trans 'Profile'%} {%endblock%}
{% block content %} {% block content %}
<div class="container-fluid"> <div class="container-fluid">
@ -180,6 +180,26 @@
</div> </div>
</div> </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>
</div> </div>

View File

@ -64,6 +64,13 @@
</div> </div>
</a> </a>
</li> </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 %} {% endif %}
</ul> </ul>

View File

@ -28,16 +28,27 @@
aria-label="Close"></button> aria-label="Close"></button>
</div> </div>
{% endif %} {% 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 %} <div class="row justify-content-center mt-5 mb-3 {% if not vendor_exists %}disabled{% endif %}">
{% include 'partials/form_errors.html' %}
<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">
<div class="d-flex flex-column flex-sm-grow-1 p-0"> <div class="d-flex flex-column flex-sm-grow-1 p-0">
<div class="row g-4"> <div class="row g-4">
<h3 class="mb-3">{% trans 'Add Car' %}</h3>
<!-- VIN Section --> <!-- 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 bg-body mb-3 shadow-sm">
<div class="card-body"> <div class="card-body">
<!-- Improved VIN input with integrated buttons --> <!-- Improved VIN input with integrated buttons -->
@ -144,7 +155,7 @@
</div> </div>
</div> </div>
<div class="row g-3"> <div class="row g-3">
<div class="col-xl-6"> <div class="col-lg-12">
<div class="row"> <div class="row">
<!--Vendor Field--> <!--Vendor Field-->
<div class="col-lg-4 col-xl-4"> <div class="col-lg-4 col-xl-4">
@ -202,28 +213,39 @@
</div> </div>
</div> </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" name="add_another"
value="true" value="true"
class="btn btn-phoenix-success me-1"> class="btn btn-lg btn-phoenix-success md-me-2">
{% trans "Save and Add Another" %} {% trans "Save and Add Another" %}
</button> </button>
<button type="submit" <button type="submit"
name="go_to_stats" name="go_to_stats"
value="true" value="true"
class="btn btn-phoenix-primary"> class="btn btn-lg btn-phoenix-primary">
{% trans "Save and Go to Inventory" %} {% trans "Save and Go to Inventory" %}
</button> </button>
</div>
</div>
</div> </div>
</div> </div>
</div> </div>
</div> </div>
</form>
<!-- Modal sections remain largely unchanged -->
</form>
</div>
</div>
<!-- Modal sections remain largely unchanged -->
<!--Specification Modal--> <!--Specification Modal-->
<div class="modal fade" <div class="modal fade"
id="specificationsModal" id="specificationsModal"
@ -312,7 +334,11 @@
</div> </div>
</div> </div>
</div> </div>
</div> </div>
</div>
<!---->
<script> <script>
function getCookie(name) { function getCookie(name) {
let cookieValue = null; let cookieValue = null;

View File

@ -28,7 +28,7 @@
<div class="container-fluid" id="projectSummary"> <div class="container-fluid" id="projectSummary">
<div class="row g-3 justify-content-between align-items-end mb-4"> <div class="row g-3 justify-content-between align-items-end mb-4">
<div class="col-12 col-sm-auto"> <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> </div>
<div class="row g-3 justify-content-between align-items-end mb-2"> <div class="row g-3 justify-content-between align-items-end mb-2">

View File

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

View File

@ -4,7 +4,7 @@
{% load i18n %} {% load i18n %}
{% block title %}{{ _("Add New Expense") }}{% endblock title %} {% block title %}{{ _("Add New Expense") }}{% endblock title %}
{% block content %} {% block content %}
<div class="row"> {% comment %} <div class="row">
<div class="row justify-content-center"> <div class="row justify-content-center">
<div class="col-md-8"> <div class="col-md-8">
<div class="card"> <div class="card">
@ -13,8 +13,6 @@
<form method="post" action=""> <form method="post" action="">
{% csrf_token %} {% csrf_token %}
{{ form|crispy }} {{ 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"> <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> <button class="btn btn-sm btn-phoenix-success me-2" type="submit"><i class="fa-solid fa-floppy-disk me-1"></i>{{ _("Save") }}</button>
@ -26,6 +24,43 @@
</div> </div>
</div> </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 %} {% endblock content %}

View File

@ -4,7 +4,7 @@
{% load i18n %} {% load i18n %}
{% block title %}{{ _("Update Expense") }}{% endblock title %} {% block title %}{{ _("Update Expense") }}{% endblock title %}
{% block content %} {% block content %}
<div class="row"> {% comment %} <div class="row">
<div class="row justify-content-center"> <div class="row justify-content-center">
<div class="col-md-8"> <div class="col-md-8">
<div class="card"> <div class="card">
@ -19,6 +19,40 @@
</div> </div>
</div> </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 %} {% endblock content %}

View File

@ -13,7 +13,7 @@
{% endblock %} {% endblock %}
{% block content %} {% block content %}
<div class="container"> {% comment %} <div class="container">
<div class="row justify-content-center"> <div class="row justify-content-center">
<div class="col-md-8"> <div class="col-md-8">
<div class="card bg-body"> <div class="card bg-body">
@ -38,6 +38,45 @@
</div> </div>
</div> </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 %} {% endblock content %}

View File

@ -12,7 +12,8 @@
{% block content %} {% block content %}
<div class="container"> {% comment %} <div class="container">
<div class="row justify-content-center"> <div class="row justify-content-center">
<div class="col-8"> <div class="col-8">
<div class="card shadow rounded bg-body"> <div class="card shadow rounded bg-body">
@ -46,5 +47,48 @@
</div> </div>
</div> </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 %} {% endblock %}

View File

@ -12,6 +12,7 @@
{% endblock %} {% endblock %}
{% block content %} {% block content %}
{% comment %}
<div class="row my-5"> <div class="row my-5">
<!-- Display Form Errors --> <!-- Display Form Errors -->
<div class="card shadow rounded bg-body"> <div class="card shadow rounded bg-body">
@ -46,5 +47,46 @@
</form> </form>
</div> </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 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 %} {% endblock %}

View File

@ -5,7 +5,7 @@
{% block title %}{{ _("Create Journal Entry") }}{% endblock title %} {% block title %}{{ _("Create Journal Entry") }}{% endblock title %}
{% block content %} {% block content %}
<div class="row mt-4"> {% comment %} <div class="row mt-4">
<h3 class="text-center">{% trans "Create Journal Entry" %}</h3> <h3 class="text-center">{% trans "Create Journal Entry" %}</h3>
<form id="mainForm" method="post" class="needs-validation"> <form id="mainForm" method="post" class="needs-validation">
{% csrf_token %} {% 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> <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> </div>
</form> </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 %} {% endblock content %}

View File

@ -5,17 +5,13 @@
{% block title %}{{ _("Create Ledger") }}{% endblock title %} {% block title %}{{ _("Create Ledger") }}{% endblock title %}
{% block content %} {% block content %}
<div class="row mt-4"> {% comment %} <div class="row mt-4">
<h3 class="text-center">{% trans "Create Ledger" %}</h3> <h3 class="text-center">{% trans "Create Ledger" %}</h3>
<form id="mainForm" method="post" class="needs-validation"> <form id="mainForm" method="post" class="needs-validation">
{% csrf_token %} {% csrf_token %}
<div class="row g-3"> <div class="row g-3">
{{ form|crispy }} {{ form|crispy }}
</div> </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"> <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> <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> </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 %} {% endblock content %}

View File

@ -87,7 +87,7 @@
<span class="fas fa-ellipsis-h fs-10"></span> <span class="fas fa-ellipsis-h fs-10"></span>
</button> </button>
<div class="dropdown-menu dropdown-menu-end py-2"> <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 %} {% if ledger.can_lock %}
<a href="{% url 'ledger-action-lock' dealer_slug=request.dealer.slug entity_slug=entity_slug ledger_pk=ledger.uuid %}" <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> 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"> <li class="nav-item dropdown">
<!-- Notification counter --> <!-- Notification counter -->
<div class="notification-count"> <div class="notification-count">
@ -236,6 +252,10 @@
notificationCard.classList.remove('unread'); notificationCard.classList.remove('unread');
notificationCard.classList.add('read'); notificationCard.classList.add('read');
updateCounter('decrement'); 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'%} {% trans 'Add New Organization'%}
{% endif %} {% endif %}
{% endblock %} {% endblock %}
{% block content %} {% block content %}
<div class="container-fluid mt-4"> <div class="row justify-content-center mt-5 mb-3">
<!--Heading-->
<h3 class="mb-3">
{% if object %}
{% trans 'Update Organization'%}
{% else %}
{% trans 'Add New Organization'%}
{% endif %}
</h3>
<!--form body-->
<div class="row mb-3"> <div class="col-lg-8 col-md-10">
<div class="col-sm-6 col-md-8"> <div class="card shadow-sm border-0 rounded-3">
<form class="form" method="post" enctype="multipart/form-data"> <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 %} {% csrf_token %}
{{ redirect_field }} {{ redirect_field }}
{{ form|crispy }} {{ 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> </form>
</div> </div>
</div> </div>
</div>
{% endblock %} </div>
</div>
{% endblock%}

View File

@ -11,7 +11,7 @@
{% block content %} {% block content %}
<section class="pt-5 pb-9 "> <section class="pt-5 pb-9 ">
<div class="row overflow-x-auto whitespace-nowrap -mx-2 sm:mx-0"> <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="row g-3 justify-content-between mb-4">
<div class="col-auto"> <div class="col-auto">

View File

@ -1,7 +1,7 @@
<div class="search-box me-2"> <div class="search-box me-2">
<form class="position-relative show" id="search-form"> <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" <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> <span class="fa fa-magnifying-glass search-box-icon"></span>
{% if request.GET.q %} {% if request.GET.q %}
<button type="button" class="btn-close position-absolute end-0 top-50 translate-middle cursor-pointer shadow-none" <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 { .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); box-shadow: 0 0 0 0.25rem rgba(13, 110, 253, 0.4);
} }
.btn-check:checked + .btn .card { .btn-check:checked + .btn .card {
/* fallback if JS fails */ /* 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); box-shadow: 0 0 0 0.25rem rgba(13, 110, 253, 0.25);
} }
@ -50,29 +50,37 @@
{% block content %} {% block content %}
<div class="container py-5"> <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"> <form method="POST" action="{% url 'submit_plan' request.dealer.slug %}" id="wizardForm">
{% csrf_token %} {% csrf_token %}
<!-- Step 1: Plan Selection -->
<div class="step" id="step1"> <!--step1: Choose Plans-->
<h4 class="mb-4">1. {{ _("Select a Plan")}}</h4> <div class="step row justify-content-center mt-5 mb-3" id="step1">
<div class="row g-4">
{% for pp in plan_list %} <div class="col-lg-8 col-md-10">
<div class="col-md-6 col-lg-3"> <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 }}" <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 %}> 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 }}"> <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 h-100 border border-2 rounded-4">
<div class="card-body p-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> <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"> <ul class="fa-ul ps-3">
{% if pp.plan.description %} {% if pp.plan.description %}
{% for line in pp.plan.description|splitlines %} {% for line in pp.plan.description|splitlines %}
<li class="mb-2"> <li class="mb-2">
<span class="fa-li"><i class="fas fa-check text-primary"></i></span> <span class="fa-li"><i class="fas fa-check text-primary"></i></span>
{{ line }} {{ line|capfirst}}
</li> </li>
{% endfor %} {% endfor %}
{% endif %} {% endif %}
@ -82,124 +90,156 @@
</label> </label>
</div> </div>
{% endfor %} {% endfor %}
</div>
</div> </div>
</div>
<!-- Step 2: User Info --> </div>
<div class="step d-none" id="step2">
<h4 class="mb-4">2. {{ _("Enter Your Information")}}</h4> </div>
<div class="row g-3">
<div class="col-md-6"> <!--step2: Information-->
<label class="form-label" for="first_name">{{ _("First Name")}}</label>
<div class="input-group"> <div class="step d-none row justify-content-center mt-5 mb-3" id="step2">
<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 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> <div class="card-body bg-light-subtle">
<div class="col-md-6">
<label class="form-label" for="last_name">{{ _("Last Name")}}</label> <label class="form-label" for="first_name">{{ _("First Name")}}</label>
<div class="input-group"> <div class="input-group">
<span class="input-group-text"><i class="fas fa-user"></i></span> <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 }}"> <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>
<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>
</div>
<!-- Step 3: Payment --> </div>
<div class="step d-none" id="step3">
<h4 class="mb-4">3. {{ _("Payment Information")}}</h4> </div>
<div class="row g-3"> <!--step3: Payment-->
<div class="col-md-6"> <div class="step d-none row justify-content-center mt-5 mb-3" id="step3">
<label class="form-label" for="card_name">{{ _("Cardholder Name")}}</label>
<div class="input-group"> <div class="col-lg-8 col-md-10">
<span class="input-group-text"><i class="fas fa-user"></i></span> <div class="card shadow-sm border-0 rounded-3">
<input type="text" name="card_name" id="card_name" class="form-control form-control-sm" placeholder="{{ _("Cardholder Name")}}" required> <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> <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>
<label class="form-label" for="card_number">{{ _("Card Number")}}</label> <div class="input-group">
<div class="input-group"> <span class="input-group-text"><i class="fas fa-credit-card"></i></span>
<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")}}"
<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}$"
maxlength="19" pattern="^\d{4}\s\d{4}\s\d{4}\s\d{4}$" inputmode="numeric" required title="Enter a 16-digit card number">
inputmode="numeric" required title="Enter a 16-digit card number"> </div>
</div>
</div>
<div class="col-md-4">
<label class="form-label" for="card_expiry">{{ _("Expiry Date")}} (MM/YY)</label> <label class="form-label" for="card_expiry">{{ _("Expiry Date")}} (MM/YY)</label>
<div class="input-group"> <div class="input-group">
<span class="input-group-text"><i class="far fa-calendar-alt"></i></span> <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")}}" <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}$" maxlength="5" pattern="^(0[1-9]|1[0-2])\/\d{2}$"
inputmode="numeric" required title="Enter expiry in MM/YY format"> inputmode="numeric" required title="Enter expiry in MM/YY format">
</div> </div>
</div>
<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 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">
</div> </div>
</div>
</div> </div>
</div>
<!-- Step 4: Confirmation --> </div>
<div class="step d-none" id="step4">
<h4 class="mb-4">4. {{ _("Confirm Your Information")}}</h4> </div>
<div class="summary-box"> <!--Step4 confirmation-->
<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="step d-none row justify-content-center mt-5 mb-3" id="step4">
<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 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> <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"><i class="fas fa-credit-card me-2"></i>{{ _("Payment") }}</h5> <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-user"></i><strong>{{ _("Cardholder") }}:</strong> <span id="summary_card_name"></span></div> <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-credit-card"></i><strong>{{ _("Card Number")}}:</strong> <span id="summary_card_number"></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="far fa-calendar-alt"></i><strong>{{ _("Expiry") }}:</strong> <span id="summary_card_expiry"></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 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>
</div>
<!---->
<!-- Navigation --> <!-- Navigation -->
<div class="d-flex justify-content-between mt-4"> <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-lg btn-phoenix-secondary" id="prevBtn" disabled>{{ _("Previous") }}</button>
<button type="button" class="btn btn-phoenix-primary" id="nextBtn">{{ _("Next") }}</button> <button type="button" class="btn btn-lg btn-phoenix-primary" id="nextBtn">{{ _("Next") }}</button>
<button type="submit" class="btn btn-phoenix-success d-none" id="submitBtn">{{ _("Confirm") }}</button> <button type="submit" class="btn btn-lg btn-phoenix-primary d-none" id="submitBtn">{{ _("Confirm") }}</button>
</div> </div>
</form> </form>
</div> </div>

View File

@ -32,15 +32,26 @@
{% if not create_po %} {% if not create_po %}
{% if style == 'po-detail' %} {% if style == 'po-detail' %}
<div class="card shadow-sm border-0 mb-2"> <div class="card shadow-sm border-0 mb-2">
<div class="card-header bg-light"> <div class="card-header bg-light ">
<div class="d-flex align-items-center"> <div class="d-flex align-items-center mb-2 ">
<span class="me-3 text-primary"> <span class="me-3 text-primary">
{% icon 'uil:bill' 36 %} {% icon 'uil:bill' 36 %}
</span> </span>
<h2 class="h3 mb-0"> <h2 class="h3 mb-0 text-primary me-4">
{{ po_model.po_number }} {{ po_model.po_number }}
</h2> </h2>
</div> </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>
<div class="card-body"> <div class="card-body">
@ -52,6 +63,7 @@
{{ po_model.get_po_status_display }} {{ po_model.get_po_status_display }}
</span> </span>
</h3> </h3>
</div> </div>
@ -99,14 +111,7 @@
</div> </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> </div>

View File

@ -10,44 +10,42 @@
{% endif %} {% endif %}
{% endblock %} {% endblock %}
{% block content %} {% block content %}
<div class="row mt-4">
<div class="row">
<div class="col-xl-9">
<div class="d-sm-flex justify-content-between">
<h3 class="mb-3"> <!---->
{% if vendor.created %} <div class="row justify-content-center mt-5 mb-3">
<!--<i class="bi bi-pencil-square"></i>-->
{{ _("Edit Purchase Order") }} <div class="col-lg-8 col-md-10 ">
{% else %} <div class="card shadow-sm border-0 rounded-3">
<!--<i class="bi bi-person-plus"></i> --> <div class="card-header bg-gray-200 py-3 border-0 rounded-top-3">
{{ _("Add New Purchase Order") }} <h3 class="mb-0 fs-4 text-center text-white">
{% endif %} {{ _("Add New Purchase Order") }}
</h3> </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>
<div class="row"> </div>
<div class="col-xl-9">
<form class="row g-3 mb-9" method="post" class="form" enctype="multipart/form-data" novalidate > </div>
{% 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>
</form> <!---->
</div>
</div>
</div>
{% endblock %} {% endblock %}

View File

@ -13,21 +13,26 @@
<div class="alert alert-success">{{ message }}</div> <div class="alert alert-success">{{ message }}</div>
{% endfor %} {% endfor %}
{% endif %} {% endif %}
<!-- Add New PO Button -->
<div class="d-flex justify-content-between mb-2"> <h2 class="">
<h3 class=""> {{ _("Purchase Orders") |capfirst }} <li class="fas fa-file-invoice text-primary ms-2"></li>
{{ _("Purchase Orders") |capfirst }}
</h2> </h2>
{% if perms.django_ledger.add_purchaseordermodel%}
<div> <div class="row g-3 justify-content-between mb-4">
{% if perms.django_ledger.add_purchaseordermodel %} <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 %}" <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> class="btn btn-md btn-phoenix-primary"><i class="fa fa-plus me-2"></i>{{ _("Create New PO") }}</a>
{% endif %} {% endif %}
</div> </div>
{% 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"> <div class="table-responsive px-1 scrollbar mt-3">
<table class= "table align-items-center table-flush table-hover"> <table class= "table align-items-center table-flush table-hover">
@ -74,7 +79,7 @@
{% endif %} {% endif %}
{% if po.po_status == 'fulfilled' %} {% if po.po_status == 'fulfilled' %}
{% if perms.inventory.add_car %} {% 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 %} {% endif %}
{% else %} {% 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> <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 %} {% csrf_token %}
{{ form|crispy }} {{ form|crispy }}
<button type="submit" <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> </button>
<a href="{% url 'purchase_order_detail' request.dealer.slug request.dealer.entity.slug po_model.uuid %}" <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> class="btn btn-phoenix-secondary 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>
</div> </div>
</div> </div>
</form> </form>

View File

@ -1,5 +1,6 @@
{% extends "base.html" %} {% extends "base.html" %}
{% load i18n custom_filters%} {% load i18n custom_filters%}
{% load crispy_forms_filters %}
{% block title %}{{ _("View Quotation") }}{% endblock title %} {% block title %}{{ _("View Quotation") }}{% endblock title %}
@ -83,6 +84,11 @@
{% if perms.django_ledger.can_approve_estimatemodel %} {% 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> <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 %} {% endif %}
{% 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' %} {% elif estimate.status == 'approved' %}
{% if perms.django_ledger.change_estimatemodel %} {% 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> <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>
@ -186,15 +192,22 @@
</tr> </tr>
{% endfor %} {% endfor %}
<tr class="bg-body-secondary total-sum"> <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"> <td class="align-middle text-start fw-semibold">
<span id="grand-total">+ {{data.total_vat_amount|floatformat}}<span class="icon-saudi_riyal"></span></span> <span id="grand-total">+ {{data.total_vat_amount|floatformat}}<span class="icon-saudi_riyal"></span></span>
</td> </td>
</tr> </tr>
<tr class="bg-body-secondary total-sum"> <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 ps-4 fw-semibold text-body-highlight" colspan="7">{% trans "Discount Amount" %}</td>
<td class="align-middle text-start text-danger fw-semibold "> <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> <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> </td>
</tr> </tr>
<tr class="bg-body-secondary total-sum"> <tr class="bg-body-secondary total-sum">
@ -203,6 +216,7 @@
{% for service in data.additionals %} {% for service in data.additionals %}
<small><span class="fw-semibold">+ {{service.name}} - {{service.price_|floatformat}}<span class="icon-saudi_riyal"></span></span></small><br> <small><span class="fw-semibold">+ {{service.name}} - {{service.price_|floatformat}}<span class="icon-saudi_riyal"></span></span></small><br>
{% endfor %} {% 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> </td>
</tr> </tr>
<tr class="bg-body-secondary total-sum"> <tr class="bg-body-secondary total-sum">
@ -221,6 +235,27 @@
</section> </section>
<!-- <section> close ============================--> <!-- <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 %} {% endblock %}
{% block customJS %} {% block customJS %}

View File

@ -120,8 +120,89 @@
} }
</style> </style>
{% endblock customCSS %} {% 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"> <div class="row mt-4">
{% if not items %} {% if not items %}
<div class="alert alert-outline-warning d-flex align-items-center" role="alert"> <div class="alert alert-outline-warning d-flex align-items-center" role="alert">
@ -184,7 +265,7 @@
</div> </div>
</form> </form>
</div> </div>
{% endblock content %} {% endblock content %} {% endcomment %}
{% block customJS %} {% block customJS %}
<script> <script>

View File

@ -4,7 +4,7 @@
{% load i18n %} {% load i18n %}
{% block title %}{{ _("Invoice") }}{% endblock title %} {% block title %}{{ _("Invoice") }}{% endblock title %}
{% block content %} {% block content %}
<div class="row paid"> {% comment %} <div class="row paid">
<div class="row justify-content-center"> <div class="row justify-content-center">
<div class="col-md-8"> <div class="col-md-8">
<div class="card"> <div class="card">
@ -19,6 +19,42 @@
</div> </div>
</div> </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 %} {% endblock content %}

View File

@ -82,7 +82,7 @@
</div> </div>
</div> </div>
<div class="d-flex align-items-center gap-2"> <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' %} {% 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> <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 %} {% endif %}

View File

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

View File

@ -13,7 +13,7 @@
{% block content %} {% block content %}
{% comment %}
<div class="row"> <div class="row">
<div class="row"> <div class="row">
<div class="col-xl-9"> <div class="col-xl-9">
@ -54,4 +54,43 @@
</div> </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 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 %} {% endblock %}

View File

@ -28,21 +28,21 @@
<link rel="preconnect" href="https://fonts.googleapis.com"> <link rel="preconnect" href="https://fonts.googleapis.com">
<link rel="preconnect" href="https://fonts.gstatic.com" crossorigin=""> <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="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"> {% comment %} <link href="{% static 'vendors/simplebar/simplebar.min.css' %}" rel="stylesheet"> {% endcomment %}
<link href="{% static 'css/sweetalert2.min.css' %}" rel="stylesheet"> {% comment %} <link href="{% static 'css/sweetalert2.min.css' %}" rel="stylesheet"> {% endcomment %}
<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 == 'en' %} {% if LANGUAGE_CODE == 'ar' %}
<link href="{% static 'css/theme.min.css' %}" type="text/css" rel="stylesheet" id="style-default"> <link href="{% static 'css/theme-rtl.min.css' %}" type="text/css" rel="stylesheet" id="style-rtl">
<link href="{% static 'css/user.min.css' %}" type="text/css" rel="stylesheet" id="user-style-default"> <link href="{% static 'css/user-rtl.min.css' %}" type="text/css" rel="stylesheet" id="user-style-rtl">
{% else %} {% else %}
<link href="{% static 'css/theme-rtl.min.css' %}" type="text/css" rel="stylesheet" id="style-rtl"> <link href="{% static 'css/theme.min.css' %}" type="text/css" rel="stylesheet" id="style-default">
<link href="{% static 'css/user-rtl.min.css' %}" type="text/css" rel="stylesheet" id="user-style-rtl"> <link href="{% static 'css/user.min.css' %}" type="text/css" rel="stylesheet" id="user-style-default">
{% endif %} {% endif %}
<script src="{% static 'vendors/simplebar/simplebar.min.js' %}"></script> <script src="{% static 'vendors/simplebar/simplebar.min.js' %}"></script>
<script src="{% static 'js/config.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"> <link href="{% static 'css/custom.css' %}" rel="stylesheet">
</head> </head>
@ -52,9 +52,27 @@
{% block content %} {% block content %}
{% endblock 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/popper/popper.min.js' %}"></script>
<script src="{% static 'vendors/bootstrap/bootstrap.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/anchorjs/anchor.min.js' %}"></script>
<script src="{% static 'vendors/is/is.min.js' %}"></script> <script src="{% static 'vendors/is/is.min.js' %}"></script>
<script src="{% static 'vendors/fontawesome/all.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="{% static 'vendors/mapbox-gl/mapbox-gl.js' %}"></script>
<script src="https://unpkg.com/@turf/turf@6/turf.min.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/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 %} {% block customJS %}
{% endblock customJS %} {% endblock customJS %}