Compare commits
12 Commits
19dcfdab26
...
71c0f4e405
| Author | SHA1 | Date | |
|---|---|---|---|
| 71c0f4e405 | |||
| 43e8888b8c | |||
| 993ba46dbc | |||
| c03fe9d85a | |||
| c4f5f7e8cb | |||
| a5785b8205 | |||
| aa58919c56 | |||
| d5d895789a | |||
| f5feee2372 | |||
| d53e10e45f | |||
| cf49777f26 | |||
| 8b2f76208a |
1
.gitignore
vendored
@ -11,6 +11,7 @@ dbtest.sqlite3
|
|||||||
db.sqlite3
|
db.sqlite3
|
||||||
db.sqlite3.backup
|
db.sqlite3.backup
|
||||||
db.sqlite*
|
db.sqlite*
|
||||||
|
new.sqlite3
|
||||||
*.sqlite3
|
*.sqlite3
|
||||||
media
|
media
|
||||||
car*.json
|
car*.json
|
||||||
|
|||||||
@ -10,16 +10,31 @@ https://docs.djangoproject.com/en/5.0/howto/deployment/asgi/
|
|||||||
# asgi.py
|
# asgi.py
|
||||||
|
|
||||||
import os
|
import os
|
||||||
from django.core.asgi import get_asgi_application
|
os.environ.setdefault("DJANGO_SETTINGS_MODULE", "car_inventory.settings")
|
||||||
|
|
||||||
|
import django
|
||||||
|
django.setup()
|
||||||
|
|
||||||
|
|
||||||
|
from django.urls import path
|
||||||
from channels.routing import ProtocolTypeRouter, URLRouter
|
from channels.routing import ProtocolTypeRouter, URLRouter
|
||||||
from channels.auth import AuthMiddlewareStack
|
from channels.auth import AuthMiddlewareStack
|
||||||
from api import routing
|
from api import routing
|
||||||
|
from inventory.notifications.sse import NotificationSSEApp
|
||||||
|
from django.urls import re_path
|
||||||
|
from django.core.asgi import get_asgi_application
|
||||||
|
|
||||||
os.environ.setdefault("DJANGO_SETTINGS_MODULE", "car_inventory.settings")
|
# application = ProtocolTypeRouter(
|
||||||
|
# {
|
||||||
application = ProtocolTypeRouter(
|
# "http": get_asgi_application(),
|
||||||
{
|
# # "websocket": AuthMiddlewareStack(URLRouter(routing.websocket_urlpatterns)),
|
||||||
"http": get_asgi_application(),
|
# }
|
||||||
"websocket": AuthMiddlewareStack(URLRouter(routing.websocket_urlpatterns)),
|
# )
|
||||||
}
|
application = ProtocolTypeRouter({
|
||||||
)
|
"http": AuthMiddlewareStack(
|
||||||
|
URLRouter([
|
||||||
|
path("sse/notifications/", NotificationSSEApp()),
|
||||||
|
re_path(r"", get_asgi_application()), # All other routes go to Django
|
||||||
|
])
|
||||||
|
),
|
||||||
|
})
|
||||||
@ -4,9 +4,9 @@ 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
|
# from debug_toolbar.toolbar import debug_toolbar_urls
|
||||||
|
|
||||||
|
|
||||||
|
from inventory.notifications.sse import NotificationSSEApp
|
||||||
# import debug_toolbar
|
# import debug_toolbar
|
||||||
from schema_graph.views import Schema
|
from schema_graph.views import Schema
|
||||||
# from two_factor.urls import urlpatterns as tf_urls
|
# from two_factor.urls import urlpatterns as tf_urls
|
||||||
@ -17,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()
|
] # + 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"),
|
||||||
@ -30,6 +30,7 @@ urlpatterns += i18n_patterns(
|
|||||||
path("plans/", include("plans.urls")),
|
path("plans/", include("plans.urls")),
|
||||||
path("schema/", Schema.as_view()),
|
path("schema/", Schema.as_view()),
|
||||||
path("tours/", include("tours.urls")),
|
path("tours/", include("tours.urls")),
|
||||||
|
|
||||||
# path('', include(tf_urls)),
|
# path('', include(tf_urls)),
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|||||||
@ -1,4 +1,4 @@
|
|||||||
# Generated by Django 5.2.4 on 2025-07-14 15:04
|
# Generated by Django 5.2.4 on 2025-07-22 08:37
|
||||||
|
|
||||||
import django.db.models.deletion
|
import django.db.models.deletion
|
||||||
import django.utils.timezone
|
import django.utils.timezone
|
||||||
|
|||||||
@ -1,4 +1,4 @@
|
|||||||
# Generated by Django 5.2.4 on 2025-07-14 15:04
|
# Generated by Django 5.2.4 on 2025-07-22 08:37
|
||||||
|
|
||||||
import django.db.models.deletion
|
import django.db.models.deletion
|
||||||
from django.db import migrations, models
|
from django.db import migrations, models
|
||||||
|
|||||||
@ -1,5 +1,6 @@
|
|||||||
from django.conf import settings
|
from django.conf import settings
|
||||||
|
|
||||||
|
|
||||||
def currency_context(request):
|
def currency_context(request):
|
||||||
"""
|
"""
|
||||||
Provides a context dictionary containing the currency setting. This is typically
|
Provides a context dictionary containing the currency setting. This is typically
|
||||||
@ -42,7 +43,6 @@ def breadcrumbs(request):
|
|||||||
return {"breadcrumbs": breadcrumbs}
|
return {"breadcrumbs": breadcrumbs}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
def user_types(request):
|
def user_types(request):
|
||||||
"""
|
"""
|
||||||
Sets various flags indicating the user's role types.
|
Sets various flags indicating the user's role types.
|
||||||
|
|||||||
@ -142,9 +142,10 @@ class StaffForm(forms.ModelForm):
|
|||||||
queryset=CustomGroup.objects.all(),
|
queryset=CustomGroup.objects.all(),
|
||||||
required=True,
|
required=True,
|
||||||
)
|
)
|
||||||
|
|
||||||
class Meta:
|
class Meta:
|
||||||
model = Staff
|
model = Staff
|
||||||
fields = ["name", "arabic_name", "phone_number", "address","logo","group"]
|
fields = ["name", "arabic_name", "phone_number", "address", "logo", "group"]
|
||||||
|
|
||||||
|
|
||||||
# Dealer Form
|
# Dealer Form
|
||||||
@ -429,25 +430,22 @@ class CarFinanceForm(forms.ModelForm):
|
|||||||
additional services associated with a car finance application.
|
additional services associated with a car finance application.
|
||||||
"""
|
"""
|
||||||
|
|
||||||
additional_finances = forms.ModelMultipleChoiceField(
|
# additional_finances = forms.ModelMultipleChoiceField(
|
||||||
queryset=AdditionalServices.objects.all(),
|
# queryset=AdditionalServices.objects.all(),
|
||||||
widget=forms.CheckboxSelectMultiple(attrs={"class": "form-check-input"}),
|
# widget=forms.CheckboxSelectMultiple(attrs={"class": "form-check-input"}),
|
||||||
required=False,
|
# required=False,
|
||||||
)
|
# )
|
||||||
|
|
||||||
class Meta:
|
class Meta:
|
||||||
model = CarFinance
|
model = CarFinance
|
||||||
exclude = [
|
fields = ["cost_price","marked_price"]
|
||||||
"car",
|
|
||||||
"profit_margin",
|
|
||||||
"vat_amount",
|
|
||||||
"total",
|
|
||||||
"additional_services",
|
|
||||||
]
|
|
||||||
|
|
||||||
def save(self, commit=True):
|
def save(self, commit=True):
|
||||||
instance = super().save()
|
instance = super().save()
|
||||||
instance.additional_services.set(self.cleaned_data["additional_finances"])
|
try:
|
||||||
|
instance.additional_services.set(self.cleaned_data["additional_finances"])
|
||||||
|
except KeyError:
|
||||||
|
pass
|
||||||
instance.save()
|
instance.save()
|
||||||
return instance
|
return instance
|
||||||
|
|
||||||
@ -1361,27 +1359,27 @@ class SaleOrderForm(forms.ModelForm):
|
|||||||
class Meta:
|
class Meta:
|
||||||
model = SaleOrder
|
model = SaleOrder
|
||||||
fields = [
|
fields = [
|
||||||
"customer",
|
# "customer",
|
||||||
"expected_delivery_date",
|
"expected_delivery_date",
|
||||||
"estimate",
|
# "estimate",
|
||||||
"opportunity",
|
# "opportunity",
|
||||||
"comments",
|
"comments",
|
||||||
"order_date",
|
# "order_date",
|
||||||
"status",
|
# "status",
|
||||||
]
|
]
|
||||||
widgets = {
|
widgets = {
|
||||||
"expected_delivery_date": forms.DateInput(
|
"expected_delivery_date": forms.DateInput(
|
||||||
attrs={"type": "date", "label": _("Expected Delivery Date")}
|
attrs={"type": "date", "label": _("Expected Delivery Date")}
|
||||||
),
|
),
|
||||||
"order_date": forms.DateInput(
|
# "order_date": forms.DateInput(
|
||||||
attrs={"type": "date", "label": _("Order Date")}
|
# attrs={"type": "date", "label": _("Order Date")}
|
||||||
),
|
# ),
|
||||||
"customer": forms.Select(
|
# "customer": forms.Select(
|
||||||
attrs={
|
# attrs={
|
||||||
"class": "form-control",
|
# "class": "form-control",
|
||||||
"label": _("Customer"),
|
# "label": _("Customer"),
|
||||||
}
|
# }
|
||||||
),
|
# ),
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
@ -1564,6 +1562,7 @@ class GroupForm(forms.ModelForm):
|
|||||||
# 60 * 60,
|
# 60 * 60,
|
||||||
# )
|
# )
|
||||||
|
|
||||||
|
|
||||||
# class Meta:
|
# class Meta:
|
||||||
# model = Permission
|
# model = Permission
|
||||||
# fields = ["name"]
|
# fields = ["name"]
|
||||||
@ -1571,6 +1570,7 @@ class PermissionForm(forms.ModelForm):
|
|||||||
"""
|
"""
|
||||||
Form for managing permissions with grouped checkboxes by app and model.
|
Form for managing permissions with grouped checkboxes by app and model.
|
||||||
"""
|
"""
|
||||||
|
|
||||||
def __init__(self, *args, **kwargs):
|
def __init__(self, *args, **kwargs):
|
||||||
super().__init__(*args, **kwargs)
|
super().__init__(*args, **kwargs)
|
||||||
EXCLUDED_MODELS = [
|
EXCLUDED_MODELS = [
|
||||||
@ -1591,31 +1591,27 @@ class PermissionForm(forms.ModelForm):
|
|||||||
"inventory.schedule",
|
"inventory.schedule",
|
||||||
"inventory.activity",
|
"inventory.activity",
|
||||||
"inventory.opportunity",
|
"inventory.opportunity",
|
||||||
"inventory.carreservation"
|
"inventory.carreservationinventory.customer",
|
||||||
"inventory.customer",
|
|
||||||
"inventory.organization",
|
"inventory.organization",
|
||||||
# "inventory.salequotation",
|
# "inventory.salequotation",
|
||||||
# "inventory.salequotationcar"
|
# "inventory.salequotationcar"
|
||||||
|
"django_ledger.purchaseordermodeldjango_ledger.bankaccountmodel",
|
||||||
"django_ledger.purchaseordermodel"
|
|
||||||
"django_ledger.bankaccountmodel",
|
|
||||||
"django_ledger.estimatemodel",
|
"django_ledger.estimatemodel",
|
||||||
"django_ledger.accountmodel",
|
"django_ledger.accountmodel",
|
||||||
"django_ledger.chartofaccountmodel",
|
"django_ledger.chartofaccountmodel",
|
||||||
"django_ledger.billmodel"
|
"django_ledger.billmodeldjango_ledger.itemmodel",
|
||||||
"django_ledger.itemmodel",
|
|
||||||
"django_ledger.invoicemodel",
|
"django_ledger.invoicemodel",
|
||||||
"django_ledger.vendormodel",
|
"django_ledger.vendormodel",
|
||||||
"django_ledger.journalentrymodel"
|
"django_ledger.journalentrymodel"
|
||||||
"django_ledger.purchaseordermodel",#TODO add purchase order
|
"django_ledger.purchaseordermodel", # TODO add purchase order
|
||||||
]
|
]
|
||||||
|
|
||||||
permissions = cache.get(
|
permissions = cache.get(
|
||||||
"permissions_queryset",
|
"permissions_queryset",
|
||||||
Permission.objects.filter(
|
Permission.objects.filter(
|
||||||
content_type__app_label__in=[m.split('.')[0] for m in EXCLUDED_MODELS],
|
content_type__app_label__in=[m.split(".")[0] for m in EXCLUDED_MODELS],
|
||||||
content_type__model__in=[m.split('.')[1] for m in EXCLUDED_MODELS]
|
content_type__model__in=[m.split(".")[1] for m in EXCLUDED_MODELS],
|
||||||
).select_related('content_type')
|
).select_related("content_type"),
|
||||||
)
|
)
|
||||||
|
|
||||||
# Group permissions by app_label and model
|
# Group permissions by app_label and model
|
||||||
@ -1630,11 +1626,11 @@ class PermissionForm(forms.ModelForm):
|
|||||||
self.grouped_permissions[app_label][model].append(perm)
|
self.grouped_permissions[app_label][model].append(perm)
|
||||||
|
|
||||||
# Create a multiple choice field (hidden, will use custom rendering)
|
# Create a multiple choice field (hidden, will use custom rendering)
|
||||||
self.fields['permissions'] = forms.ModelMultipleChoiceField(
|
self.fields["permissions"] = forms.ModelMultipleChoiceField(
|
||||||
queryset=permissions,
|
queryset=permissions,
|
||||||
widget=forms.MultipleHiddenInput(),
|
widget=forms.MultipleHiddenInput(),
|
||||||
required=False,
|
required=False,
|
||||||
initial=self.instance.permissions.all() if self.instance.pk else []
|
initial=self.instance.permissions.all() if self.instance.pk else [],
|
||||||
)
|
)
|
||||||
|
|
||||||
class Meta:
|
class Meta:
|
||||||
@ -2036,17 +2032,17 @@ class CSVUploadForm(forms.Form):
|
|||||||
)
|
)
|
||||||
year = forms.IntegerField(
|
year = forms.IntegerField(
|
||||||
label=_("Year"),
|
label=_("Year"),
|
||||||
widget=forms.NumberInput(attrs=
|
widget=forms.NumberInput(
|
||||||
{
|
attrs={
|
||||||
"class": "form-control",
|
"class": "form-control",
|
||||||
"hx-get": "",
|
"hx-get": "",
|
||||||
"hx-target": "#serie",
|
"hx-target": "#serie",
|
||||||
"hx-select": "#serie",
|
"hx-select": "#serie",
|
||||||
"hx-include": "#model",
|
"hx-include": "#model",
|
||||||
"hx-trigger": "input delay:500ms",
|
"hx-trigger": "input delay:500ms",
|
||||||
"hx-swap": "outerHTML",
|
"hx-swap": "outerHTML",
|
||||||
}
|
}
|
||||||
),
|
),
|
||||||
required=True,
|
required=True,
|
||||||
)
|
)
|
||||||
exterior = forms.ModelChoiceField(
|
exterior = forms.ModelChoiceField(
|
||||||
@ -2101,7 +2097,8 @@ class AdditionalFinancesForm(forms.Form):
|
|||||||
required=False,
|
required=False,
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
class VatRateForm(forms.ModelForm):
|
class VatRateForm(forms.ModelForm):
|
||||||
class Meta:
|
class Meta:
|
||||||
model = VatRate
|
model = VatRate
|
||||||
fields = ['rate']
|
fields = ["rate"]
|
||||||
|
|||||||
79
inventory/management/commands/led.py
Normal file
@ -0,0 +1,79 @@
|
|||||||
|
from decimal import Decimal
|
||||||
|
import random
|
||||||
|
from django.core.management.base import BaseCommand
|
||||||
|
from inventory.models import Car
|
||||||
|
from django_ledger.models import EntityModel,InvoiceModel,ItemModel
|
||||||
|
from inventory.utils import CarFinanceCalculator
|
||||||
|
from rich import print
|
||||||
|
class Command(BaseCommand):
|
||||||
|
help = ""
|
||||||
|
|
||||||
|
def handle(self, *args, **options):
|
||||||
|
e = EntityModel.objects.first()
|
||||||
|
customer = e.get_customers().first()
|
||||||
|
admin = e.admin
|
||||||
|
# estimate = e.get_estimates().first()
|
||||||
|
# e.create_invoice(coa_model=e.get_default_coa(), customer_model=customer, terms="net_30")
|
||||||
|
i=InvoiceModel.objects.first()
|
||||||
|
|
||||||
|
calc = CarFinanceCalculator(i)
|
||||||
|
data = calc.get_finance_data()
|
||||||
|
for car_data in data['cars']:
|
||||||
|
car = i.get_itemtxs_data()[0].filter(
|
||||||
|
item_model__car__vin=car_data['vin']
|
||||||
|
).first().item_model.car
|
||||||
|
print("car", car)
|
||||||
|
qty = Decimal(car_data['quantity'])
|
||||||
|
print("qty", qty)
|
||||||
|
|
||||||
|
# amounts from calculator
|
||||||
|
net_car_price = Decimal(car_data['total']) # after discount
|
||||||
|
net_add_price = Decimal(data['total_additionals']) # per car or split however you want
|
||||||
|
vat_amount = Decimal(data['total_vat_amount']) * qty # prorate if multi-qty
|
||||||
|
# grand_total = net_car_price + net_add_price + vat_amount
|
||||||
|
grand_total = Decimal(data['grand_total'])
|
||||||
|
cost_total = Decimal(car_data['cost_price']) * qty
|
||||||
|
|
||||||
|
print("net_car_price", net_car_price, "net_add_price", net_add_price, "vat_amount", vat_amount, "grand_total", grand_total, "cost_total", cost_total)
|
||||||
|
|
||||||
|
# acc_cars = e.get_coa_accounts().get(name="Inventory (Cars)")
|
||||||
|
# acc_sales = e.get_coa_accounts().get(name="Car Sales")
|
||||||
|
# acc_tax = e.get_coa_accounts().get(name="Tax-Payable")
|
||||||
|
# acc_service = e.get_coa_accounts().get(name="After-Sales Services")
|
||||||
|
# uom = e.get_uom_all().get(name="Unit")
|
||||||
|
# car_item = e.get_items_products().get(name='2025 Ford Mustang GT')
|
||||||
|
# car_item = e.create_item_product(
|
||||||
|
# name='2025 Ford Mustang GT',
|
||||||
|
# item_type=ItemModel.ITEM_TYPE_MATERIAL,
|
||||||
|
# uom_model=uom,
|
||||||
|
# coa_model=e.get_default_coa(),
|
||||||
|
# )
|
||||||
|
# car_item.earnings_account=acc_sales
|
||||||
|
# car_item.save()
|
||||||
|
# service_item = e.get_items_services().get(name='Extended Warranty 5yr/60k')
|
||||||
|
# service_item = e.create_item_service(
|
||||||
|
# name='Extended Warranty 5yr/60k',
|
||||||
|
# uom_model=uom,
|
||||||
|
# coa_model=e.get_default_coa(),
|
||||||
|
# )
|
||||||
|
# service_item.earnings_account=acc_service
|
||||||
|
# service_item.save()
|
||||||
|
# i.invoice_items.add(car_item)
|
||||||
|
# i.invoice_items.add(service_item)
|
||||||
|
|
||||||
|
# invoices_item_models = i.invoice_items.all()
|
||||||
|
# invoice_itemtxs = {
|
||||||
|
# im.item_number: {
|
||||||
|
# 'unit_cost': Decimal(1500),
|
||||||
|
# 'unit_revenue': Decimal(1500),
|
||||||
|
# 'quantity': 1,
|
||||||
|
# 'total_amount': Decimal(1500),
|
||||||
|
# } for im in invoices_item_models
|
||||||
|
# }
|
||||||
|
# print(invoice_itemtxs)
|
||||||
|
# invoice_itemtxs = i.migrate_itemtxs(itemtxs=invoice_itemtxs,
|
||||||
|
# commit=True,
|
||||||
|
# operation=InvoiceModel.ITEMIZE_APPEND)
|
||||||
|
# print(i.amount_due)
|
||||||
|
|
||||||
|
# i.save()
|
||||||
117
inventory/management/commands/seed.py
Normal file
@ -0,0 +1,117 @@
|
|||||||
|
# <your_app>/management/commands/seed_dealership.py
|
||||||
|
import json, random, string, decimal
|
||||||
|
from django.core.management.base import BaseCommand
|
||||||
|
from django.test import Client
|
||||||
|
from django.contrib.auth import get_user_model
|
||||||
|
from plans.models import Order, PlanPricing, AbstractOrder, UserPlan, BillingInfo,Plan
|
||||||
|
from inventory.tasks import create_user_dealer
|
||||||
|
from inventory import models # adjust import to your app
|
||||||
|
from django_q.tasks import async_task
|
||||||
|
|
||||||
|
User = get_user_model()
|
||||||
|
|
||||||
|
class Command(BaseCommand):
|
||||||
|
help = "Seed a full dealership via the real signup & downstream views"
|
||||||
|
|
||||||
|
def add_arguments(self, parser):
|
||||||
|
parser.add_argument('--count', type=int, default=1, help='Number of dealers to seed')
|
||||||
|
|
||||||
|
def handle(self, *args, **opts):
|
||||||
|
count = opts['count']
|
||||||
|
client = Client() # lives inside management command
|
||||||
|
|
||||||
|
for n in range(1, 10):
|
||||||
|
self.stdout.write(f"🚗 Seeding dealer #{n}")
|
||||||
|
self._create_dealer(client, n)
|
||||||
|
# self._create_cars(client, n)
|
||||||
|
# self._create_customers_and_sales(client, n)
|
||||||
|
self.stdout.write(self.style.SUCCESS(f"✅ Dealer #{n} ready"))
|
||||||
|
|
||||||
|
# ----------------------------------------------------------
|
||||||
|
# 1. Sign-up via the real view
|
||||||
|
# ----------------------------------------------------------
|
||||||
|
def _create_dealer(self, client, n):
|
||||||
|
payload = {
|
||||||
|
"email": f"dealer{n}@example.com",
|
||||||
|
"password": "Password123",
|
||||||
|
"confirm_password": "Password123",
|
||||||
|
"name": f"Dealer #{n}",
|
||||||
|
"arabic_name": f"تاجر {n}",
|
||||||
|
"phone_number": "+96651234567",
|
||||||
|
"crn": f"CRN{n}000",
|
||||||
|
"vrn": f"VRN{n}000",
|
||||||
|
"address": f"Street {n}, Riyadh",
|
||||||
|
}
|
||||||
|
|
||||||
|
dealer = create_user_dealer(payload['email'], payload['password'], payload['name'], payload['arabic_name'], payload['phone_number'], payload['crn'], payload['vrn'], payload['address'])
|
||||||
|
user = dealer.user
|
||||||
|
self._assign_random_plan(user)
|
||||||
|
self._services(dealer)
|
||||||
|
|
||||||
|
# resp = client.post(
|
||||||
|
# "/en/signup/", # adjust URL if necessary
|
||||||
|
# data=json.dumps(payload),
|
||||||
|
# content_type="application/json",
|
||||||
|
# )
|
||||||
|
# if resp.status_code != 200:
|
||||||
|
# raise Exception(f"Signup failed: {resp.content}")
|
||||||
|
|
||||||
|
# # Log in
|
||||||
|
client.login(email=payload["email"], password=payload["password"])
|
||||||
|
|
||||||
|
return payload["email"]
|
||||||
|
|
||||||
|
def _assign_random_plan(self,user):
|
||||||
|
"""
|
||||||
|
Pick a random Plan and create + initialize a UserPlan for the user.
|
||||||
|
"""
|
||||||
|
plans = Plan.objects.all()
|
||||||
|
if not plans.exists():
|
||||||
|
raise ValueError("No plans found – please create at least one Plan record.")
|
||||||
|
|
||||||
|
plan = random.choice(plans)
|
||||||
|
|
||||||
|
user_plan, created = UserPlan.objects.get_or_create(
|
||||||
|
user=user,
|
||||||
|
defaults={'plan': plan, 'active': True}
|
||||||
|
)
|
||||||
|
if created:
|
||||||
|
user_plan.initialize()
|
||||||
|
return user_plan
|
||||||
|
|
||||||
|
def _services(self,dealer):
|
||||||
|
additional_services = [
|
||||||
|
{
|
||||||
|
"name": "Vehicle registration transfer assistance",
|
||||||
|
"arabic_name": "مساعدة في نقل ملكية السيارة",
|
||||||
|
"price": decimal.Decimal(random.randrange(100, 1000)),
|
||||||
|
"description": "This is service 1",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "Paperwork collection",
|
||||||
|
"arabic_name": "جمع الأوراق",
|
||||||
|
"price": decimal.Decimal(random.randrange(100, 1000)),
|
||||||
|
"description": "This is service 2",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "Inspection and test drives",
|
||||||
|
"arabic_name": "فحص وقيادة تجريبية",
|
||||||
|
"price": decimal.Decimal(random.randrange(100, 1000)),
|
||||||
|
"description": "This is service 3",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "Shipping and transportation",
|
||||||
|
"arabic_name": "شحن ونقل",
|
||||||
|
"price": decimal.Decimal(random.randrange(100, 1000)),
|
||||||
|
"description": "This is service 4",
|
||||||
|
},
|
||||||
|
]
|
||||||
|
|
||||||
|
for additional_service in additional_services:
|
||||||
|
models.AdditionalServices.objects.create(
|
||||||
|
name=additional_service["name"],
|
||||||
|
arabic_name=additional_service["arabic_name"],
|
||||||
|
price=additional_service["price"],
|
||||||
|
description=additional_service["description"],
|
||||||
|
dealer=dealer,
|
||||||
|
)
|
||||||
242
inventory/management/commands/seed1.py
Normal file
@ -0,0 +1,242 @@
|
|||||||
|
import datetime
|
||||||
|
from time import sleep
|
||||||
|
import json, random, string, decimal
|
||||||
|
from django.core.management.base import BaseCommand
|
||||||
|
from django.test import Client
|
||||||
|
from django.contrib.auth import get_user_model
|
||||||
|
from plans.models import Order, PlanPricing, AbstractOrder, UserPlan, BillingInfo,Plan
|
||||||
|
from inventory.services import decodevin
|
||||||
|
from inventory.tasks import create_user_dealer
|
||||||
|
from inventory.models import AdditionalServices, Car, CarColors, CarFinance, CarMake, CustomGroup, Customer, Dealer, ExteriorColors, InteriorColors, Lead, UnitOfMeasure,Vendor,Staff
|
||||||
|
from django_ledger.models import PurchaseOrderModel,ItemTransactionModel,ItemModel,EntityModel
|
||||||
|
from django_q.tasks import async_task
|
||||||
|
from faker import Faker
|
||||||
|
from appointment.models import Appointment, AppointmentRequest, Service, StaffMember
|
||||||
|
|
||||||
|
User = get_user_model()
|
||||||
|
fake = Faker()
|
||||||
|
|
||||||
|
class Command(BaseCommand):
|
||||||
|
help = "Seed a full dealership via the real signup & downstream views"
|
||||||
|
|
||||||
|
def add_arguments(self, parser):
|
||||||
|
parser.add_argument('--count', type=int, default=1, help='Number of dealers to seed')
|
||||||
|
|
||||||
|
def handle(self, *args, **opts):
|
||||||
|
dealers = Dealer.objects.all()
|
||||||
|
|
||||||
|
for dealer in dealers:
|
||||||
|
self._create_random_po(dealer)
|
||||||
|
self._create_random_vendors(dealer)
|
||||||
|
# self._create_random_staff(dealer)
|
||||||
|
# self._create_random_cars(dealer)
|
||||||
|
self._create_random_customers(dealer)
|
||||||
|
# self._create_randome_services(dealer)
|
||||||
|
|
||||||
|
|
||||||
|
# dealer = Dealer.objects.get(name="Dealer #6")
|
||||||
|
# coa_model = dealer.entity.get_default_coa()
|
||||||
|
# inventory_account = dealer.entity.get_all_accounts().get(name="Inventory (Cars)")
|
||||||
|
# uom = dealer.entity.get_uom_all().get(name="Unit")
|
||||||
|
# item = dealer.entity.create_item_inventory(coa_model=coa_model,inventory_account=inventory_account,uom_model=uom,item_type=ItemModel.ITEM_TYPE_MATERIAL, name=f"Test item {random.randint(1,9999)}")
|
||||||
|
# item = ItemTransactionModel.objects.create(item_name=f"Test item {random.randint(1,9999)}", entity=dealer.entity)
|
||||||
|
# po = PurchaseOrderModel.objects.first()
|
||||||
|
|
||||||
|
self.stdout.write(self.style.SUCCESS(f"✅ PO created for {dealers}"))
|
||||||
|
|
||||||
|
def _create_random_po(self, dealer):
|
||||||
|
for i in range(random.randint(1,70)):
|
||||||
|
try:
|
||||||
|
e: EntityModel = dealer.entity
|
||||||
|
e.create_purchase_order(po_title=f"Test PO {random.randint(1,9999)}-{i}")
|
||||||
|
except Exception as e:
|
||||||
|
pass
|
||||||
|
|
||||||
|
def _create_random_vendors(self, dealer):
|
||||||
|
for i in range(random.randint(1,50)):
|
||||||
|
try:
|
||||||
|
name = fake.name()
|
||||||
|
n = random.randint(1,9999)
|
||||||
|
phone = f"05678{random.randint(0,9)}{random.randint(0,9)}{random.randint(0,9)}{random.randint(0,9)}{random.randint(0,9)}"
|
||||||
|
Vendor.objects.create(dealer=dealer, name=f"{name}{n}", arabic_name=f"{name}{n}", email=f"{name}{n}@tenhal.sa", phone_number=phone,crn=f"CRN {n}", vrn=f"VRN {n}", address=f"Address {fake.address()}",contact_person=f"Contact Person {name}{n}")
|
||||||
|
except Exception as e:
|
||||||
|
pass
|
||||||
|
|
||||||
|
def _create_random_staff(self, dealer):
|
||||||
|
for i in range(5):
|
||||||
|
name = f"{fake.name()}{i}"
|
||||||
|
email = fake.email()
|
||||||
|
password = f"{fake.password()}{i}"
|
||||||
|
user = User.objects.create_user(username=email, email=email, password=password)
|
||||||
|
user.is_staff = True
|
||||||
|
user.save()
|
||||||
|
|
||||||
|
staff_member = StaffMember.objects.create(user=user)
|
||||||
|
services = Service.objects.all()
|
||||||
|
for service in services:
|
||||||
|
staff_member.services_offered.add(service)
|
||||||
|
|
||||||
|
staff = Staff.objects.create(dealer=dealer,staff_member=staff_member,name=name,arabic_name=name,phone_number=fake.phone_number(),active=True)
|
||||||
|
|
||||||
|
groups = CustomGroup.objects.filter(dealer=dealer)
|
||||||
|
random_group = random.choice(list(groups))
|
||||||
|
staff.add_group(random_group.group)
|
||||||
|
# for i in range(random.randint(1,15)):
|
||||||
|
# n = random.randint(1,9999)
|
||||||
|
# phone = f"05678{random.randint(1,9999)}{random.randint(1,9999)}{random.randint(1,9999)}{random.randint(1,9999)}{random.randint(1,9999)}"
|
||||||
|
# Vendor.objects.create(dealer=dealer, name=f"{fake.name}", arabic_name=f"{fake.first_name_female()} {fake.last_name_female()}", email=f"vendor{n}@test.com", phone_number=phone,crn=f"CRN {n}", vrn=f"VRN {n}", address=f"Address {fake.address()}",contact_person=f"Contact Person {n}")
|
||||||
|
|
||||||
|
def _create_random_cars(self,dealer):
|
||||||
|
vendors = Vendor.objects.filter(dealer=dealer).all()
|
||||||
|
|
||||||
|
vin_list = [
|
||||||
|
"1B3ES56C13D120225",
|
||||||
|
"1GB4KYC86FF131536",
|
||||||
|
"1HSHXAHR15J136217",
|
||||||
|
"1G1ZT52845F231124",
|
||||||
|
"1J4GK48K43W721617",
|
||||||
|
"JTDBE32K430163717",
|
||||||
|
"1J4FA69S05P331572",
|
||||||
|
"2FMGK5D86EBD28496",
|
||||||
|
"KNADE243696530337",
|
||||||
|
"1N4AL21EX8N499928",
|
||||||
|
"1N4AL21E49N400571",
|
||||||
|
"1G2NW12E54C145398",
|
||||||
|
]
|
||||||
|
for vin in vin_list:
|
||||||
|
try:
|
||||||
|
for _ in range(random.randint(1,2)):
|
||||||
|
vin = f"{vin[:-4]}{random.randint(0, 9)}{random.randint(0, 9)}{random.randint(0, 9)}{random.randint(0, 9)}"
|
||||||
|
result = decodevin(vin)
|
||||||
|
make = CarMake.objects.get(name=result["maker"])
|
||||||
|
model = make.carmodel_set.filter(name__contains=result["model"]).first()
|
||||||
|
if not model or model == "":
|
||||||
|
model = random.choice(make.carmodel_set.all())
|
||||||
|
year = result["modelYear"]
|
||||||
|
serie = random.choice(model.carserie_set.all())
|
||||||
|
trim = random.choice(serie.cartrim_set.all())
|
||||||
|
vendor = random.choice(vendors)
|
||||||
|
print(make, model, serie, trim, vendor,vin)
|
||||||
|
car = Car.objects.create(
|
||||||
|
vin=vin,
|
||||||
|
id_car_make=make,
|
||||||
|
id_car_model=model,
|
||||||
|
id_car_serie=serie,
|
||||||
|
id_car_trim=trim,
|
||||||
|
vendor=vendor,
|
||||||
|
year=(int(year) or 2025),
|
||||||
|
receiving_date=datetime.datetime.now(),
|
||||||
|
dealer=dealer,
|
||||||
|
mileage=0,
|
||||||
|
)
|
||||||
|
print(car)
|
||||||
|
CarFinance.objects.create(
|
||||||
|
car=car, cost_price=random.randint(10000, 100000), selling_price=0,marked_price=random.randint(10000, 100000)+random.randint(2000, 7000)
|
||||||
|
)
|
||||||
|
CarColors.objects.create(
|
||||||
|
car=car,
|
||||||
|
interior=random.choice(InteriorColors.objects.all()),
|
||||||
|
exterior=random.choice(ExteriorColors.objects.all()),
|
||||||
|
)
|
||||||
|
print(make, model, serie, trim)
|
||||||
|
except Exception as e:
|
||||||
|
print(e)
|
||||||
|
|
||||||
|
def _create_random_customers(self,dealer):
|
||||||
|
for i in range(random.randint(1,60)):
|
||||||
|
try:
|
||||||
|
c = Customer(
|
||||||
|
dealer=dealer,
|
||||||
|
title="MR",
|
||||||
|
first_name=fake.name(),
|
||||||
|
last_name=fake.last_name(),
|
||||||
|
gender="m",
|
||||||
|
email=fake.email(),
|
||||||
|
phone_number=fake.phone_number(),
|
||||||
|
address=fake.address(),
|
||||||
|
national_id=random.randint(1000000000, 9999999999),
|
||||||
|
)
|
||||||
|
c.create_user_model()
|
||||||
|
c.create_customer_model()
|
||||||
|
c.save()
|
||||||
|
except Exception as e:
|
||||||
|
pass
|
||||||
|
|
||||||
|
def _create_randome_services(self,dealer):
|
||||||
|
additional_services = [
|
||||||
|
{
|
||||||
|
"name": "Vehicle registration transfer assistance",
|
||||||
|
"arabic_name": "مساعدة في نقل ملكية السيارة",
|
||||||
|
"price": decimal.Decimal(random.randrange(100, 1000)),
|
||||||
|
"description": "This is service 1",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "Paperwork collection",
|
||||||
|
"arabic_name": "جمع الأوراق",
|
||||||
|
"price": decimal.Decimal(random.randrange(100, 1000)),
|
||||||
|
"description": "This is service 2",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "Inspection and test drives",
|
||||||
|
"arabic_name": "فحص وقيادة تجريبية",
|
||||||
|
"price": decimal.Decimal(random.randrange(100, 1000)),
|
||||||
|
"description": "This is service 3",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "Shipping and transportation",
|
||||||
|
"arabic_name": "شحن ونقل",
|
||||||
|
"price": decimal.Decimal(random.randrange(100, 1000)),
|
||||||
|
"description": "This is service 4",
|
||||||
|
},
|
||||||
|
]
|
||||||
|
uom = UnitOfMeasure.EACH
|
||||||
|
for additional_service in additional_services:
|
||||||
|
AdditionalServices.objects.create(
|
||||||
|
name=additional_service["name"],
|
||||||
|
arabic_name=additional_service["arabic_name"],
|
||||||
|
price=additional_service["price"],
|
||||||
|
description=additional_service["description"],
|
||||||
|
dealer=dealer,
|
||||||
|
uom=uom
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
def _create_random_lead(self,dealer):
|
||||||
|
for i in range(random.randint(1,60)):
|
||||||
|
try:
|
||||||
|
first_name = fake.name()
|
||||||
|
last_name = fake.last_name()
|
||||||
|
email = fake.email()
|
||||||
|
staff = random.choice(Staff.objects.filter(dealer=dealer))
|
||||||
|
|
||||||
|
make = random.choice(CarMake.objects.all())
|
||||||
|
model = random.choice(make.carmodel_set.all())
|
||||||
|
lead = Lead.objects.create(
|
||||||
|
dealer=dealer,
|
||||||
|
first_name=first_name,
|
||||||
|
last_name=last_name,
|
||||||
|
email=email,
|
||||||
|
address=fake.address(),
|
||||||
|
lead_type="customer",
|
||||||
|
id_car_make=make,
|
||||||
|
id_car_model=model,
|
||||||
|
source="website",
|
||||||
|
channel="website",
|
||||||
|
staff=staff
|
||||||
|
)
|
||||||
|
c = Customer(
|
||||||
|
dealer=dealer,
|
||||||
|
title="MR",
|
||||||
|
first_name=fake.name(),
|
||||||
|
last_name=fake.last_name(),
|
||||||
|
gender="m",
|
||||||
|
email=fake.email(),
|
||||||
|
phone_number=fake.phone_number(),
|
||||||
|
address=fake.address(),
|
||||||
|
national_id=random.randint(1000000000, 9999999999),
|
||||||
|
)
|
||||||
|
c.create_user_model()
|
||||||
|
c.create_customer_model()
|
||||||
|
c.save()
|
||||||
|
except Exception as e:
|
||||||
|
pass
|
||||||
@ -1,16 +1,49 @@
|
|||||||
from inventory.models import Lead,Car
|
from inventory.models import Lead, Car
|
||||||
from django.contrib.auth.models import Permission
|
from django.contrib.auth.models import Permission
|
||||||
from django.core.management.base import BaseCommand
|
from django.core.management.base import BaseCommand
|
||||||
from django.contrib.contenttypes.models import ContentType
|
from django.contrib.contenttypes.models import ContentType
|
||||||
from django_ledger.models import EstimateModel,BillModel,AccountModel,LedgerModel
|
from django_ledger.models import EstimateModel, BillModel, AccountModel, LedgerModel
|
||||||
|
|
||||||
|
|
||||||
class Command(BaseCommand):
|
class Command(BaseCommand):
|
||||||
def handle(self, *args, **kwargs):
|
def handle(self, *args, **kwargs):
|
||||||
Permission.objects.get_or_create(name="Can view crm",codename="can_view_crm",content_type=ContentType.objects.get_for_model(Lead))
|
Permission.objects.get_or_create(
|
||||||
Permission.objects.get_or_create(name="Can reassign lead",codename="can_reassign_lead",content_type=ContentType.objects.get_for_model(Lead))
|
name="Can view crm",
|
||||||
Permission.objects.get_or_create(name="Can view sales",codename="can_view_sales",content_type=ContentType.objects.get_for_model(EstimateModel))
|
codename="can_view_crm",
|
||||||
Permission.objects.get_or_create(name="Can view reports",codename="can_view_reports",content_type=ContentType.objects.get_for_model(LedgerModel))
|
content_type=ContentType.objects.get_for_model(Lead),
|
||||||
Permission.objects.get_or_create(name="Can view inventory",codename="can_view_inventory",content_type=ContentType.objects.get_for_model(Car))
|
)
|
||||||
Permission.objects.get_or_create(name="Can approve bill",codename="can_approve_billmodel",content_type=ContentType.objects.get_for_model(BillModel))
|
Permission.objects.get_or_create(
|
||||||
Permission.objects.get_or_create(name="Can view financials",codename="can_view_financials",content_type=ContentType.objects.get_for_model(AccountModel))
|
name="Can reassign lead",
|
||||||
Permission.objects.get_or_create(name="Can approve estimate",codename="can_approve_estimatemodel",content_type=ContentType.objects.get_for_model(EstimateModel))
|
codename="can_reassign_lead",
|
||||||
|
content_type=ContentType.objects.get_for_model(Lead),
|
||||||
|
)
|
||||||
|
Permission.objects.get_or_create(
|
||||||
|
name="Can view sales",
|
||||||
|
codename="can_view_sales",
|
||||||
|
content_type=ContentType.objects.get_for_model(EstimateModel),
|
||||||
|
)
|
||||||
|
Permission.objects.get_or_create(
|
||||||
|
name="Can view reports",
|
||||||
|
codename="can_view_reports",
|
||||||
|
content_type=ContentType.objects.get_for_model(LedgerModel),
|
||||||
|
)
|
||||||
|
Permission.objects.get_or_create(
|
||||||
|
name="Can view inventory",
|
||||||
|
codename="can_view_inventory",
|
||||||
|
content_type=ContentType.objects.get_for_model(Car),
|
||||||
|
)
|
||||||
|
Permission.objects.get_or_create(
|
||||||
|
name="Can approve bill",
|
||||||
|
codename="can_approve_billmodel",
|
||||||
|
content_type=ContentType.objects.get_for_model(BillModel),
|
||||||
|
)
|
||||||
|
Permission.objects.get_or_create(
|
||||||
|
name="Can view financials",
|
||||||
|
codename="can_view_financials",
|
||||||
|
content_type=ContentType.objects.get_for_model(AccountModel),
|
||||||
|
)
|
||||||
|
Permission.objects.get_or_create(
|
||||||
|
name="Can approve estimate",
|
||||||
|
codename="can_approve_estimatemodel",
|
||||||
|
content_type=ContentType.objects.get_for_model(EstimateModel),
|
||||||
|
)
|
||||||
|
|||||||
@ -1,4 +1,5 @@
|
|||||||
import logging
|
import logging
|
||||||
|
import time
|
||||||
|
|
||||||
# from django.http import Http404, HttpResponseForbidden
|
# from django.http import Http404, HttpResponseForbidden
|
||||||
# from django.shortcuts import redirect
|
# from django.shortcuts import redirect
|
||||||
@ -44,33 +45,33 @@ logger = logging.getLogger("user_activity")
|
|||||||
# return request.META.get("REMOTE_ADDR")
|
# return request.META.get("REMOTE_ADDR")
|
||||||
|
|
||||||
|
|
||||||
class InjectParamsMiddleware:
|
# class InjectParamsMiddleware:
|
||||||
"""
|
# """
|
||||||
Middleware to add processed user-related parameters to the request object.
|
# Middleware to add processed user-related parameters to the request object.
|
||||||
|
|
||||||
This middleware processes incoming requests to extract and enhance user
|
# This middleware processes incoming requests to extract and enhance user
|
||||||
information, specifically linking user context such as `dealer` to the
|
# information, specifically linking user context such as `dealer` to the
|
||||||
request. It allows subsequent views and middlewares to access these enriched
|
# request. It allows subsequent views and middlewares to access these enriched
|
||||||
request parameters with ease.
|
# request parameters with ease.
|
||||||
|
|
||||||
:ivar get_response: The callable to get the next middleware or view response.
|
# :ivar get_response: The callable to get the next middleware or view response.
|
||||||
:type get_response: Callable
|
# :type get_response: Callable
|
||||||
"""
|
# """
|
||||||
|
|
||||||
def __init__(self, get_response):
|
# def __init__(self, get_response):
|
||||||
self.get_response = get_response
|
# self.get_response = get_response
|
||||||
|
|
||||||
def __call__(self, request):
|
# def __call__(self, request):
|
||||||
try:
|
# try:
|
||||||
if request.user.is_authenticated:
|
# if request.user.is_authenticated:
|
||||||
request.dealer = get_user_type(request)
|
# request.dealer = get_user_type(request)
|
||||||
request.entity = request.dealer.entity
|
# request.entity = request.dealer.entity
|
||||||
else:
|
# else:
|
||||||
request.dealer = None
|
# request.dealer = None
|
||||||
except Exception:
|
# except Exception:
|
||||||
pass
|
# pass
|
||||||
response = self.get_response(request)
|
# response = self.get_response(request)
|
||||||
return response
|
# return response
|
||||||
|
|
||||||
|
|
||||||
class InjectDealerMiddleware:
|
class InjectDealerMiddleware:
|
||||||
@ -93,6 +94,7 @@ class InjectDealerMiddleware:
|
|||||||
|
|
||||||
def __call__(self, request):
|
def __call__(self, request):
|
||||||
try:
|
try:
|
||||||
|
start = time.time()
|
||||||
if request.user.is_authenticated:
|
if request.user.is_authenticated:
|
||||||
request.is_dealer = False
|
request.is_dealer = False
|
||||||
request.is_staff = False
|
request.is_staff = False
|
||||||
@ -103,6 +105,7 @@ class InjectDealerMiddleware:
|
|||||||
if hasattr(request.user, "dealer"):
|
if hasattr(request.user, "dealer"):
|
||||||
request.is_dealer = True
|
request.is_dealer = True
|
||||||
request.dealer = request.user.dealer
|
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.staff = request.user.staffmember.staff
|
||||||
@ -120,6 +123,7 @@ class InjectDealerMiddleware:
|
|||||||
request.is_inventory = True
|
request.is_inventory = True
|
||||||
request.entity = request.dealer.entity
|
request.entity = request.dealer.entity
|
||||||
request.admin = request.dealer.entity.admin
|
request.admin = request.dealer.entity.admin
|
||||||
|
print("\033[92m⏱ Middleware time:", time.time() - start, "\033[0m")
|
||||||
except Exception:
|
except Exception:
|
||||||
pass
|
pass
|
||||||
response = self.get_response(request)
|
response = self.get_response(request)
|
||||||
@ -135,6 +139,7 @@ class InjectDealerMiddleware:
|
|||||||
# return redirect(reverse('verify_otp'))
|
# return redirect(reverse('verify_otp'))
|
||||||
# return self.get_response(request)
|
# return self.get_response(request)
|
||||||
|
|
||||||
|
|
||||||
class DealerSlugMiddleware:
|
class DealerSlugMiddleware:
|
||||||
def __init__(self, get_response):
|
def __init__(self, get_response):
|
||||||
self.get_response = get_response
|
self.get_response = get_response
|
||||||
@ -142,19 +147,22 @@ class DealerSlugMiddleware:
|
|||||||
def __call__(self, request):
|
def __call__(self, request):
|
||||||
response = self.get_response(request)
|
response = self.get_response(request)
|
||||||
return response
|
return response
|
||||||
|
|
||||||
def process_view(self, request, view_func, view_args, view_kwargs):
|
def process_view(self, request, view_func, view_args, view_kwargs):
|
||||||
if request.path_info.startswith('/ar/signup/') or \
|
if (
|
||||||
request.path_info.startswith('/en/signup/') or \
|
request.path_info.startswith("/ar/signup/")
|
||||||
request.path_info.startswith('/ar/login/') or \
|
or request.path_info.startswith("/en/signup/")
|
||||||
request.path_info.startswith('/en/login/') or \
|
or request.path_info.startswith("/ar/login/")
|
||||||
request.path_info.startswith('/ar/logout/') or \
|
or request.path_info.startswith("/en/login/")
|
||||||
request.path_info.startswith('/en/logout/') or \
|
or request.path_info.startswith("/ar/logout/")
|
||||||
request.path_info.startswith('/en/ledger/') or \
|
or request.path_info.startswith("/en/logout/")
|
||||||
request.path_info.startswith('/ar/ledger/') or \
|
or request.path_info.startswith("/en/ledger/")
|
||||||
request.path_info.startswith('/en/notifications/') or \
|
or request.path_info.startswith("/ar/ledger/")
|
||||||
request.path_info.startswith('/ar/notifications/') or \
|
or request.path_info.startswith("/en/notifications/")
|
||||||
request.path_info.startswith('/en/appointment/') or \
|
or request.path_info.startswith("/ar/notifications/")
|
||||||
request.path_info.startswith('/ar/appointment/'):
|
or request.path_info.startswith("/en/appointment/")
|
||||||
|
or request.path_info.startswith("/ar/appointment/")
|
||||||
|
):
|
||||||
return None
|
return None
|
||||||
|
|
||||||
if not request.user.is_authenticated:
|
if not request.user.is_authenticated:
|
||||||
@ -164,12 +172,14 @@ class DealerSlugMiddleware:
|
|||||||
if not dealer_slug:
|
if not dealer_slug:
|
||||||
return None
|
return None
|
||||||
|
|
||||||
if not hasattr(request, 'dealer') or not request.dealer:
|
if not hasattr(request, "dealer") or not request.dealer:
|
||||||
logger.warning("No dealer associated with request")
|
logger.warning("No dealer associated with request")
|
||||||
return None
|
return None
|
||||||
|
|
||||||
if dealer_slug.lower() != request.dealer.slug.lower():
|
if dealer_slug.lower() != request.dealer.slug.lower():
|
||||||
logger.warning(f"Dealer slug mismatch: {dealer_slug} != {request.dealer.slug}")
|
logger.warning(
|
||||||
|
f"Dealer slug mismatch: {dealer_slug} != {request.dealer.slug}"
|
||||||
|
)
|
||||||
raise Http404("Dealer slug mismatch")
|
raise Http404("Dealer slug mismatch")
|
||||||
|
|
||||||
return None
|
return None
|
||||||
|
|||||||
@ -1,4 +1,4 @@
|
|||||||
# Generated by Django 5.2.4 on 2025-07-14 15:04
|
# Generated by Django 5.2.4 on 2025-07-22 08:37
|
||||||
|
|
||||||
import datetime
|
import datetime
|
||||||
import django.core.serializers.json
|
import django.core.serializers.json
|
||||||
@ -48,7 +48,7 @@ class Migration(migrations.Migration):
|
|||||||
('name', models.CharField(blank=True, max_length=255, null=True)),
|
('name', models.CharField(blank=True, max_length=255, null=True)),
|
||||||
('slug', models.SlugField(blank=True, max_length=255, null=True, unique=True)),
|
('slug', models.SlugField(blank=True, max_length=255, null=True, unique=True)),
|
||||||
('arabic_name', models.CharField(blank=True, max_length=255, null=True)),
|
('arabic_name', models.CharField(blank=True, max_length=255, null=True)),
|
||||||
('logo', models.ImageField(blank=True, null=True, upload_to='car_make', verbose_name='logo')),
|
('logo', models.ImageField(blank=True, default='user-logo.png', null=True, upload_to='car_make', verbose_name='logo')),
|
||||||
('is_sa_import', models.BooleanField(default=False)),
|
('is_sa_import', models.BooleanField(default=False)),
|
||||||
('car_type', models.SmallIntegerField(blank=True, choices=[(1, 'Car'), (2, 'Light Commercial'), (3, 'Heavy-Duty Tractors'), (4, 'Trailers'), (5, 'Medium Trucks'), (6, 'Buses'), (20, 'Motorcycles'), (21, 'Buggy'), (22, 'Moto ATV'), (23, 'Scooters'), (24, 'Karting'), (25, 'ATV'), (26, 'Snowmobiles')], null=True)),
|
('car_type', models.SmallIntegerField(blank=True, choices=[(1, 'Car'), (2, 'Light Commercial'), (3, 'Heavy-Duty Tractors'), (4, 'Trailers'), (5, 'Medium Trucks'), (6, 'Buses'), (20, 'Motorcycles'), (21, 'Buggy'), (22, 'Moto ATV'), (23, 'Scooters'), (24, 'Karting'), (25, 'ATV'), (26, 'Snowmobiles')], null=True)),
|
||||||
],
|
],
|
||||||
@ -253,7 +253,7 @@ class Migration(migrations.Migration):
|
|||||||
('name', models.CharField(max_length=255, verbose_name='English Name')),
|
('name', models.CharField(max_length=255, verbose_name='English Name')),
|
||||||
('phone_number', phonenumber_field.modelfields.PhoneNumberField(max_length=128, region='SA', verbose_name='Phone Number')),
|
('phone_number', phonenumber_field.modelfields.PhoneNumberField(max_length=128, region='SA', verbose_name='Phone Number')),
|
||||||
('address', models.CharField(blank=True, max_length=200, null=True, verbose_name='Address')),
|
('address', models.CharField(blank=True, max_length=200, null=True, verbose_name='Address')),
|
||||||
('logo', models.ImageField(blank=True, null=True, upload_to='logos/users', verbose_name='Logo')),
|
('logo', models.ImageField(blank=True, default='user-logo.png', null=True, upload_to='logos/users', verbose_name='Logo')),
|
||||||
('joined_at', models.DateTimeField(auto_now_add=True, verbose_name='Joined At')),
|
('joined_at', models.DateTimeField(auto_now_add=True, verbose_name='Joined At')),
|
||||||
('updated_at', models.DateTimeField(auto_now=True, verbose_name='Updated At')),
|
('updated_at', models.DateTimeField(auto_now=True, verbose_name='Updated At')),
|
||||||
('slug', models.SlugField(blank=True, max_length=255, null=True, unique=True)),
|
('slug', models.SlugField(blank=True, max_length=255, null=True, unique=True)),
|
||||||
@ -566,7 +566,7 @@ class Migration(migrations.Migration):
|
|||||||
('email', models.EmailField(max_length=254, verbose_name='Email')),
|
('email', models.EmailField(max_length=254, verbose_name='Email')),
|
||||||
('phone_number', phonenumber_field.modelfields.PhoneNumberField(max_length=128, region='SA', verbose_name='Phone Number')),
|
('phone_number', phonenumber_field.modelfields.PhoneNumberField(max_length=128, region='SA', verbose_name='Phone Number')),
|
||||||
('address', models.CharField(blank=True, max_length=200, null=True, verbose_name='Address')),
|
('address', models.CharField(blank=True, max_length=200, null=True, verbose_name='Address')),
|
||||||
('logo', models.ImageField(blank=True, null=True, upload_to='logos', verbose_name='Logo')),
|
('logo', models.ImageField(blank=True, default='user-logo.png', null=True, upload_to='logos', verbose_name='Logo')),
|
||||||
('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')),
|
||||||
@ -772,7 +772,7 @@ class Migration(migrations.Migration):
|
|||||||
('phone_number', phonenumber_field.modelfields.PhoneNumberField(max_length=128, region='SA', verbose_name='Phone Number')),
|
('phone_number', phonenumber_field.modelfields.PhoneNumberField(max_length=128, region='SA', verbose_name='Phone Number')),
|
||||||
('staff_type', models.CharField(choices=[('inventory', 'Inventory'), ('accountant', 'Accountant'), ('sales', 'Sales')], max_length=255, verbose_name='Staff Type')),
|
('staff_type', models.CharField(choices=[('inventory', 'Inventory'), ('accountant', 'Accountant'), ('sales', 'Sales')], max_length=255, verbose_name='Staff Type')),
|
||||||
('address', models.CharField(blank=True, max_length=200, null=True, verbose_name='Address')),
|
('address', models.CharField(blank=True, max_length=200, null=True, verbose_name='Address')),
|
||||||
('image', models.ImageField(blank=True, null=True, upload_to='staff/', verbose_name='Image')),
|
('logo', models.ImageField(blank=True, default='user-logo.png', null=True, upload_to='logos/staff', 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')),
|
||||||
@ -872,7 +872,7 @@ class Migration(migrations.Migration):
|
|||||||
('phone_number', phonenumber_field.modelfields.PhoneNumberField(max_length=128, region='SA', verbose_name='Phone Number')),
|
('phone_number', phonenumber_field.modelfields.PhoneNumberField(max_length=128, region='SA', verbose_name='Phone Number')),
|
||||||
('email', models.EmailField(max_length=255, verbose_name='Email Address')),
|
('email', models.EmailField(max_length=255, verbose_name='Email Address')),
|
||||||
('address', models.CharField(max_length=200, verbose_name='Address')),
|
('address', models.CharField(max_length=200, verbose_name='Address')),
|
||||||
('logo', models.ImageField(blank=True, null=True, upload_to='logos/vendors', verbose_name='Logo')),
|
('logo', models.ImageField(blank=True, default='user-logo.png', null=True, upload_to='logos/vendors', verbose_name='Logo')),
|
||||||
('active', models.BooleanField(default=True, verbose_name='Active')),
|
('active', models.BooleanField(default=True, verbose_name='Active')),
|
||||||
('created_at', models.DateTimeField(auto_now_add=True, verbose_name='Created At')),
|
('created_at', models.DateTimeField(auto_now_add=True, verbose_name='Created At')),
|
||||||
('slug', models.SlugField(blank=True, max_length=255, null=True, unique=True, verbose_name='Slug')),
|
('slug', models.SlugField(blank=True, max_length=255, null=True, unique=True, verbose_name='Slug')),
|
||||||
@ -905,7 +905,7 @@ class Migration(migrations.Migration):
|
|||||||
options={
|
options={
|
||||||
'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')],
|
'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=['marked_price'], name='car_finance_marked_price_idx'), models.Index(fields=['discount_amount'], name='car_finance_discount_idx')],
|
||||||
},
|
},
|
||||||
),
|
),
|
||||||
migrations.AddIndex(
|
migrations.AddIndex(
|
||||||
|
|||||||
@ -79,13 +79,15 @@ class DealerSlugMixin:
|
|||||||
|
|
||||||
class AuthorizedEntityMixin:
|
class AuthorizedEntityMixin:
|
||||||
def get_authorized_entity_queryset(self):
|
def get_authorized_entity_queryset(self):
|
||||||
dealer = get_object_or_404(models.Dealer,slug=self.kwargs["dealer_slug"])
|
dealer = get_object_or_404(models.Dealer, slug=self.kwargs["dealer_slug"])
|
||||||
return EntityModel.objects.for_user(
|
return EntityModel.objects.for_user(
|
||||||
user_model=dealer.entity.admin,
|
user_model=dealer.entity.admin,
|
||||||
authorized_superuser=self.get_superuser_authorization(),
|
authorized_superuser=self.get_superuser_authorization(),
|
||||||
)
|
)
|
||||||
|
|
||||||
def get_queryset(self):
|
def get_queryset(self):
|
||||||
dealer = get_object_or_404(models.Dealer,slug=self.kwargs["dealer_slug"])
|
dealer = get_object_or_404(models.Dealer, slug=self.kwargs["dealer_slug"])
|
||||||
self.queryset = EntityModel.objects.for_user(
|
self.queryset = EntityModel.objects.for_user(
|
||||||
user_model=dealer.entity.admin).select_related('default_coa')
|
user_model=dealer.entity.admin
|
||||||
|
).select_related("default_coa")
|
||||||
return super().get_queryset()
|
return super().get_queryset()
|
||||||
|
|||||||
@ -18,7 +18,7 @@ from django_ledger.models import (
|
|||||||
ItemModel,
|
ItemModel,
|
||||||
CustomerModel,
|
CustomerModel,
|
||||||
JournalEntryModel,
|
JournalEntryModel,
|
||||||
LedgerModel
|
LedgerModel,
|
||||||
)
|
)
|
||||||
from django_ledger.io.io_core import get_localdate
|
from django_ledger.io.io_core import get_localdate
|
||||||
from django.core.exceptions import ValidationError
|
from django.core.exceptions import ValidationError
|
||||||
@ -35,7 +35,7 @@ from django_ledger.models import (
|
|||||||
EntityManagementModel,
|
EntityManagementModel,
|
||||||
PurchaseOrderModel,
|
PurchaseOrderModel,
|
||||||
ItemTransactionModel,
|
ItemTransactionModel,
|
||||||
BillModel
|
BillModel,
|
||||||
)
|
)
|
||||||
from django.contrib.contenttypes.fields import GenericForeignKey
|
from django.contrib.contenttypes.fields import GenericForeignKey
|
||||||
from django.contrib.contenttypes.models import ContentType
|
from django.contrib.contenttypes.models import ContentType
|
||||||
@ -223,7 +223,7 @@ class CarMake(models.Model, LocalizedNameMixin):
|
|||||||
name = models.CharField(max_length=255, blank=True, null=True)
|
name = models.CharField(max_length=255, blank=True, null=True)
|
||||||
slug = models.SlugField(max_length=255, unique=True, blank=True, null=True)
|
slug = models.SlugField(max_length=255, unique=True, blank=True, null=True)
|
||||||
arabic_name = models.CharField(max_length=255, blank=True, null=True)
|
arabic_name = models.CharField(max_length=255, blank=True, null=True)
|
||||||
logo = models.ImageField(_("logo"), upload_to="car_make", blank=True, null=True)
|
logo = models.ImageField(_("logo"), upload_to="car_make", blank=True, null=True,default="user-logo.png")
|
||||||
is_sa_import = models.BooleanField(default=False)
|
is_sa_import = models.BooleanField(default=False)
|
||||||
car_type = models.SmallIntegerField(choices=CarType.choices, blank=True, null=True)
|
car_type = models.SmallIntegerField(choices=CarType.choices, blank=True, null=True)
|
||||||
|
|
||||||
@ -248,9 +248,9 @@ class CarMake(models.Model, LocalizedNameMixin):
|
|||||||
class Meta:
|
class Meta:
|
||||||
verbose_name = _("Make")
|
verbose_name = _("Make")
|
||||||
indexes = [
|
indexes = [
|
||||||
models.Index(fields=['name'], name='car_make_name_idx'),
|
models.Index(fields=["name"], name="car_make_name_idx"),
|
||||||
models.Index(fields=['is_sa_import'], name='car_make_sa_import_idx'),
|
models.Index(fields=["is_sa_import"], name="car_make_sa_import_idx"),
|
||||||
models.Index(fields=['car_type'], name='car_make_type_idx'),
|
models.Index(fields=["car_type"], name="car_make_type_idx"),
|
||||||
]
|
]
|
||||||
|
|
||||||
|
|
||||||
@ -282,9 +282,11 @@ class CarModel(models.Model, LocalizedNameMixin):
|
|||||||
class Meta:
|
class Meta:
|
||||||
verbose_name = _("Model")
|
verbose_name = _("Model")
|
||||||
indexes = [
|
indexes = [
|
||||||
models.Index(fields=['id_car_make'], name='car_model_make_idx'),
|
models.Index(fields=["id_car_make"], name="car_model_make_idx"),
|
||||||
models.Index(fields=['name'], name='car_model_name_idx'),
|
models.Index(fields=["name"], name="car_model_name_idx"),
|
||||||
models.Index(fields=['id_car_make', 'name'], name='car_model_make_name_idx'),
|
models.Index(
|
||||||
|
fields=["id_car_make", "name"], name="car_model_make_name_idx"
|
||||||
|
),
|
||||||
]
|
]
|
||||||
|
|
||||||
|
|
||||||
@ -321,10 +323,10 @@ class CarSerie(models.Model, LocalizedNameMixin):
|
|||||||
class Meta:
|
class Meta:
|
||||||
verbose_name = _("Series")
|
verbose_name = _("Series")
|
||||||
indexes = [
|
indexes = [
|
||||||
models.Index(fields=['id_car_model'], name='car_serie_model_idx'),
|
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=["year_begin", "year_end"], name="car_serie_years_idx"),
|
||||||
models.Index(fields=['name'], name='car_serie_name_idx'),
|
models.Index(fields=["name"], name="car_serie_name_idx"),
|
||||||
models.Index(fields=['generation_name'], name='car_serie_generation_idx'),
|
models.Index(fields=["generation_name"], name="car_serie_generation_idx"),
|
||||||
]
|
]
|
||||||
|
|
||||||
|
|
||||||
@ -360,9 +362,12 @@ class CarTrim(models.Model, LocalizedNameMixin):
|
|||||||
class Meta:
|
class Meta:
|
||||||
verbose_name = _("Trim")
|
verbose_name = _("Trim")
|
||||||
indexes = [
|
indexes = [
|
||||||
models.Index(fields=['id_car_serie'], name='car_trim_serie_idx'),
|
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(
|
||||||
models.Index(fields=['name'], name='car_trim_name_idx'),
|
fields=["start_production_year", "end_production_year"],
|
||||||
|
name="car_trim_prod_years_idx",
|
||||||
|
),
|
||||||
|
models.Index(fields=["name"], name="car_trim_name_idx"),
|
||||||
]
|
]
|
||||||
|
|
||||||
|
|
||||||
@ -385,9 +390,9 @@ class CarEquipment(models.Model, LocalizedNameMixin):
|
|||||||
class Meta:
|
class Meta:
|
||||||
verbose_name = _("Equipment")
|
verbose_name = _("Equipment")
|
||||||
indexes = [
|
indexes = [
|
||||||
models.Index(fields=['id_car_trim'], name='car_equipment_trim_idx'),
|
models.Index(fields=["id_car_trim"], name="car_equipment_trim_idx"),
|
||||||
models.Index(fields=['year_begin'], name='car_equipment_year_idx'),
|
models.Index(fields=["year_begin"], name="car_equipment_year_idx"),
|
||||||
models.Index(fields=['name'], name='car_equipment_name_idx'),
|
models.Index(fields=["name"], name="car_equipment_name_idx"),
|
||||||
]
|
]
|
||||||
|
|
||||||
|
|
||||||
@ -421,8 +426,8 @@ class CarSpecification(models.Model, LocalizedNameMixin):
|
|||||||
class Meta:
|
class Meta:
|
||||||
verbose_name = _("Specification")
|
verbose_name = _("Specification")
|
||||||
indexes = [
|
indexes = [
|
||||||
models.Index(fields=['id_parent'], name='car_spec_parent_idx'),
|
models.Index(fields=["id_parent"], name="car_spec_parent_idx"),
|
||||||
models.Index(fields=['name'], name='car_spec_name_idx'),
|
models.Index(fields=["name"], name="car_spec_name_idx"),
|
||||||
]
|
]
|
||||||
|
|
||||||
|
|
||||||
@ -441,9 +446,12 @@ class CarSpecificationValue(models.Model):
|
|||||||
class Meta:
|
class Meta:
|
||||||
verbose_name = _("Specification Value")
|
verbose_name = _("Specification Value")
|
||||||
indexes = [
|
indexes = [
|
||||||
models.Index(fields=['id_car_trim'], name='car_spec_val_trim_idx'),
|
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_specification"], name="car_spec_val_spec_idx"),
|
||||||
models.Index(fields=['id_car_trim', 'id_car_specification'], name='car_spec_val_trim_spec_idx'),
|
models.Index(
|
||||||
|
fields=["id_car_trim", "id_car_specification"],
|
||||||
|
name="car_spec_val_trim_spec_idx",
|
||||||
|
),
|
||||||
]
|
]
|
||||||
|
|
||||||
|
|
||||||
@ -477,8 +485,8 @@ class CarOption(models.Model, LocalizedNameMixin):
|
|||||||
class Meta:
|
class Meta:
|
||||||
verbose_name = _("Option")
|
verbose_name = _("Option")
|
||||||
indexes = [
|
indexes = [
|
||||||
models.Index(fields=['id_parent'], name='car_option_parent_idx'),
|
models.Index(fields=["id_parent"], name="car_option_parent_idx"),
|
||||||
models.Index(fields=['name'], name='car_option_name_idx'),
|
models.Index(fields=["name"], name="car_option_name_idx"),
|
||||||
]
|
]
|
||||||
|
|
||||||
|
|
||||||
@ -500,10 +508,13 @@ class CarOptionValue(models.Model):
|
|||||||
class Meta:
|
class Meta:
|
||||||
verbose_name = _("Option Value")
|
verbose_name = _("Option Value")
|
||||||
indexes = [
|
indexes = [
|
||||||
models.Index(fields=['id_car_option'], name='car_opt_val_option_idx'),
|
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=["id_car_equipment"], name="car_opt_val_equipment_idx"),
|
||||||
models.Index(fields=['is_base'], name='car_opt_val_is_base_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'),
|
models.Index(
|
||||||
|
fields=["id_car_option", "id_car_equipment"],
|
||||||
|
name="cov_option_equipment_idx",
|
||||||
|
),
|
||||||
]
|
]
|
||||||
|
|
||||||
|
|
||||||
@ -566,7 +577,7 @@ class AdditionalServices(models.Model, LocalizedNameMixin):
|
|||||||
|
|
||||||
@property
|
@property
|
||||||
def price_(self):
|
def price_(self):
|
||||||
vat = VatRate.objects.filter(dealer=self.dealer,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
|
||||||
@ -639,6 +650,7 @@ class Car(Base):
|
|||||||
default=CarStatusChoices.AVAILABLE,
|
default=CarStatusChoices.AVAILABLE,
|
||||||
verbose_name=_("Status"),
|
verbose_name=_("Status"),
|
||||||
)
|
)
|
||||||
|
|
||||||
stock_type = models.CharField(
|
stock_type = models.CharField(
|
||||||
max_length=10,
|
max_length=10,
|
||||||
choices=CarStockTypeChoices.choices,
|
choices=CarStockTypeChoices.choices,
|
||||||
@ -648,13 +660,17 @@ class Car(Base):
|
|||||||
remarks = models.TextField(blank=True, null=True, verbose_name=_("Remarks"))
|
remarks = models.TextField(blank=True, null=True, verbose_name=_("Remarks"))
|
||||||
mileage = models.IntegerField(blank=True, null=True, verbose_name=_("Mileage"))
|
mileage = models.IntegerField(blank=True, null=True, verbose_name=_("Mileage"))
|
||||||
receiving_date = models.DateTimeField(verbose_name=_("Receiving Date"))
|
receiving_date = models.DateTimeField(verbose_name=_("Receiving Date"))
|
||||||
|
sold_date=models.DateTimeField(verbose_name=_("Sold Date"))
|
||||||
hash = models.CharField(
|
hash = models.CharField(
|
||||||
max_length=64, blank=True, null=True, verbose_name=_("Hash")
|
max_length=64, blank=True, null=True, verbose_name=_("Hash")
|
||||||
)
|
)
|
||||||
# history = HistoricalRecords()
|
# history = HistoricalRecords()
|
||||||
|
|
||||||
def get_absolute_url(self):
|
def get_absolute_url(self):
|
||||||
return reverse("car_detail", kwargs={"dealer_slug": self.dealer.slug,"slug": self.slug})
|
return reverse(
|
||||||
|
"car_detail", kwargs={"dealer_slug": self.dealer.slug, "slug": self.slug}
|
||||||
|
)
|
||||||
|
|
||||||
def save(self, *args, **kwargs):
|
def save(self, *args, **kwargs):
|
||||||
self.slug = slugify(self.vin)
|
self.slug = slugify(self.vin)
|
||||||
self.hash = self.get_hash
|
self.hash = self.get_hash
|
||||||
@ -664,24 +680,27 @@ class Car(Base):
|
|||||||
verbose_name = _("Car")
|
verbose_name = _("Car")
|
||||||
verbose_name_plural = _("Cars")
|
verbose_name_plural = _("Cars")
|
||||||
indexes = [
|
indexes = [
|
||||||
models.Index(fields=['vin'], name='car_vin_idx'),
|
models.Index(fields=["vin"], name="car_vin_idx"),
|
||||||
models.Index(fields=['year'], name='car_year_idx'),
|
models.Index(fields=["year"], name="car_year_idx"),
|
||||||
models.Index(fields=['status'], name='car_status_idx'),
|
models.Index(fields=["status"], name="car_status_idx"),
|
||||||
|
models.Index(fields=["dealer"], name="car_dealer_idx"),
|
||||||
models.Index(fields=['dealer'], name='car_dealer_idx'),
|
models.Index(fields=["vendor"], name="car_vendor_idx"),
|
||||||
models.Index(fields=['vendor'], name='car_vendor_idx'),
|
models.Index(fields=["id_car_make"], name="car_make_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_model'], name='car_model_idx'),
|
models.Index(fields=["id_car_serie"], name="car_serie_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_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', 'id_car_model'], name='car_make_model_idx'),
|
),
|
||||||
models.Index(fields=['id_car_make', 'year'], name='car_make_year_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=["dealer", "status"], name="car_dealer_status_idx"),
|
||||||
models.Index(fields=['vendor', 'status'], name='car_vendor_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=["year", "status"], name="car_year_status_idx"),
|
||||||
models.Index(fields=['status'], name='car_active_status_idx',
|
models.Index(
|
||||||
condition=Q(status=CarStatusChoices.AVAILABLE)),
|
fields=["status"],
|
||||||
|
name="car_active_status_idx",
|
||||||
|
condition=Q(status=CarStatusChoices.AVAILABLE),
|
||||||
|
),
|
||||||
]
|
]
|
||||||
|
|
||||||
def __str__(self):
|
def __str__(self):
|
||||||
@ -702,6 +721,12 @@ class Car(Base):
|
|||||||
return active_reservations.exists()
|
return active_reservations.exists()
|
||||||
|
|
||||||
@property
|
@property
|
||||||
|
def logo(self):
|
||||||
|
return getattr(self.id_car_make, "logo", "")
|
||||||
|
@property
|
||||||
|
def additional_services(self):
|
||||||
|
return self.finances.additional_services.all()
|
||||||
|
@property
|
||||||
def ready(self):
|
def ready(self):
|
||||||
try:
|
try:
|
||||||
return all(
|
return all(
|
||||||
@ -797,10 +822,12 @@ class Car(Base):
|
|||||||
car=self, exterior=exterior, interior=interior
|
car=self, exterior=exterior, interior=interior
|
||||||
)
|
)
|
||||||
self.save()
|
self.save()
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def logo(self):
|
def logo(self):
|
||||||
return self.id_car_make.logo.url if self.id_car_make.logo else None
|
return self.id_car_make.logo.url if self.id_car_make.logo else None
|
||||||
|
|
||||||
|
|
||||||
class CarTransfer(models.Model):
|
class CarTransfer(models.Model):
|
||||||
car = models.ForeignKey(
|
car = models.ForeignKey(
|
||||||
"Car",
|
"Car",
|
||||||
@ -890,10 +917,16 @@ class CarFinance(models.Model):
|
|||||||
max_digits=14, decimal_places=2, verbose_name=_("Cost Price")
|
max_digits=14, decimal_places=2, verbose_name=_("Cost Price")
|
||||||
)
|
)
|
||||||
selling_price = models.DecimalField(
|
selling_price = models.DecimalField(
|
||||||
max_digits=14, decimal_places=2, verbose_name=_("Selling Price"),default=Decimal("0.00")
|
max_digits=14,
|
||||||
|
decimal_places=2,
|
||||||
|
verbose_name=_("Selling Price"),
|
||||||
|
default=Decimal("0.00"),
|
||||||
)
|
)
|
||||||
marked_price = models.DecimalField(
|
marked_price = models.DecimalField(
|
||||||
max_digits=14, decimal_places=2, verbose_name=_("Marked Price"),default=Decimal("0.00")
|
max_digits=14,
|
||||||
|
decimal_places=2,
|
||||||
|
verbose_name=_("Marked Price"),
|
||||||
|
default=Decimal("0.00"),
|
||||||
)
|
)
|
||||||
discount_amount = models.DecimalField(
|
discount_amount = models.DecimalField(
|
||||||
max_digits=14,
|
max_digits=14,
|
||||||
@ -901,7 +934,8 @@ class CarFinance(models.Model):
|
|||||||
verbose_name=_("Discount Amount"),
|
verbose_name=_("Discount Amount"),
|
||||||
default=Decimal("0.00"),
|
default=Decimal("0.00"),
|
||||||
)
|
)
|
||||||
is_sold = models.BooleanField(default=False)
|
# is_sold = models.BooleanField(default=False)
|
||||||
|
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def total(self):
|
def total(self):
|
||||||
@ -965,11 +999,13 @@ class CarFinance(models.Model):
|
|||||||
verbose_name = _("Car Financial Details")
|
verbose_name = _("Car Financial Details")
|
||||||
verbose_name_plural = _("Car Financial Details")
|
verbose_name_plural = _("Car Financial Details")
|
||||||
indexes = [
|
indexes = [
|
||||||
models.Index(fields=['car'], name='car_finance_car_idx'),
|
models.Index(fields=["car"], name="car_finance_car_idx"),
|
||||||
models.Index(fields=['cost_price'], name='car_finance_cost_price_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(
|
||||||
models.Index(fields=['marked_price'], name='car_finance_marked_price_idx'),
|
fields=["selling_price"], name="car_finance_selling_price_idx"
|
||||||
models.Index(fields=['discount_amount'], name='car_finance_discount_idx'),
|
),
|
||||||
|
models.Index(fields=["marked_price"], name="car_finance_marked_price_idx"),
|
||||||
|
models.Index(fields=["discount_amount"], name="car_finance_discount_idx"),
|
||||||
]
|
]
|
||||||
|
|
||||||
|
|
||||||
@ -982,8 +1018,8 @@ class ExteriorColors(models.Model, LocalizedNameMixin):
|
|||||||
verbose_name = _("Exterior Colors")
|
verbose_name = _("Exterior Colors")
|
||||||
verbose_name_plural = _("Exterior Colors")
|
verbose_name_plural = _("Exterior Colors")
|
||||||
indexes = [
|
indexes = [
|
||||||
models.Index(fields=['name'], name='exterior_color_name_idx'),
|
models.Index(fields=["name"], name="exterior_color_name_idx"),
|
||||||
models.Index(fields=['arabic_name'], name='exterior_color_arabic_name_idx'),
|
models.Index(fields=["arabic_name"], name="exterior_color_arabic_name_idx"),
|
||||||
]
|
]
|
||||||
|
|
||||||
def __str__(self):
|
def __str__(self):
|
||||||
@ -999,8 +1035,8 @@ class InteriorColors(models.Model, LocalizedNameMixin):
|
|||||||
verbose_name = _("Interior Colors")
|
verbose_name = _("Interior Colors")
|
||||||
verbose_name_plural = _("Interior Colors")
|
verbose_name_plural = _("Interior Colors")
|
||||||
indexes = [
|
indexes = [
|
||||||
models.Index(fields=['name'], name='interior_color_name_idx'),
|
models.Index(fields=["name"], name="interior_color_name_idx"),
|
||||||
models.Index(fields=['arabic_name'], name='interior_color_arabic_name_idx'),
|
models.Index(fields=["arabic_name"], name="interior_color_arabic_name_idx"),
|
||||||
]
|
]
|
||||||
|
|
||||||
def __str__(self):
|
def __str__(self):
|
||||||
@ -1021,9 +1057,11 @@ class CarColors(models.Model):
|
|||||||
verbose_name_plural = _("Colors")
|
verbose_name_plural = _("Colors")
|
||||||
unique_together = ("car", "exterior", "interior")
|
unique_together = ("car", "exterior", "interior")
|
||||||
indexes = [
|
indexes = [
|
||||||
models.Index(fields=['exterior'], name='car_colors_exterior_idx'),
|
models.Index(fields=["exterior"], name="car_colors_exterior_idx"),
|
||||||
models.Index(fields=['interior'], name='car_colors_interior_idx'),
|
models.Index(fields=["interior"], name="car_colors_interior_idx"),
|
||||||
models.Index(fields=['exterior', 'interior'], name='car_colors_ext_int_combo_idx'),
|
models.Index(
|
||||||
|
fields=["exterior", "interior"], name="car_colors_ext_int_combo_idx"
|
||||||
|
),
|
||||||
]
|
]
|
||||||
|
|
||||||
def __str__(self):
|
def __str__(self):
|
||||||
@ -1141,7 +1179,17 @@ class Dealer(models.Model, LocalizedNameMixin):
|
|||||||
max_length=200, blank=True, null=True, verbose_name=_("Address")
|
max_length=200, blank=True, null=True, verbose_name=_("Address")
|
||||||
)
|
)
|
||||||
logo = models.ImageField(
|
logo = models.ImageField(
|
||||||
upload_to="logos/users", blank=True, null=True, verbose_name=_("Logo"),default="logo.png"
|
upload_to="logos/users",
|
||||||
|
blank=True,
|
||||||
|
null=True,
|
||||||
|
verbose_name=_("Logo"),
|
||||||
|
default="user-logo.png",
|
||||||
|
)
|
||||||
|
thumbnail = ImageSpecField(
|
||||||
|
source="logo",
|
||||||
|
processors=[ResizeToFill(40, 40)],
|
||||||
|
format="WEBP",
|
||||||
|
options={"quality": 80},
|
||||||
)
|
)
|
||||||
entity = models.ForeignKey(
|
entity = models.ForeignKey(
|
||||||
EntityModel, on_delete=models.SET_NULL, null=True, blank=True
|
EntityModel, on_delete=models.SET_NULL, null=True, blank=True
|
||||||
@ -1200,7 +1248,8 @@ class Dealer(models.Model, LocalizedNameMixin):
|
|||||||
|
|
||||||
@property
|
@property
|
||||||
def vat_rate(self):
|
def vat_rate(self):
|
||||||
return VatRate.objects.get(dealer=self,is_active=True).rate
|
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")
|
||||||
@ -1238,13 +1287,13 @@ class Staff(models.Model, LocalizedNameMixin):
|
|||||||
max_length=200, blank=True, null=True, verbose_name=_("Address")
|
max_length=200, blank=True, null=True, verbose_name=_("Address")
|
||||||
)
|
)
|
||||||
logo = models.ImageField(
|
logo = models.ImageField(
|
||||||
upload_to="logos/staff", blank=True, null=True, verbose_name=_("Image")
|
upload_to="logos/staff", blank=True, null=True, verbose_name=_("Image"),default="user-logo.png"
|
||||||
)
|
)
|
||||||
thumbnail = ImageSpecField(
|
thumbnail = ImageSpecField(
|
||||||
source='logo',
|
source="logo",
|
||||||
processors=[ResizeToFill(40, 40)],
|
processors=[ResizeToFill(40, 40)],
|
||||||
format='WEBP',
|
format="WEBP",
|
||||||
options={'quality': 80}
|
options={"quality": 80},
|
||||||
)
|
)
|
||||||
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"))
|
||||||
@ -1297,13 +1346,15 @@ class Staff(models.Model, LocalizedNameMixin):
|
|||||||
|
|
||||||
@property
|
@property
|
||||||
def groups(self):
|
def groups(self):
|
||||||
return CustomGroup.objects.select_related("group").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()
|
||||||
return self.user.groups.clear()
|
return self.user.groups.clear()
|
||||||
|
|
||||||
def add_group(self, group,clean=False):
|
def add_group(self, group, clean=False):
|
||||||
if clean:
|
if clean:
|
||||||
self.clear_groups()
|
self.clear_groups()
|
||||||
try:
|
try:
|
||||||
@ -1317,19 +1368,19 @@ class Staff(models.Model, LocalizedNameMixin):
|
|||||||
entity = self.dealer.entity
|
entity = self.dealer.entity
|
||||||
if entity.managers.count() == 0:
|
if entity.managers.count() == 0:
|
||||||
entity.managers.add(self.user)
|
entity.managers.add(self.user)
|
||||||
|
|
||||||
def remove_superuser_permission(self):
|
def remove_superuser_permission(self):
|
||||||
entity = self.dealer.entity
|
entity = self.dealer.entity
|
||||||
if self.user in entity.managers.all():
|
if self.user in entity.managers.all():
|
||||||
entity.managers.remove(self.user)
|
entity.managers.remove(self.user)
|
||||||
|
|
||||||
|
|
||||||
class Meta:
|
class Meta:
|
||||||
verbose_name = _("Staff")
|
verbose_name = _("Staff")
|
||||||
verbose_name_plural = _("Staff")
|
verbose_name_plural = _("Staff")
|
||||||
indexes = [
|
indexes = [
|
||||||
models.Index(fields=["name"]),
|
models.Index(fields=["name"]),
|
||||||
models.Index(fields=["staff_type"]),
|
models.Index(fields=["staff_type"]),
|
||||||
]
|
]
|
||||||
permissions = []
|
permissions = []
|
||||||
|
|
||||||
def __str__(self):
|
def __str__(self):
|
||||||
@ -1461,6 +1512,12 @@ class Customer(models.Model):
|
|||||||
image = models.ImageField(
|
image = models.ImageField(
|
||||||
upload_to="customers/", blank=True, null=True, verbose_name=_("Image")
|
upload_to="customers/", blank=True, null=True, verbose_name=_("Image")
|
||||||
)
|
)
|
||||||
|
thumbnail = ImageSpecField(
|
||||||
|
source="image",
|
||||||
|
processors=[ResizeToFill(40, 40)],
|
||||||
|
format="WEBP",
|
||||||
|
options={"quality": 80},
|
||||||
|
)
|
||||||
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"))
|
||||||
slug = models.SlugField(
|
slug = models.SlugField(
|
||||||
@ -1603,7 +1660,13 @@ class Organization(models.Model, LocalizedNameMixin):
|
|||||||
max_length=200, blank=True, null=True, verbose_name=_("Address")
|
max_length=200, blank=True, null=True, verbose_name=_("Address")
|
||||||
)
|
)
|
||||||
logo = models.ImageField(
|
logo = models.ImageField(
|
||||||
upload_to="logos", blank=True, null=True, verbose_name=_("Logo")
|
upload_to="logos", blank=True, null=True, verbose_name=_("Logo"),default="user-logo.png"
|
||||||
|
)
|
||||||
|
thumbnail = ImageSpecField(
|
||||||
|
source="logo",
|
||||||
|
processors=[ResizeToFill(40, 40)],
|
||||||
|
format="WEBP",
|
||||||
|
options={"quality": 80},
|
||||||
)
|
)
|
||||||
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"))
|
||||||
@ -1975,7 +2038,7 @@ class Schedule(models.Model):
|
|||||||
dealer = models.ForeignKey(Dealer, on_delete=models.CASCADE)
|
dealer = models.ForeignKey(Dealer, on_delete=models.CASCADE)
|
||||||
content_type = models.ForeignKey(ContentType, on_delete=models.CASCADE)
|
content_type = models.ForeignKey(ContentType, on_delete=models.CASCADE)
|
||||||
object_id = models.PositiveIntegerField()
|
object_id = models.PositiveIntegerField()
|
||||||
content_object = GenericForeignKey('content_type', 'object_id')
|
content_object = GenericForeignKey("content_type", "object_id")
|
||||||
customer = models.ForeignKey(
|
customer = models.ForeignKey(
|
||||||
CustomerModel,
|
CustomerModel,
|
||||||
on_delete=models.CASCADE,
|
on_delete=models.CASCADE,
|
||||||
@ -2152,7 +2215,6 @@ class Opportunity(models.Model):
|
|||||||
# scheduled_by=self.request.user
|
# scheduled_by=self.request.user
|
||||||
# )
|
# )
|
||||||
return (
|
return (
|
||||||
|
|
||||||
self.lead.get_all_schedules()
|
self.lead.get_all_schedules()
|
||||||
.filter(scheduled_at__gt=timezone.now())
|
.filter(scheduled_at__gt=timezone.now())
|
||||||
.order_by("scheduled_at")
|
.order_by("scheduled_at")
|
||||||
@ -2222,17 +2284,19 @@ class Notes(models.Model):
|
|||||||
verbose_name = _("Note")
|
verbose_name = _("Note")
|
||||||
verbose_name_plural = _("Notes")
|
verbose_name_plural = _("Notes")
|
||||||
indexes = [
|
indexes = [
|
||||||
models.Index(fields=['dealer'], name='note_dealer_idx'),
|
models.Index(fields=["dealer"], name="note_dealer_idx"),
|
||||||
models.Index(fields=['created_by'], name='note_created_by_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"], name="note_content_type_idx"),
|
||||||
models.Index(fields=['content_type', 'object_id'], name='note_content_object_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=["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=["dealer", "created"], name="note_dealer_created_idx"),
|
||||||
models.Index(fields=['content_type', 'object_id', 'created'],
|
models.Index(
|
||||||
name='note_content_obj_created_idx'),
|
fields=["content_type", "object_id", "created"],
|
||||||
|
name="note_content_obj_created_idx",
|
||||||
|
),
|
||||||
]
|
]
|
||||||
|
|
||||||
def __str__(self):
|
def __str__(self):
|
||||||
@ -2265,17 +2329,19 @@ class Tasks(models.Model):
|
|||||||
verbose_name = _("Task")
|
verbose_name = _("Task")
|
||||||
verbose_name_plural = _("Tasks")
|
verbose_name_plural = _("Tasks")
|
||||||
indexes = [
|
indexes = [
|
||||||
models.Index(fields=['dealer'], name='task_dealer_idx'),
|
models.Index(fields=["dealer"], name="task_dealer_idx"),
|
||||||
models.Index(fields=['created_by'], name='task_created_by_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"], name="task_content_type_idx"),
|
||||||
models.Index(fields=['content_type', 'object_id'], name='task_content_object_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=["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=["dealer", "created"], name="task_dealer_created_idx"),
|
||||||
models.Index(fields=['content_type', 'object_id', 'created'],
|
models.Index(
|
||||||
name='task_content_obj_created_idx'),
|
fields=["content_type", "object_id", "created"],
|
||||||
|
name="task_content_obj_created_idx",
|
||||||
|
),
|
||||||
]
|
]
|
||||||
|
|
||||||
def __str__(self):
|
def __str__(self):
|
||||||
@ -2306,15 +2372,17 @@ class Email(models.Model):
|
|||||||
verbose_name = _("Email")
|
verbose_name = _("Email")
|
||||||
verbose_name_plural = _("Emails")
|
verbose_name_plural = _("Emails")
|
||||||
indexes = [
|
indexes = [
|
||||||
models.Index(fields=['created_by'], name='email_created_by_idx'),
|
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"], name="email_content_type_idx"),
|
||||||
models.Index(fields=['content_type', 'object_id'], name='email_content_object_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=["created"], name="email_created_date_idx"),
|
||||||
|
models.Index(fields=["updated"], name="email_updated_date_idx"),
|
||||||
models.Index(fields=['content_type', 'object_id', 'created'],
|
models.Index(
|
||||||
name='email_content_obj_created_idx'),
|
fields=["content_type", "object_id", "created"],
|
||||||
|
name="email_content_obj_created_idx",
|
||||||
|
),
|
||||||
]
|
]
|
||||||
|
|
||||||
def __str__(self):
|
def __str__(self):
|
||||||
@ -2342,15 +2410,17 @@ class Activity(models.Model):
|
|||||||
verbose_name = _("Activity")
|
verbose_name = _("Activity")
|
||||||
verbose_name_plural = _("Activities")
|
verbose_name_plural = _("Activities")
|
||||||
indexes = [
|
indexes = [
|
||||||
models.Index(fields=['created_by'], name='activity_created_by_idx'),
|
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"], name="activity_content_type_idx"),
|
||||||
models.Index(fields=['content_type', 'object_id'], name='activity_content_object_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=["created"], name="activity_created_date_idx"),
|
||||||
|
models.Index(fields=["updated"], name="activity_updated_date_idx"),
|
||||||
models.Index(fields=['content_type', 'object_id', 'created'],
|
models.Index(
|
||||||
name='a_content_obj_created_idx'),
|
fields=["content_type", "object_id", "created"],
|
||||||
|
name="a_content_obj_created_idx",
|
||||||
|
),
|
||||||
]
|
]
|
||||||
|
|
||||||
def __str__(self):
|
def __str__(self):
|
||||||
@ -2371,9 +2441,9 @@ class Notification(models.Model):
|
|||||||
ordering = ["-created"]
|
ordering = ["-created"]
|
||||||
|
|
||||||
indexes = [
|
indexes = [
|
||||||
models.Index(fields=['user'], name='notification_user_idx'),
|
models.Index(fields=["user"], name="notification_user_idx"),
|
||||||
models.Index(fields=['is_read'], name='notification_is_read_idx'),
|
models.Index(fields=["is_read"], name="notification_is_read_idx"),
|
||||||
models.Index(fields=['created'], name='notification_created_date_idx'),
|
models.Index(fields=["created"], name="notification_created_date_idx"),
|
||||||
]
|
]
|
||||||
|
|
||||||
def __str__(self):
|
def __str__(self):
|
||||||
@ -2410,7 +2480,13 @@ class Vendor(models.Model, LocalizedNameMixin):
|
|||||||
email = models.EmailField(max_length=255, verbose_name=_("Email Address"))
|
email = models.EmailField(max_length=255, verbose_name=_("Email Address"))
|
||||||
address = models.CharField(max_length=200, verbose_name=_("Address"))
|
address = models.CharField(max_length=200, verbose_name=_("Address"))
|
||||||
logo = models.ImageField(
|
logo = models.ImageField(
|
||||||
upload_to="logos/vendors", blank=True, null=True, verbose_name=_("Logo")
|
upload_to="logos/vendors", blank=True, null=True, verbose_name=_("Logo"),default="user-logo.png"
|
||||||
|
)
|
||||||
|
thumbnail = ImageSpecField(
|
||||||
|
source="logo",
|
||||||
|
processors=[ResizeToFill(40, 40)],
|
||||||
|
format="WEBP",
|
||||||
|
options={"quality": 80},
|
||||||
)
|
)
|
||||||
active = models.BooleanField(default=True, verbose_name=_("Active"))
|
active = models.BooleanField(default=True, verbose_name=_("Active"))
|
||||||
created_at = models.DateTimeField(auto_now_add=True, verbose_name=_("Created At"))
|
created_at = models.DateTimeField(auto_now_add=True, verbose_name=_("Created At"))
|
||||||
@ -2419,7 +2495,9 @@ class Vendor(models.Model, LocalizedNameMixin):
|
|||||||
)
|
)
|
||||||
|
|
||||||
def get_absolute_url(self):
|
def get_absolute_url(self):
|
||||||
return reverse("vendor_detail", kwargs={"dealer_slug":self.dealer.slug,"slug": self.slug})
|
return reverse(
|
||||||
|
"vendor_detail", kwargs={"dealer_slug": self.dealer.slug, "slug": self.slug}
|
||||||
|
)
|
||||||
|
|
||||||
def save(self, *args, **kwargs):
|
def save(self, *args, **kwargs):
|
||||||
if not self.slug:
|
if not self.slug:
|
||||||
@ -2440,10 +2518,10 @@ class Vendor(models.Model, LocalizedNameMixin):
|
|||||||
verbose_name = _("Vendor")
|
verbose_name = _("Vendor")
|
||||||
verbose_name_plural = _("Vendors")
|
verbose_name_plural = _("Vendors")
|
||||||
indexes = [
|
indexes = [
|
||||||
models.Index(fields=['slug'], name='vendor_slug_idx'),
|
models.Index(fields=["slug"], name="vendor_slug_idx"),
|
||||||
models.Index(fields=['active'], name='vendor_active_idx'),
|
models.Index(fields=["active"], name="vendor_active_idx"),
|
||||||
models.Index(fields=['crn'], name='vendor_crn_idx'),
|
models.Index(fields=["crn"], name="vendor_crn_idx"),
|
||||||
models.Index(fields=['vrn'], name='vendor_vrn_idx'),
|
models.Index(fields=["vrn"], name="vendor_vrn_idx"),
|
||||||
]
|
]
|
||||||
|
|
||||||
def __str__(self):
|
def __str__(self):
|
||||||
@ -2731,7 +2809,7 @@ class SaleOrder(models.Model):
|
|||||||
if self.invoice:
|
if self.invoice:
|
||||||
# Check if get_itemtxs_data returns data before proceeding
|
# Check if get_itemtxs_data returns data before proceeding
|
||||||
# You might want to handle what get_itemtxs_data returns if it can be empty
|
# You might want to handle what get_itemtxs_data returns if it can be empty
|
||||||
item_data = self.invoice.get_itemtxs_data()
|
item_data = self.estimate.get_itemtxs_data()[0]
|
||||||
if item_data:
|
if item_data:
|
||||||
return item_data
|
return item_data
|
||||||
return [] # Return an empty list if no invoice or no item data
|
return [] # Return an empty list if no invoice or no item data
|
||||||
@ -2758,6 +2836,7 @@ 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:
|
class Meta:
|
||||||
verbose_name = _("Custom Group")
|
verbose_name = _("Custom Group")
|
||||||
verbose_name_plural = _("Custom Groups")
|
verbose_name_plural = _("Custom Groups")
|
||||||
@ -2804,7 +2883,7 @@ class CustomGroup(models.Model):
|
|||||||
|
|
||||||
######################################
|
######################################
|
||||||
######################################
|
######################################
|
||||||
#MANAGER
|
# MANAGER
|
||||||
######################################
|
######################################
|
||||||
######################################
|
######################################
|
||||||
if self.name == "Manager":
|
if self.name == "Manager":
|
||||||
@ -2851,14 +2930,21 @@ class CustomGroup(models.Model):
|
|||||||
"journalentrymodel",
|
"journalentrymodel",
|
||||||
"purchaseordermodel",
|
"purchaseordermodel",
|
||||||
"ledgermodel",
|
"ledgermodel",
|
||||||
"transactionmodel"
|
"transactionmodel",
|
||||||
|
],
|
||||||
|
other_perms=[
|
||||||
|
"can_approve_estimatemodel",
|
||||||
|
"can_approve_billmodel",
|
||||||
|
"can_view_inventory",
|
||||||
|
"can_view_sales",
|
||||||
|
"can_view_crm",
|
||||||
|
"can_view_financials",
|
||||||
|
"can_view_reports",
|
||||||
],
|
],
|
||||||
other_perms=["can_approve_estimatemodel","can_approve_billmodel","can_view_inventory","can_view_sales","can_view_crm","can_view_financials","can_view_reports"],
|
|
||||||
|
|
||||||
)
|
)
|
||||||
######################################
|
######################################
|
||||||
######################################
|
######################################
|
||||||
#Inventory
|
# Inventory
|
||||||
######################################
|
######################################
|
||||||
######################################
|
######################################
|
||||||
elif self.name == "Inventory":
|
elif self.name == "Inventory":
|
||||||
@ -2876,7 +2962,7 @@ class CustomGroup(models.Model):
|
|||||||
"notes",
|
"notes",
|
||||||
"tasks",
|
"tasks",
|
||||||
"activity",
|
"activity",
|
||||||
"poitemsuploaded"
|
"poitemsuploaded",
|
||||||
],
|
],
|
||||||
)
|
)
|
||||||
self.set_permissions(
|
self.set_permissions(
|
||||||
@ -2884,12 +2970,11 @@ class CustomGroup(models.Model):
|
|||||||
allowed_models=[],
|
allowed_models=[],
|
||||||
other_perms=[
|
other_perms=[
|
||||||
"view_purchaseordermodel",
|
"view_purchaseordermodel",
|
||||||
|
],
|
||||||
]
|
|
||||||
)
|
)
|
||||||
######################################
|
######################################
|
||||||
######################################
|
######################################
|
||||||
#Sales
|
# Sales
|
||||||
######################################
|
######################################
|
||||||
######################################
|
######################################
|
||||||
elif self.name == "Sales":
|
elif self.name == "Sales":
|
||||||
@ -2911,8 +2996,7 @@ class CustomGroup(models.Model):
|
|||||||
"organization",
|
"organization",
|
||||||
"notes",
|
"notes",
|
||||||
"tasks",
|
"tasks",
|
||||||
"lead"
|
"leadactivity",
|
||||||
"activity",
|
|
||||||
],
|
],
|
||||||
other_perms=[
|
other_perms=[
|
||||||
"view_car",
|
"view_car",
|
||||||
@ -2931,7 +3015,7 @@ class CustomGroup(models.Model):
|
|||||||
)
|
)
|
||||||
######################################
|
######################################
|
||||||
######################################
|
######################################
|
||||||
#Accountant
|
# Accountant
|
||||||
######################################
|
######################################
|
||||||
######################################
|
######################################
|
||||||
elif self.name == "Accountant":
|
elif self.name == "Accountant":
|
||||||
@ -2944,7 +3028,7 @@ class CustomGroup(models.Model):
|
|||||||
"activity",
|
"activity",
|
||||||
"payment",
|
"payment",
|
||||||
"vendor",
|
"vendor",
|
||||||
],
|
],
|
||||||
other_perms=[
|
other_perms=[
|
||||||
"view_car",
|
"view_car",
|
||||||
"view_carlocation",
|
"view_carlocation",
|
||||||
@ -2955,7 +3039,6 @@ class CustomGroup(models.Model):
|
|||||||
"view_leads",
|
"view_leads",
|
||||||
"view_opportunity",
|
"view_opportunity",
|
||||||
"view_customers",
|
"view_customers",
|
||||||
|
|
||||||
],
|
],
|
||||||
)
|
)
|
||||||
self.set_permissions(
|
self.set_permissions(
|
||||||
@ -2967,18 +3050,28 @@ class CustomGroup(models.Model):
|
|||||||
"itemmodel",
|
"itemmodel",
|
||||||
"invoicemodel",
|
"invoicemodel",
|
||||||
"vendormodel",
|
"vendormodel",
|
||||||
|
|
||||||
"journalentrymodel",
|
"journalentrymodel",
|
||||||
"purchaseordermodel",
|
"purchaseordermodel",
|
||||||
"estimatemodel",
|
"estimatemodel",
|
||||||
"customermodel",
|
"customermodel",
|
||||||
"ledgermodel",
|
"ledgermodel",
|
||||||
"transactionmodel"
|
"transactionmodel",
|
||||||
|
],
|
||||||
|
other_perms=[
|
||||||
|
"view_billmodel",
|
||||||
|
"add_billmodel",
|
||||||
|
"change_billmodel",
|
||||||
|
"delete_billmodel",
|
||||||
|
"view_customermodel",
|
||||||
|
"view_estimatemodel",
|
||||||
|
"can_view_inventory",
|
||||||
|
"can_view_sales",
|
||||||
|
"can_view_crm",
|
||||||
|
"can_view_financials",
|
||||||
|
"can_view_reports",
|
||||||
],
|
],
|
||||||
other_perms=["view_billmodel","add_billmodel","change_billmodel","delete_billmodel","view_customermodel", "view_estimatemodel","can_view_inventory","can_view_sales","can_view_crm","can_view_financials","can_view_reports"],
|
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
def set_permissions(self, app="inventory", allowed_models=[], other_perms=[]):
|
def set_permissions(self, app="inventory", allowed_models=[], other_perms=[]):
|
||||||
try:
|
try:
|
||||||
for perm in Permission.objects.filter(
|
for perm in Permission.objects.filter(
|
||||||
@ -3158,7 +3251,11 @@ class PaymentHistory(models.Model):
|
|||||||
class PoItemsUploaded(models.Model):
|
class PoItemsUploaded(models.Model):
|
||||||
dealer = models.ForeignKey(Dealer, on_delete=models.CASCADE, null=True, blank=True)
|
dealer = models.ForeignKey(Dealer, on_delete=models.CASCADE, null=True, blank=True)
|
||||||
po = models.ForeignKey(
|
po = models.ForeignKey(
|
||||||
PurchaseOrderModel, on_delete=models.CASCADE, null=True, blank=True, related_name="items"
|
PurchaseOrderModel,
|
||||||
|
on_delete=models.CASCADE,
|
||||||
|
null=True,
|
||||||
|
blank=True,
|
||||||
|
related_name="items",
|
||||||
)
|
)
|
||||||
item = models.ForeignKey(
|
item = models.ForeignKey(
|
||||||
ItemTransactionModel,
|
ItemTransactionModel,
|
||||||
@ -3178,8 +3275,11 @@ class PoItemsUploaded(models.Model):
|
|||||||
models.Index(fields=["po"]),
|
models.Index(fields=["po"]),
|
||||||
models.Index(fields=["item"]),
|
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):
|
||||||
"""
|
"""
|
||||||
Stores additional information for any model with:
|
Stores additional information for any model with:
|
||||||
@ -3187,20 +3287,19 @@ class ExtraInfo(models.Model):
|
|||||||
- JSON data storage
|
- JSON data storage
|
||||||
- Tracking fields
|
- Tracking fields
|
||||||
"""
|
"""
|
||||||
|
|
||||||
dealer = models.ForeignKey(
|
dealer = models.ForeignKey(
|
||||||
Dealer,
|
Dealer,
|
||||||
on_delete=models.SET_NULL,
|
on_delete=models.SET_NULL,
|
||||||
null=True,
|
null=True,
|
||||||
blank=True,
|
blank=True,
|
||||||
related_name="extra_info"
|
related_name="extra_info",
|
||||||
)
|
)
|
||||||
content_type = models.ForeignKey(
|
content_type = models.ForeignKey(
|
||||||
ContentType,
|
ContentType, on_delete=models.CASCADE, related_name="extra_info_primary"
|
||||||
on_delete=models.CASCADE,
|
|
||||||
related_name="extra_info_primary"
|
|
||||||
)
|
)
|
||||||
object_id = models.CharField(max_length=255, null=True, blank=True)
|
object_id = models.CharField(max_length=255, null=True, blank=True)
|
||||||
content_object = GenericForeignKey('content_type', 'object_id')
|
content_object = GenericForeignKey("content_type", "object_id")
|
||||||
|
|
||||||
# Secondary GenericForeignKey (optional additional link)
|
# Secondary GenericForeignKey (optional additional link)
|
||||||
related_content_type = models.ForeignKey(
|
related_content_type = models.ForeignKey(
|
||||||
@ -3208,43 +3307,34 @@ class ExtraInfo(models.Model):
|
|||||||
on_delete=models.SET_NULL,
|
on_delete=models.SET_NULL,
|
||||||
null=True,
|
null=True,
|
||||||
blank=True,
|
blank=True,
|
||||||
related_name="extra_info_secondary"
|
related_name="extra_info_secondary",
|
||||||
)
|
)
|
||||||
related_object_id = models.CharField(max_length=255, null=True, blank=True)
|
related_object_id = models.CharField(max_length=255, null=True, blank=True)
|
||||||
related_object = GenericForeignKey('related_content_type', 'related_object_id')
|
related_object = GenericForeignKey("related_content_type", "related_object_id")
|
||||||
|
|
||||||
# JSON Data Storage
|
# JSON Data Storage
|
||||||
data = models.JSONField(
|
data = models.JSONField(encoder=DjangoJSONEncoder, default=dict, blank=True)
|
||||||
encoder=DjangoJSONEncoder,
|
|
||||||
default=dict,
|
|
||||||
blank=True
|
|
||||||
)
|
|
||||||
|
|
||||||
# Metadata
|
# Metadata
|
||||||
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)
|
||||||
created_by = models.ForeignKey(
|
created_by = models.ForeignKey(
|
||||||
User,
|
User, on_delete=models.SET_NULL, null=True, related_name="created_extra_info"
|
||||||
on_delete=models.SET_NULL,
|
|
||||||
null=True,
|
|
||||||
related_name="created_extra_info"
|
|
||||||
)
|
)
|
||||||
|
|
||||||
class Meta:
|
class Meta:
|
||||||
indexes = [
|
indexes = [
|
||||||
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")
|
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})"
|
||||||
|
|
||||||
|
|
||||||
@classmethod
|
@classmethod
|
||||||
def get_sale_orders(cls, staff=None, is_dealer=False):
|
def get_sale_orders(cls, staff=None, is_dealer=False,dealer=None):
|
||||||
if not staff and not is_dealer:
|
if not staff and not is_dealer:
|
||||||
return []
|
return []
|
||||||
|
|
||||||
@ -3253,20 +3343,37 @@ class ExtraInfo(models.Model):
|
|||||||
|
|
||||||
if is_dealer:
|
if is_dealer:
|
||||||
qs = cls.objects.filter(
|
qs = cls.objects.filter(
|
||||||
|
dealer=dealer,
|
||||||
content_type=content_type,
|
content_type=content_type,
|
||||||
related_content_type=related_content_type,
|
related_content_type=related_content_type,
|
||||||
related_object_id__isnull=False
|
related_object_id__isnull=False,
|
||||||
)
|
).union(cls.objects.filter(
|
||||||
|
dealer=dealer,
|
||||||
|
content_type=ContentType.objects.get_for_model(EstimateModel),
|
||||||
|
related_content_type=ContentType.objects.get_for_model(User),
|
||||||
|
))
|
||||||
else:
|
else:
|
||||||
qs = cls.objects.filter(
|
qs = cls.objects.filter(
|
||||||
|
dealer=dealer,
|
||||||
content_type=content_type,
|
content_type=content_type,
|
||||||
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")
|
# qs = qs.select_related("customer","estimate","invoice")
|
||||||
return [x.content_object.sale_orders.select_related("customer","estimate","invoice").first() for x in qs if x.content_object.sale_orders.first()]
|
data = SaleOrder.objects.filter(pk__in=[x.content_object.sale_orders.select_related("customer","estimate","invoice").first().pk for x in qs if x.content_object.sale_orders.first()])
|
||||||
|
|
||||||
|
return data
|
||||||
|
|
||||||
|
# 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,dealer=None):
|
||||||
if not staff and not is_dealer:
|
if not staff and not is_dealer:
|
||||||
return []
|
return []
|
||||||
|
|
||||||
@ -3275,15 +3382,24 @@ class ExtraInfo(models.Model):
|
|||||||
|
|
||||||
if is_dealer:
|
if is_dealer:
|
||||||
qs = cls.objects.filter(
|
qs = cls.objects.filter(
|
||||||
|
dealer=dealer,
|
||||||
content_type=content_type,
|
content_type=content_type,
|
||||||
related_content_type=related_content_type,
|
related_content_type=related_content_type,
|
||||||
related_object_id__isnull=False
|
related_object_id__isnull=False,
|
||||||
)
|
).union(cls.objects.filter(
|
||||||
|
dealer=dealer,
|
||||||
|
content_type=content_type,
|
||||||
|
related_content_type=ContentType.objects.get_for_model(User),
|
||||||
|
))
|
||||||
else:
|
else:
|
||||||
qs = cls.objects.filter(
|
qs = cls.objects.filter(
|
||||||
|
dealer=dealer,
|
||||||
content_type=content_type,
|
content_type=content_type,
|
||||||
related_content_type=related_content_type,
|
related_content_type=related_content_type,
|
||||||
related_object_id=staff.pk
|
related_object_id=staff.pk,
|
||||||
)
|
)
|
||||||
print(qs[0].content_object.invoicemodel_set.first())
|
return [
|
||||||
return [x.content_object.invoicemodel_set.first() for x in qs if x.content_object.invoicemodel_set.first()]
|
x.content_object.invoicemodel_set.first()
|
||||||
|
for x in qs
|
||||||
|
if x.content_object.invoicemodel_set.first()
|
||||||
|
]
|
||||||
|
|||||||
0
inventory/notifications/__init__.py
Normal file
205
inventory/notifications/sse.py
Normal file
@ -0,0 +1,205 @@
|
|||||||
|
# import json
|
||||||
|
# from django.contrib.auth.models import AnonymousUser
|
||||||
|
# from django.contrib.auth import get_user_model
|
||||||
|
# from django.db import close_old_connections
|
||||||
|
# from urllib.parse import parse_qs
|
||||||
|
# from channels.db import database_sync_to_async
|
||||||
|
# from inventory.models import Notification
|
||||||
|
# import asyncio
|
||||||
|
|
||||||
|
# @database_sync_to_async
|
||||||
|
# def get_notifications(user, last_id):
|
||||||
|
# return Notification.objects.filter(
|
||||||
|
# user=user, id__gt=last_id, is_read=False
|
||||||
|
# ).order_by("created")
|
||||||
|
|
||||||
|
# class NotificationSSEApp:
|
||||||
|
# async def __call__(self, scope, receive, send):
|
||||||
|
# if scope["type"] != "http":
|
||||||
|
# return
|
||||||
|
|
||||||
|
# query_string = parse_qs(scope["query_string"].decode())
|
||||||
|
# last_id = int(query_string.get("last_id", [0])[0])
|
||||||
|
|
||||||
|
# # Get user from scope if using AuthMiddlewareStack
|
||||||
|
# user = scope.get("user", AnonymousUser())
|
||||||
|
# if not user.is_authenticated:
|
||||||
|
# await send({
|
||||||
|
# "type": "http.response.start",
|
||||||
|
# "status": 403,
|
||||||
|
# "headers": [(b"content-type", b"text/plain")],
|
||||||
|
# })
|
||||||
|
# await send({
|
||||||
|
# "type": "http.response.body",
|
||||||
|
# "body": b"Unauthorized",
|
||||||
|
# })
|
||||||
|
# return
|
||||||
|
|
||||||
|
# await send({
|
||||||
|
# "type": "http.response.start",
|
||||||
|
# "status": 200,
|
||||||
|
# "headers": [
|
||||||
|
# (b"content-type", b"text/event-stream"),
|
||||||
|
# (b"cache-control", b"no-cache"),
|
||||||
|
# (b"x-accel-buffering", b"no"),
|
||||||
|
# ]
|
||||||
|
# })
|
||||||
|
|
||||||
|
# try:
|
||||||
|
# while True:
|
||||||
|
# close_old_connections()
|
||||||
|
|
||||||
|
# notifications = await get_notifications(user, last_id)
|
||||||
|
# for notification in notifications:
|
||||||
|
# data = {
|
||||||
|
# "id": notification.id,
|
||||||
|
# "message": notification.message,
|
||||||
|
# "created": notification.created.isoformat(),
|
||||||
|
# "is_read": notification.is_read,
|
||||||
|
# }
|
||||||
|
|
||||||
|
# event_str = (
|
||||||
|
# f"id: {notification.id}\n"
|
||||||
|
# f"event: notification\n"
|
||||||
|
# f"data: {json.dumps(data)}\n\n"
|
||||||
|
# )
|
||||||
|
|
||||||
|
# await send({
|
||||||
|
# "type": "http.response.body",
|
||||||
|
# "body": event_str.encode("utf-8"),
|
||||||
|
# "more_body": True
|
||||||
|
# })
|
||||||
|
|
||||||
|
# last_id = notification.id
|
||||||
|
|
||||||
|
# await asyncio.sleep(2)
|
||||||
|
|
||||||
|
# except asyncio.CancelledError:
|
||||||
|
# pass
|
||||||
|
|
||||||
|
import json
|
||||||
|
import time
|
||||||
|
from django.contrib.auth.models import AnonymousUser
|
||||||
|
from urllib.parse import parse_qs
|
||||||
|
from channels.db import database_sync_to_async
|
||||||
|
from django.contrib.auth import get_user_model
|
||||||
|
from inventory.models import Notification
|
||||||
|
import asyncio
|
||||||
|
from datetime import datetime
|
||||||
|
|
||||||
|
@database_sync_to_async
|
||||||
|
def get_user(user_id):
|
||||||
|
User = get_user_model()
|
||||||
|
try:
|
||||||
|
return User.objects.get(id=user_id)
|
||||||
|
except User.DoesNotExist:
|
||||||
|
return AnonymousUser()
|
||||||
|
|
||||||
|
@database_sync_to_async
|
||||||
|
def get_notifications(user, last_id):
|
||||||
|
notifications = Notification.objects.filter(
|
||||||
|
user=user,
|
||||||
|
id__gt=last_id,
|
||||||
|
is_read=False
|
||||||
|
).order_by("created")
|
||||||
|
|
||||||
|
return [
|
||||||
|
{
|
||||||
|
'id': n.id,
|
||||||
|
'message': n.message,
|
||||||
|
'created': n.created.isoformat(), # Convert datetime to string
|
||||||
|
'is_read': n.is_read
|
||||||
|
}
|
||||||
|
for n in notifications
|
||||||
|
]
|
||||||
|
|
||||||
|
class NotificationSSEApp:
|
||||||
|
async def __call__(self, scope, receive, send):
|
||||||
|
if scope["type"] != "http":
|
||||||
|
return
|
||||||
|
|
||||||
|
# Parse query parameters
|
||||||
|
query_string = parse_qs(scope["query_string"].decode())
|
||||||
|
last_id = int(query_string.get("last_id", [0])[0])
|
||||||
|
|
||||||
|
# Get user from scope
|
||||||
|
user = scope.get("user")
|
||||||
|
if not user or user.is_anonymous:
|
||||||
|
await self._send_response(send, 403, b"Unauthorized")
|
||||||
|
return
|
||||||
|
|
||||||
|
# Send SSE headers
|
||||||
|
await self._send_headers(send)
|
||||||
|
|
||||||
|
try:
|
||||||
|
while True:
|
||||||
|
try:
|
||||||
|
message = await asyncio.wait_for(receive(), timeout=3)
|
||||||
|
if message["type"] == "http.disconnect":
|
||||||
|
print("🔌 Client disconnected")
|
||||||
|
break
|
||||||
|
except asyncio.TimeoutError:
|
||||||
|
notifications = await get_notifications(user, last_id)
|
||||||
|
|
||||||
|
for notification in notifications:
|
||||||
|
await self._send_notification(send, notification)
|
||||||
|
if notification['id'] > last_id:
|
||||||
|
last_id = notification['id']
|
||||||
|
|
||||||
|
# Send keep-alive comment every 15 seconds
|
||||||
|
await send({
|
||||||
|
"type": "http.response.body",
|
||||||
|
"body": b":keep-alive\n\n",
|
||||||
|
"more_body": True
|
||||||
|
})
|
||||||
|
|
||||||
|
# await asyncio.sleep(3)
|
||||||
|
|
||||||
|
except (asyncio.CancelledError, ConnectionResetError):
|
||||||
|
pass
|
||||||
|
finally:
|
||||||
|
await self._close_connection(send)
|
||||||
|
|
||||||
|
async def _send_headers(self, send):
|
||||||
|
await send({
|
||||||
|
"type": "http.response.start",
|
||||||
|
"status": 200,
|
||||||
|
"headers": [
|
||||||
|
(b"content-type", b"text/event-stream"),
|
||||||
|
(b"cache-control", b"no-cache"),
|
||||||
|
(b"connection", b"keep-alive"),
|
||||||
|
(b"x-accel-buffering", b"no"),
|
||||||
|
]
|
||||||
|
})
|
||||||
|
|
||||||
|
async def _send_notification(self, send, notification):
|
||||||
|
try:
|
||||||
|
event_str = (
|
||||||
|
f"id: {notification['id']}\n"
|
||||||
|
f"event: notification\n"
|
||||||
|
f"data: {json.dumps(notification)}\n\n"
|
||||||
|
)
|
||||||
|
await send({
|
||||||
|
"type": "http.response.body",
|
||||||
|
"body": event_str.encode("utf-8"),
|
||||||
|
"more_body": True
|
||||||
|
})
|
||||||
|
except Exception as e:
|
||||||
|
print(f"Error sending notification: {e}")
|
||||||
|
|
||||||
|
async def _send_response(self, send, status, body):
|
||||||
|
await send({
|
||||||
|
"type": "http.response.start",
|
||||||
|
"status": status,
|
||||||
|
"headers": [(b"content-type", b"text/plain")]
|
||||||
|
})
|
||||||
|
await send({
|
||||||
|
"type": "http.response.body",
|
||||||
|
"body": body
|
||||||
|
})
|
||||||
|
|
||||||
|
async def _close_connection(self, send):
|
||||||
|
await send({
|
||||||
|
"type": "http.response.body",
|
||||||
|
"body": b""
|
||||||
|
})
|
||||||
@ -1,7 +1,8 @@
|
|||||||
|
from datetime import timezone
|
||||||
import logging
|
import logging
|
||||||
from .models import Dealer
|
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 (
|
||||||
BillModelCreateForm,
|
BillModelCreateForm,
|
||||||
BaseBillModelUpdateForm,
|
BaseBillModelUpdateForm,
|
||||||
@ -11,7 +12,7 @@ from django_ledger.forms.bill import (
|
|||||||
InReviewBillModelUpdateForm,
|
InReviewBillModelUpdateForm,
|
||||||
ApprovedBillModelUpdateForm,
|
ApprovedBillModelUpdateForm,
|
||||||
AccruedAndApprovedBillModelUpdateForm,
|
AccruedAndApprovedBillModelUpdateForm,
|
||||||
PaidBillModelUpdateForm
|
PaidBillModelUpdateForm,
|
||||||
)
|
)
|
||||||
from django.http import HttpResponseForbidden
|
from django.http import HttpResponseForbidden
|
||||||
from django.utils.html import format_html
|
from django.utils.html import format_html
|
||||||
@ -19,37 +20,43 @@ from django.contrib import messages
|
|||||||
from django.http import HttpResponseRedirect
|
from django.http import HttpResponseRedirect
|
||||||
from django.shortcuts import get_object_or_404
|
from django.shortcuts import get_object_or_404
|
||||||
from django.urls import reverse
|
from django.urls import reverse
|
||||||
from django_ledger.models import ItemTransactionModel
|
from django_ledger.models import ItemTransactionModel,InvoiceModel,LedgerModel,EntityModel
|
||||||
from django.views.generic.detail import DetailView
|
from django.views.generic.detail import DetailView
|
||||||
from django_ledger.forms.purchase_order import (ApprovedPurchaseOrderModelUpdateForm,
|
from django_ledger.forms.purchase_order import (
|
||||||
BasePurchaseOrderModelUpdateForm,
|
ApprovedPurchaseOrderModelUpdateForm,
|
||||||
DraftPurchaseOrderModelUpdateForm,
|
BasePurchaseOrderModelUpdateForm,
|
||||||
ReviewPurchaseOrderModelUpdateForm,
|
DraftPurchaseOrderModelUpdateForm,
|
||||||
get_po_itemtxs_formset_class)
|
ReviewPurchaseOrderModelUpdateForm,
|
||||||
|
get_po_itemtxs_formset_class,
|
||||||
|
)
|
||||||
from django_ledger.views.purchase_order import PurchaseOrderModelModelViewQuerySetMixIn
|
from django_ledger.views.purchase_order import PurchaseOrderModelModelViewQuerySetMixIn
|
||||||
from django_ledger.models import PurchaseOrderModel,EstimateModel,BillModel
|
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 django.views.generic.list import ListView
|
from django.views.generic.list import ListView
|
||||||
from django.utils.translation import gettext_lazy as _
|
from django.utils.translation import gettext_lazy as _
|
||||||
|
from django_ledger.forms.invoice import (BaseInvoiceModelUpdateForm, InvoiceModelCreateForEstimateForm,
|
||||||
|
get_invoice_itemtxs_formset_class,
|
||||||
|
DraftInvoiceModelUpdateForm, InReviewInvoiceModelUpdateForm,
|
||||||
|
ApprovedInvoiceModelUpdateForm, PaidInvoiceModelUpdateForm,
|
||||||
|
AccruedAndApprovedInvoiceModelUpdateForm, InvoiceModelCreateForm)
|
||||||
|
|
||||||
|
|
||||||
logger = logging.getLogger(__name__)
|
logger = logging.getLogger(__name__)
|
||||||
logging.basicConfig(level=logging.INFO)
|
logging.basicConfig(level=logging.INFO)
|
||||||
|
|
||||||
class PurchaseOrderModelUpdateView(LoginRequiredMixin,
|
|
||||||
PermissionRequiredMixin,
|
class PurchaseOrderModelUpdateView(
|
||||||
UpdateView):
|
LoginRequiredMixin, PermissionRequiredMixin, UpdateView
|
||||||
slug_url_kwarg = 'po_pk'
|
):
|
||||||
slug_field = 'uuid'
|
slug_url_kwarg = "po_pk"
|
||||||
context_object_name = 'po_model'
|
slug_field = "uuid"
|
||||||
|
context_object_name = "po_model"
|
||||||
template_name = "purchase_orders/po_update.html"
|
template_name = "purchase_orders/po_update.html"
|
||||||
context_object_name = "po_model"
|
context_object_name = "po_model"
|
||||||
permission_required = "django_ledger.change_purchaseordermodel"
|
permission_required = "django_ledger.change_purchaseordermodel"
|
||||||
extra_context = {
|
extra_context = {"header_subtitle_icon": "uil:bill"}
|
||||||
'header_subtitle_icon': 'uil:bill'
|
|
||||||
}
|
|
||||||
action_update_items = False
|
action_update_items = False
|
||||||
queryset = None
|
queryset = None
|
||||||
|
|
||||||
@ -79,10 +86,10 @@ class PurchaseOrderModelUpdateView(LoginRequiredMixin,
|
|||||||
dealer = get_object_or_404(Dealer, slug=self.kwargs["dealer_slug"])
|
dealer = get_object_or_404(Dealer, slug=self.kwargs["dealer_slug"])
|
||||||
if self.queryset is None:
|
if self.queryset is None:
|
||||||
self.queryset = PurchaseOrderModel.objects.for_entity(
|
self.queryset = PurchaseOrderModel.objects.for_entity(
|
||||||
entity_slug=self.kwargs['entity_slug'],
|
entity_slug=self.kwargs["entity_slug"], user_model=dealer.entity.admin
|
||||||
user_model=dealer.entity.admin
|
).select_related("entity", "ce_model")
|
||||||
).select_related('entity', 'ce_model')
|
|
||||||
return super().get_queryset()
|
return super().get_queryset()
|
||||||
|
|
||||||
def get_success_url(self):
|
def get_success_url(self):
|
||||||
return reverse(
|
return reverse(
|
||||||
"purchase_order_update",
|
"purchase_order_update",
|
||||||
@ -92,6 +99,7 @@ class PurchaseOrderModelUpdateView(LoginRequiredMixin,
|
|||||||
"po_pk": self.kwargs["po_pk"],
|
"po_pk": self.kwargs["po_pk"],
|
||||||
},
|
},
|
||||||
)
|
)
|
||||||
|
|
||||||
def get(self, request, dealer_slug, entity_slug, po_pk, *args, **kwargs):
|
def get(self, request, dealer_slug, entity_slug, po_pk, *args, **kwargs):
|
||||||
if self.action_update_items:
|
if self.action_update_items:
|
||||||
return HttpResponseRedirect(
|
return HttpResponseRedirect(
|
||||||
@ -106,7 +114,7 @@ class PurchaseOrderModelUpdateView(LoginRequiredMixin,
|
|||||||
)
|
)
|
||||||
return super(PurchaseOrderModelUpdateView, self).get(
|
return super(PurchaseOrderModelUpdateView, self).get(
|
||||||
request, dealer_slug, entity_slug, po_pk, *args, **kwargs
|
request, dealer_slug, entity_slug, po_pk, *args, **kwargs
|
||||||
)
|
)
|
||||||
|
|
||||||
def post(self, request, dealer_slug, entity_slug, *args, **kwargs):
|
def post(self, request, dealer_slug, entity_slug, *args, **kwargs):
|
||||||
if self.action_update_items:
|
if self.action_update_items:
|
||||||
@ -199,78 +207,87 @@ class PurchaseOrderModelUpdateView(LoginRequiredMixin,
|
|||||||
def get_form_kwargs(self):
|
def get_form_kwargs(self):
|
||||||
if self.action_update_items:
|
if self.action_update_items:
|
||||||
return {
|
return {
|
||||||
'initial': self.get_initial(),
|
"initial": self.get_initial(),
|
||||||
'prefix': self.get_prefix(),
|
"prefix": self.get_prefix(),
|
||||||
'instance': self.object
|
"instance": self.object,
|
||||||
}
|
}
|
||||||
return super(PurchaseOrderModelUpdateView, self).get_form_kwargs()
|
return super(PurchaseOrderModelUpdateView, self).get_form_kwargs()
|
||||||
|
|
||||||
|
|
||||||
def get_po_itemtxs_qs(self, po_model: PurchaseOrderModel):
|
def get_po_itemtxs_qs(self, po_model: PurchaseOrderModel):
|
||||||
return po_model.itemtransactionmodel_set.select_related('bill_model', 'po_model').order_by('created')
|
return po_model.itemtransactionmodel_set.select_related(
|
||||||
|
"bill_model", "po_model"
|
||||||
|
).order_by("created")
|
||||||
|
|
||||||
def form_valid(self, form: BasePurchaseOrderModelUpdateForm):
|
def form_valid(self, form: BasePurchaseOrderModelUpdateForm):
|
||||||
po_model: PurchaseOrderModel = form.save(commit=False)
|
po_model: PurchaseOrderModel = form.save(commit=False)
|
||||||
|
|
||||||
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=self.request.admin,
|
user_model=self.request.admin,
|
||||||
po_pk=po_model.uuid,
|
po_pk=po_model.uuid,
|
||||||
).select_related('bill_model')
|
).select_related("bill_model")
|
||||||
|
|
||||||
if all(['po_status' in form.changed_data,
|
if all(
|
||||||
po_model.po_status == po_model.PO_STATUS_APPROVED]):
|
[
|
||||||
po_items_qs.update(po_item_status=ItemTransactionModel.STATUS_NOT_ORDERED)
|
"po_status" in form.changed_data,
|
||||||
|
po_model.po_status == po_model.PO_STATUS_APPROVED,
|
||||||
if 'fulfilled' in form.changed_data:
|
]
|
||||||
|
):
|
||||||
|
po_items_qs.update(
|
||||||
|
po_item_status=ItemTransactionModel.STATUS_NOT_ORDERED
|
||||||
|
)
|
||||||
|
|
||||||
|
if "fulfilled" in form.changed_data:
|
||||||
if not all([i.bill_model for i in po_items_qs]):
|
if not all([i.bill_model for i in po_items_qs]):
|
||||||
messages.add_message(self.request,
|
messages.add_message(
|
||||||
messages.ERROR,
|
self.request,
|
||||||
f'All PO items must be billed before marking'
|
messages.ERROR,
|
||||||
f' PO: {po_model.po_number} as fulfilled.',
|
f"All PO items must be billed before marking"
|
||||||
extra_tags='is-danger')
|
f" PO: {po_model.po_number} as fulfilled.",
|
||||||
|
extra_tags="is-danger",
|
||||||
|
)
|
||||||
return self.get(self.request)
|
return self.get(self.request)
|
||||||
|
|
||||||
else:
|
else:
|
||||||
if not all([i.bill_model.is_paid() for i in po_items_qs]):
|
if not all([i.bill_model.is_paid() for i in po_items_qs]):
|
||||||
messages.add_message(self.request,
|
messages.add_message(
|
||||||
messages.SUCCESS,
|
self.request,
|
||||||
f'All bills must be paid before marking'
|
messages.SUCCESS,
|
||||||
f' PO: {po_model.po_number} as fulfilled.',
|
f"All bills must be paid before marking"
|
||||||
extra_tags='is-success')
|
f" PO: {po_model.po_number} as fulfilled.",
|
||||||
|
extra_tags="is-success",
|
||||||
|
)
|
||||||
return self.get(self.request)
|
return self.get(self.request)
|
||||||
|
|
||||||
po_items_qs.update(po_item_status=ItemTransactionModel.STATUS_RECEIVED)
|
po_items_qs.update(po_item_status=ItemTransactionModel.STATUS_RECEIVED)
|
||||||
|
|
||||||
messages.add_message(self.request,
|
messages.add_message(
|
||||||
messages.SUCCESS,
|
self.request,
|
||||||
f'{self.object.po_number} successfully updated.',
|
messages.SUCCESS,
|
||||||
extra_tags='is-success')
|
f"{self.object.po_number} successfully updated.",
|
||||||
|
extra_tags="is-success",
|
||||||
|
)
|
||||||
|
|
||||||
return super().form_valid(form)
|
return super().form_valid(form)
|
||||||
|
|
||||||
|
|
||||||
|
class BasePurchaseOrderActionActionView(
|
||||||
class BasePurchaseOrderActionActionView(LoginRequiredMixin,
|
LoginRequiredMixin, PermissionRequiredMixin, RedirectView, SingleObjectMixin
|
||||||
PermissionRequiredMixin,
|
):
|
||||||
RedirectView,
|
http_method_names = ["get"]
|
||||||
SingleObjectMixin):
|
pk_url_kwarg = "po_pk"
|
||||||
http_method_names = ['get']
|
|
||||||
pk_url_kwarg = 'po_pk'
|
|
||||||
action_name = None
|
action_name = None
|
||||||
commit = True
|
commit = True
|
||||||
permission_required = None
|
permission_required = None
|
||||||
queryset = None
|
queryset = None
|
||||||
|
|
||||||
def get_queryset(self):
|
def get_queryset(self):
|
||||||
dealer = get_object_or_404(Dealer, slug=self.kwargs['dealer_slug'])
|
dealer = get_object_or_404(Dealer, slug=self.kwargs["dealer_slug"])
|
||||||
if self.queryset is None:
|
if self.queryset is None:
|
||||||
self.queryset = PurchaseOrderModel.objects.for_entity(
|
self.queryset = PurchaseOrderModel.objects.for_entity(
|
||||||
entity_slug=self.kwargs['entity_slug'],
|
entity_slug=self.kwargs["entity_slug"], user_model=dealer.entity.admin
|
||||||
user_model=dealer.entity.admin
|
).select_related("entity", "ce_model")
|
||||||
).select_related('entity', 'ce_model')
|
|
||||||
return super().get_queryset()
|
return super().get_queryset()
|
||||||
|
|
||||||
def get_redirect_url(self, dealer_slug, entity_slug, po_pk, *args, **kwargs):
|
def get_redirect_url(self, dealer_slug, entity_slug, po_pk, *args, **kwargs):
|
||||||
@ -286,7 +303,9 @@ class BasePurchaseOrderActionActionView(LoginRequiredMixin,
|
|||||||
def get(self, request, dealer_slug, entity_slug, po_pk, *args, **kwargs):
|
def get(self, request, dealer_slug, entity_slug, po_pk, *args, **kwargs):
|
||||||
# kwargs["user_model"] = dealer.entity.admin
|
# kwargs["user_model"] = dealer.entity.admin
|
||||||
# Get user information for logging
|
# Get user information for logging
|
||||||
user_username = request.user.username if request.user.is_authenticated else 'anonymous'
|
user_username = (
|
||||||
|
request.user.username if request.user.is_authenticated else "anonymous"
|
||||||
|
)
|
||||||
|
|
||||||
dealer = get_object_or_404(Dealer, slug=dealer_slug)
|
dealer = get_object_or_404(Dealer, slug=dealer_slug)
|
||||||
kwargs["user_model"] = dealer.entity.admin
|
kwargs["user_model"] = dealer.entity.admin
|
||||||
@ -297,86 +316,123 @@ class BasePurchaseOrderActionActionView(LoginRequiredMixin,
|
|||||||
)
|
)
|
||||||
po_model: PurchaseOrderModel = self.get_object()
|
po_model: PurchaseOrderModel = self.get_object()
|
||||||
|
|
||||||
# Log the attempt to perform the action
|
# Log the attempt to perform the action
|
||||||
logger.debug(
|
logger.debug(
|
||||||
f"User {user_username} attempting to call action '{self.action_name}' "
|
f"User {user_username} attempting to call action '{self.action_name}' "
|
||||||
f"on Purchase Order ID: {po_model.pk} (Entity: {entity_slug})."
|
f"on Purchase Order ID: {po_model.pk} (Entity: {entity_slug})."
|
||||||
)
|
)
|
||||||
try:
|
print(self.action_name)
|
||||||
getattr(po_model, self.action_name)(commit=self.commit, **kwargs)
|
if self.action_name == "mark_as_fulfilled":
|
||||||
# --- Single-line log for successful action ---
|
try:
|
||||||
logger.info(
|
if po_model.can_fulfill():
|
||||||
f"User {user_username} successfully executed action '{self.action_name}' "
|
po_model.mark_as_fulfilled()
|
||||||
f"on Purchase Order ID: {po_model.pk}."
|
# po_model.date_fulfilled = timezone.now()
|
||||||
)
|
po_model.save()
|
||||||
messages.add_message(
|
messages.add_message(
|
||||||
request,
|
request,
|
||||||
message="PO updated successfully.",
|
message="PO marked as fulfilled successfully.",
|
||||||
level=messages.SUCCESS,
|
level=messages.SUCCESS,
|
||||||
)
|
)
|
||||||
except ValidationError as e:
|
logger.info(
|
||||||
# --- Single-line log for ValidationError ---
|
f"User {user_username} successfully executed action '{self.action_name}' "
|
||||||
print(f"User {user_username} encountered a validation error "
|
f"on Purchase Order ID: {po_model.pk}."
|
||||||
f"while performing action '{self.action_name}' on Purchase Order ID: {po_model.pk}. "
|
)
|
||||||
f"Error: {e}")
|
except Exception as e:
|
||||||
logger.warning(
|
messages.add_message(
|
||||||
f"User {user_username} encountered a validation error "
|
request,
|
||||||
f"while performing action '{self.action_name}' on Purchase Order ID: {po_model.pk}. "
|
message=f"Failed to mark PO {po_model.po_number} as fulfilled. {e}",
|
||||||
f"Error: {e}"
|
level=messages.ERROR,
|
||||||
)
|
)
|
||||||
except AttributeError as e:
|
logger.warning(
|
||||||
print(f"User {user_username} encountered an AttributeError "
|
f"User {user_username} encountered an exception "
|
||||||
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}"
|
||||||
logger.warning(
|
)
|
||||||
f"User {user_username} encountered an AttributeError "
|
else:
|
||||||
f"while performing action '{self.action_name}' on Purchase Order ID: {po_model.pk}. "
|
try:
|
||||||
f"Error: {e}"
|
getattr(po_model, self.action_name)(commit=self.commit, **kwargs)
|
||||||
)
|
logger.info(
|
||||||
|
f"User {user_username} successfully executed action '{self.action_name}' "
|
||||||
|
f"on Purchase Order ID: {po_model.pk}."
|
||||||
|
)
|
||||||
|
messages.add_message(
|
||||||
|
request,
|
||||||
|
message="PO updated successfully.",
|
||||||
|
level=messages.SUCCESS,
|
||||||
|
)
|
||||||
|
except ValidationError as e:
|
||||||
|
# --- Single-line log for ValidationError ---
|
||||||
|
print(
|
||||||
|
f"User {user_username} encountered a validation error "
|
||||||
|
f"while performing action '{self.action_name}' on Purchase Order ID: {po_model.pk}. "
|
||||||
|
f"Error: {e}"
|
||||||
|
)
|
||||||
|
logger.warning(
|
||||||
|
f"User {user_username} encountered a validation error "
|
||||||
|
f"while performing action '{self.action_name}' on Purchase Order ID: {po_model.pk}. "
|
||||||
|
f"Error: {e}"
|
||||||
|
)
|
||||||
|
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):
|
||||||
slug_url_kwarg = 'bill_pk'
|
slug_url_kwarg = "bill_pk"
|
||||||
slug_field = 'uuid'
|
slug_field = "uuid"
|
||||||
context_object_name = 'bill'
|
context_object_name = "bill"
|
||||||
template_name = "bill/bill_detail.html"
|
template_name = "bill/bill_detail.html"
|
||||||
extra_context = {
|
extra_context = {"header_subtitle_icon": "uil:bill", "hide_menu": True}
|
||||||
'header_subtitle_icon': 'uil:bill',
|
|
||||||
'hide_menu': True
|
|
||||||
}
|
|
||||||
|
|
||||||
def get_context_data(self, *, object_list=None, **kwargs):
|
def get_context_data(self, *, object_list=None, **kwargs):
|
||||||
context = super().get_context_data(object_list=object_list, **kwargs)
|
context = super().get_context_data(object_list=object_list, **kwargs)
|
||||||
context["dealer"] = self.request.dealer
|
context["dealer"] = self.request.dealer
|
||||||
bill_model: BillModel = self.object
|
bill_model: BillModel = self.object
|
||||||
title = f'Bill {bill_model.bill_number}'
|
title = f"Bill {bill_model.bill_number}"
|
||||||
context['page_title'] = title
|
context["page_title"] = title
|
||||||
context['header_title'] = title
|
context["header_title"] = title
|
||||||
|
|
||||||
bill_model: BillModel = self.object
|
bill_model: BillModel = self.object
|
||||||
bill_items_qs, item_data = bill_model.get_itemtxs_data()
|
bill_items_qs, item_data = bill_model.get_itemtxs_data()
|
||||||
context['itemtxs_qs'] = bill_items_qs
|
context["itemtxs_qs"] = bill_items_qs
|
||||||
context['total_amount__sum'] = item_data['total_amount__sum']
|
context["total_amount__sum"] = item_data["total_amount__sum"]
|
||||||
|
|
||||||
if not bill_model.is_configured():
|
if not bill_model.is_configured():
|
||||||
link = format_html(f"""
|
link = format_html(f"""
|
||||||
<a href="{reverse("bill-update", kwargs={
|
<a href="{
|
||||||
'dealer_slug': self.kwargs['dealer_slug'],
|
reverse(
|
||||||
'entity_slug': self.kwargs['entity_slug'],
|
"bill-update",
|
||||||
'bill_pk': bill_model.uuid
|
kwargs={
|
||||||
})}">here</a>
|
"dealer_slug": self.kwargs["dealer_slug"],
|
||||||
|
"entity_slug": self.kwargs["entity_slug"],
|
||||||
|
"bill_pk": bill_model.uuid,
|
||||||
|
},
|
||||||
|
)
|
||||||
|
}">here</a>
|
||||||
""")
|
""")
|
||||||
msg = f'Bill {bill_model.bill_number} has not been fully set up. ' + \
|
msg = (
|
||||||
f'Please update or assign associated accounts {link}.'
|
f"Bill {bill_model.bill_number} has not been fully set up. "
|
||||||
messages.add_message(self.request,
|
+ f"Please update or assign associated accounts {link}."
|
||||||
message=msg,
|
)
|
||||||
level=messages.WARNING,
|
messages.add_message(
|
||||||
extra_tags='is-danger')
|
self.request,
|
||||||
|
message=msg,
|
||||||
|
level=messages.WARNING,
|
||||||
|
extra_tags="is-danger",
|
||||||
|
)
|
||||||
return context
|
return context
|
||||||
|
|
||||||
|
|
||||||
def get_queryset(self):
|
def get_queryset(self):
|
||||||
dealer = get_object_or_404(Dealer,slug=self.kwargs['dealer_slug'])
|
dealer = get_object_or_404(Dealer, slug=self.kwargs["dealer_slug"])
|
||||||
if self.queryset is None:
|
if self.queryset is None:
|
||||||
entity_model = dealer.entity
|
entity_model = dealer.entity
|
||||||
qs = entity_model.get_bills()
|
qs = entity_model.get_bills()
|
||||||
@ -385,54 +441,59 @@ class BillModelDetailView(LoginRequiredMixin, PermissionRequiredMixin, DetailVie
|
|||||||
|
|
||||||
|
|
||||||
######################################################3
|
######################################################3
|
||||||
#BILL
|
# BILL
|
||||||
|
|
||||||
|
|
||||||
class BillModelUpdateView(LoginRequiredMixin, PermissionRequiredMixin, UpdateView):
|
class BillModelUpdateView(LoginRequiredMixin, PermissionRequiredMixin, UpdateView):
|
||||||
slug_url_kwarg = 'bill_pk'
|
slug_url_kwarg = "bill_pk"
|
||||||
slug_field = 'uuid'
|
slug_field = "uuid"
|
||||||
context_object_name = 'bill_model'
|
context_object_name = "bill_model"
|
||||||
template_name = "bill/bill_update.html"
|
template_name = "bill/bill_update.html"
|
||||||
extra_context = {
|
extra_context = {"header_subtitle_icon": "uil:bill"}
|
||||||
'header_subtitle_icon': 'uil:bill'
|
http_method_names = ["get", "post"]
|
||||||
}
|
|
||||||
http_method_names = ['get', 'post']
|
|
||||||
action_update_items = False
|
action_update_items = False
|
||||||
queryset = None
|
queryset = None
|
||||||
|
|
||||||
def get_queryset(self):
|
def get_queryset(self):
|
||||||
dealer = get_object_or_404(Dealer,slug=self.kwargs['dealer_slug'])
|
dealer = get_object_or_404(Dealer, slug=self.kwargs["dealer_slug"])
|
||||||
if self.queryset is None:
|
if self.queryset is None:
|
||||||
entity_model = dealer.entity
|
entity_model = dealer.entity
|
||||||
qs = entity_model.get_bills()
|
qs = entity_model.get_bills()
|
||||||
self.queryset = qs
|
self.queryset = qs
|
||||||
return super().get_queryset().select_related(
|
return (
|
||||||
'ledger',
|
super()
|
||||||
'ledger__entity',
|
.get_queryset()
|
||||||
'vendor',
|
.select_related(
|
||||||
'cash_account',
|
"ledger",
|
||||||
'prepaid_account',
|
"ledger__entity",
|
||||||
'unearned_account',
|
"vendor",
|
||||||
'cash_account__coa_model',
|
"cash_account",
|
||||||
'prepaid_account__coa_model',
|
"prepaid_account",
|
||||||
'unearned_account__coa_model'
|
"unearned_account",
|
||||||
|
"cash_account__coa_model",
|
||||||
|
"prepaid_account__coa_model",
|
||||||
|
"unearned_account__coa_model",
|
||||||
|
)
|
||||||
)
|
)
|
||||||
|
|
||||||
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()
|
||||||
entity_model = self.request.dealer.entity
|
entity_model = self.request.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=self.request.admin,
|
user_model=self.request.admin,
|
||||||
instance=self.object
|
instance=self.object,
|
||||||
)
|
)
|
||||||
form = form_class(
|
form = form_class(
|
||||||
entity_model=entity_model,
|
entity_model=entity_model,
|
||||||
user_model=self.request.admin,
|
user_model=self.request.admin,
|
||||||
**self.get_form_kwargs()
|
**self.get_form_kwargs(),
|
||||||
)
|
)
|
||||||
try:
|
try:
|
||||||
form.initial['amount_paid'] = self.object.get_itemtxs_data()[1]["total_amount__sum"]
|
form.initial["amount_paid"] = self.object.get_itemtxs_data()[1][
|
||||||
|
"total_amount__sum"
|
||||||
|
]
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
print(e)
|
print(e)
|
||||||
return form
|
return form
|
||||||
@ -453,54 +514,57 @@ class BillModelUpdateView(LoginRequiredMixin, PermissionRequiredMixin, UpdateVie
|
|||||||
return PaidBillModelUpdateForm
|
return PaidBillModelUpdateForm
|
||||||
return BaseBillModelUpdateForm
|
return BaseBillModelUpdateForm
|
||||||
|
|
||||||
def get_context_data(self,
|
def get_context_data(self, *, object_list=None, itemtxs_formset=None, **kwargs):
|
||||||
*,
|
|
||||||
object_list=None,
|
|
||||||
itemtxs_formset=None,
|
|
||||||
**kwargs):
|
|
||||||
|
|
||||||
context = super().get_context_data(object_list=object_list, **kwargs)
|
context = super().get_context_data(object_list=object_list, **kwargs)
|
||||||
dealer = get_object_or_404(Dealer,slug=self.kwargs['dealer_slug'])
|
dealer = get_object_or_404(Dealer, slug=self.kwargs["dealer_slug"])
|
||||||
entity_model = dealer.entity
|
entity_model = dealer.entity
|
||||||
bill_model: BillModel = self.object
|
bill_model: BillModel = self.object
|
||||||
ledger_model = bill_model.ledger
|
ledger_model = bill_model.ledger
|
||||||
|
|
||||||
title = f'Bill {bill_model.bill_number}'
|
title = f"Bill {bill_model.bill_number}"
|
||||||
context['page_title'] = title
|
context["page_title"] = title
|
||||||
context['header_title'] = title
|
context["header_title"] = title
|
||||||
context['header_subtitle'] = bill_model.get_bill_status_display()
|
context["header_subtitle"] = bill_model.get_bill_status_display()
|
||||||
|
|
||||||
if not bill_model.is_configured():
|
if not bill_model.is_configured():
|
||||||
messages.add_message(
|
messages.add_message(
|
||||||
request=self.request,
|
request=self.request,
|
||||||
message=f'Bill {bill_model.bill_number} must have all accounts configured.',
|
message=f"Bill {bill_model.bill_number} must have all accounts configured.",
|
||||||
level=messages.ERROR,
|
level=messages.ERROR,
|
||||||
extra_tags='is-danger'
|
extra_tags="is-danger",
|
||||||
)
|
)
|
||||||
|
|
||||||
if not bill_model.is_paid():
|
if not bill_model.is_paid():
|
||||||
if ledger_model.locked:
|
if ledger_model.locked:
|
||||||
messages.add_message(self.request,
|
messages.add_message(
|
||||||
messages.ERROR,
|
self.request,
|
||||||
f'Warning! This bill is locked. Must unlock before making any changes.',
|
messages.ERROR,
|
||||||
extra_tags='is-danger')
|
f"Warning! This bill is locked. Must unlock before making any changes.",
|
||||||
|
extra_tags="is-danger",
|
||||||
|
)
|
||||||
|
|
||||||
if ledger_model.locked:
|
if ledger_model.locked:
|
||||||
messages.add_message(self.request,
|
messages.add_message(
|
||||||
messages.ERROR,
|
self.request,
|
||||||
f'Warning! This bill is locked. Must unlock before making any changes.',
|
messages.ERROR,
|
||||||
extra_tags='is-danger')
|
f"Warning! This bill is locked. Must unlock before making any changes.",
|
||||||
|
extra_tags="is-danger",
|
||||||
|
)
|
||||||
|
|
||||||
if not ledger_model.is_posted():
|
if not ledger_model.is_posted():
|
||||||
messages.add_message(self.request,
|
messages.add_message(
|
||||||
messages.INFO,
|
self.request,
|
||||||
f'This bill has not been posted. Must post to see ledger changes.',
|
messages.INFO,
|
||||||
extra_tags='is-info')
|
f"This bill has not been posted. Must post to see ledger changes.",
|
||||||
|
extra_tags="is-info",
|
||||||
|
)
|
||||||
|
|
||||||
itemtxs_qs = itemtxs_formset.get_queryset() if itemtxs_formset else None
|
itemtxs_qs = itemtxs_formset.get_queryset() if itemtxs_formset else None
|
||||||
if not itemtxs_formset:
|
if not itemtxs_formset:
|
||||||
itemtxs_formset_class = get_bill_itemtxs_formset_class(bill_model)
|
itemtxs_formset_class = get_bill_itemtxs_formset_class(bill_model)
|
||||||
itemtxs_formset = itemtxs_formset_class(entity_model=entity_model, bill_model=bill_model)
|
itemtxs_formset = itemtxs_formset_class(
|
||||||
|
entity_model=entity_model, bill_model=bill_model
|
||||||
|
)
|
||||||
itemtxs_qs, itemtxs_agg = bill_model.get_itemtxs_data(queryset=itemtxs_qs)
|
itemtxs_qs, itemtxs_agg = bill_model.get_itemtxs_data(queryset=itemtxs_qs)
|
||||||
|
|
||||||
has_po = any(i.po_model_id for i in itemtxs_qs)
|
has_po = any(i.po_model_id for i in itemtxs_qs)
|
||||||
@ -509,9 +573,9 @@ class BillModelUpdateView(LoginRequiredMixin, PermissionRequiredMixin, UpdateVie
|
|||||||
itemtxs_formset.can_delete = False
|
itemtxs_formset.can_delete = False
|
||||||
itemtxs_formset.has_po = has_po
|
itemtxs_formset.has_po = has_po
|
||||||
|
|
||||||
context['itemtxs_formset'] = itemtxs_formset
|
context["itemtxs_formset"] = itemtxs_formset
|
||||||
context['total_amount__sum'] = itemtxs_agg['total_amount__sum']
|
context["total_amount__sum"] = itemtxs_agg["total_amount__sum"]
|
||||||
context['has_po'] = has_po
|
context["has_po"] = has_po
|
||||||
return context
|
return context
|
||||||
|
|
||||||
def get_success_url(self):
|
def get_success_url(self):
|
||||||
@ -523,23 +587,28 @@ class BillModelUpdateView(LoginRequiredMixin, PermissionRequiredMixin, UpdateVie
|
|||||||
"bill_pk": self.kwargs["bill_pk"],
|
"bill_pk": self.kwargs["bill_pk"],
|
||||||
},
|
},
|
||||||
)
|
)
|
||||||
|
|
||||||
def form_valid(self, form):
|
def form_valid(self, form):
|
||||||
form.save(commit=False)
|
form.save(commit=False)
|
||||||
messages.add_message(self.request,
|
messages.add_message(
|
||||||
messages.SUCCESS,
|
self.request,
|
||||||
f'Bill {self.object.bill_number} successfully updated.',
|
messages.SUCCESS,
|
||||||
extra_tags='is-success')
|
f"Bill {self.object.bill_number} successfully updated.",
|
||||||
|
extra_tags="is-success",
|
||||||
|
)
|
||||||
return super().form_valid(form)
|
return super().form_valid(form)
|
||||||
|
|
||||||
def get(self, request,dealer_slug,entity_slug,bill_pk, *args, **kwargs):
|
def get(self, request, dealer_slug, entity_slug, bill_pk, *args, **kwargs):
|
||||||
if self.action_update_items:
|
if self.action_update_items:
|
||||||
return HttpResponseRedirect(
|
return HttpResponseRedirect(
|
||||||
redirect_to=reverse('bill-update',
|
redirect_to=reverse(
|
||||||
kwargs={
|
"bill-update",
|
||||||
'dealer_slug': dealer_slug,
|
kwargs={
|
||||||
'entity_slug': entity_slug,
|
"dealer_slug": dealer_slug,
|
||||||
'bill_pk': bill_pk
|
"entity_slug": entity_slug,
|
||||||
})
|
"bill_pk": bill_pk,
|
||||||
|
},
|
||||||
|
)
|
||||||
)
|
)
|
||||||
return super(BillModelUpdateView, self).get(request, *args, **kwargs)
|
return super(BillModelUpdateView, self).get(request, *args, **kwargs)
|
||||||
|
|
||||||
@ -614,10 +683,11 @@ class BillModelUpdateView(LoginRequiredMixin, PermissionRequiredMixin, UpdateVie
|
|||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
|
class BaseBillActionView(
|
||||||
class BaseBillActionView(LoginRequiredMixin,PermissionRequiredMixin, RedirectView, SingleObjectMixin):
|
LoginRequiredMixin, PermissionRequiredMixin, RedirectView, SingleObjectMixin
|
||||||
http_method_names = ['get']
|
):
|
||||||
pk_url_kwarg = 'bill_pk'
|
http_method_names = ["get"]
|
||||||
|
pk_url_kwarg = "bill_pk"
|
||||||
action_name = None
|
action_name = None
|
||||||
commit = True
|
commit = True
|
||||||
permission_required = "django_ledger.change_billmodel"
|
permission_required = "django_ledger.change_billmodel"
|
||||||
@ -631,7 +701,6 @@ class BaseBillActionView(LoginRequiredMixin,PermissionRequiredMixin, RedirectVie
|
|||||||
self.queryset = qs
|
self.queryset = qs
|
||||||
return super().get_queryset()
|
return super().get_queryset()
|
||||||
|
|
||||||
|
|
||||||
def get_redirect_url(self, dealer_slug, entity_slug, bill_pk, *args, **kwargs):
|
def get_redirect_url(self, dealer_slug, entity_slug, bill_pk, *args, **kwargs):
|
||||||
return reverse(
|
return reverse(
|
||||||
"bill-update",
|
"bill-update",
|
||||||
@ -642,58 +711,265 @@ class BaseBillActionView(LoginRequiredMixin,PermissionRequiredMixin, RedirectVie
|
|||||||
},
|
},
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
def get(self, request, *args, **kwargs):
|
def get(self, request, *args, **kwargs):
|
||||||
dealer = get_object_or_404(Dealer, slug=self.kwargs["dealer_slug"])
|
dealer = get_object_or_404(Dealer, slug=self.kwargs["dealer_slug"])
|
||||||
kwargs['user_model'] = dealer.entity.admin
|
kwargs["user_model"] = dealer.entity.admin
|
||||||
if not self.action_name:
|
if not self.action_name:
|
||||||
raise ImproperlyConfigured('View attribute action_name is required.')
|
raise ImproperlyConfigured("View attribute action_name is required.")
|
||||||
response = super(BaseBillActionView, self).get(request, *args, **kwargs)
|
response = super(BaseBillActionView, self).get(request, *args, **kwargs)
|
||||||
bill_model: BillModel = self.get_object()
|
bill_model: BillModel = self.get_object()
|
||||||
|
|
||||||
try:
|
try:
|
||||||
getattr(bill_model, self.action_name)(commit=self.commit, **kwargs)
|
getattr(bill_model, self.action_name)(commit=self.commit, **kwargs)
|
||||||
except ValidationError as e:
|
except ValidationError as e:
|
||||||
messages.add_message(request,
|
messages.add_message(
|
||||||
message=e.message,
|
request, message=e.message, level=messages.ERROR, extra_tags="is-danger"
|
||||||
level=messages.ERROR,
|
)
|
||||||
extra_tags='is-danger')
|
|
||||||
return response
|
return response
|
||||||
|
|
||||||
|
|
||||||
class InventoryListView(LoginRequiredMixin, PermissionRequiredMixin, ListView):
|
class InventoryListView(LoginRequiredMixin, PermissionRequiredMixin, ListView):
|
||||||
template_name = 'django_ledger/inventory/inventory_list.html'
|
template_name = "django_ledger/inventory/inventory_list.html"
|
||||||
context_object_name = 'inventory_list'
|
context_object_name = "inventory_list"
|
||||||
http_method_names = ['get']
|
http_method_names = ["get"]
|
||||||
|
|
||||||
def get_context_data(self, *, object_list=None, **kwargs):
|
def get_context_data(self, *, object_list=None, **kwargs):
|
||||||
context = super(InventoryListView, self).get_context_data(**kwargs)
|
context = super(InventoryListView, self).get_context_data(**kwargs)
|
||||||
qs = self.get_queryset()
|
qs = self.get_queryset()
|
||||||
|
|
||||||
# evaluates the queryset...
|
# evaluates the queryset...
|
||||||
context['qs_count'] = qs.count()
|
context["qs_count"] = qs.count()
|
||||||
|
|
||||||
# ordered inventory...
|
# ordered inventory...
|
||||||
ordered_qs = qs.is_ordered()
|
ordered_qs = qs.is_ordered()
|
||||||
context['inventory_ordered'] = ordered_qs
|
context["inventory_ordered"] = ordered_qs
|
||||||
|
|
||||||
# in transit inventory...
|
# in transit inventory...
|
||||||
in_transit_qs = qs.in_transit()
|
in_transit_qs = qs.in_transit()
|
||||||
context['inventory_in_transit'] = in_transit_qs
|
context["inventory_in_transit"] = in_transit_qs
|
||||||
|
|
||||||
# on hand inventory...
|
# on hand inventory...
|
||||||
received_qs = qs.is_received()
|
received_qs = qs.is_received()
|
||||||
context['inventory_received'] = received_qs
|
context["inventory_received"] = received_qs
|
||||||
|
|
||||||
context['page_title'] = _('Inventory')
|
context["page_title"] = _("Inventory")
|
||||||
context['header_title'] = _('Inventory Status')
|
context["header_title"] = _("Inventory Status")
|
||||||
context['header_subtitle'] = _('Ordered/In Transit/On Hand')
|
context["header_subtitle"] = _("Ordered/In Transit/On Hand")
|
||||||
context['header_subtitle_icon'] = 'ic:round-inventory'
|
context["header_subtitle_icon"] = "ic:round-inventory"
|
||||||
return context
|
return context
|
||||||
|
|
||||||
def get_queryset(self):
|
def get_queryset(self):
|
||||||
if self.queryset is None:
|
if self.queryset is None:
|
||||||
self.queryset = ItemTransactionModel.objects.inventory_pipeline_aggregate(
|
self.queryset = ItemTransactionModel.objects.inventory_pipeline_aggregate(
|
||||||
entity_slug=self.kwargs['entity_slug'],
|
entity_slug=self.kwargs["entity_slug"],
|
||||||
)
|
)
|
||||||
return super().get_queryset()
|
return super().get_queryset()
|
||||||
|
|
||||||
|
|
||||||
|
class InvoiceModelUpdateView(LoginRequiredMixin, PermissionRequiredMixin, UpdateView):
|
||||||
|
slug_url_kwarg = 'invoice_pk'
|
||||||
|
slug_field = 'uuid'
|
||||||
|
context_object_name = 'invoice'
|
||||||
|
# template_name = 'inventory/sales/invoices/invoice_update.html'
|
||||||
|
form_class = BaseInvoiceModelUpdateForm
|
||||||
|
http_method_names = ['get', 'post']
|
||||||
|
|
||||||
|
action_update_items = False
|
||||||
|
|
||||||
|
def get_form_class(self):
|
||||||
|
invoice_model: InvoiceModel = self.object
|
||||||
|
|
||||||
|
if invoice_model.is_draft():
|
||||||
|
return DraftInvoiceModelUpdateForm
|
||||||
|
elif invoice_model.is_review():
|
||||||
|
return InReviewInvoiceModelUpdateForm
|
||||||
|
elif invoice_model.is_approved():
|
||||||
|
if invoice_model.accrue:
|
||||||
|
return AccruedAndApprovedInvoiceModelUpdateForm
|
||||||
|
return ApprovedInvoiceModelUpdateForm
|
||||||
|
elif invoice_model.is_paid():
|
||||||
|
return PaidInvoiceModelUpdateForm
|
||||||
|
return BaseInvoiceModelUpdateForm
|
||||||
|
|
||||||
|
def get_form(self, form_class=None):
|
||||||
|
form_class = self.get_form_class()
|
||||||
|
if self.request.method == 'POST' and self.action_update_items:
|
||||||
|
return form_class(
|
||||||
|
entity_slug=self.kwargs['entity_slug'],
|
||||||
|
user_model=self.request.dealer.user,
|
||||||
|
instance=self.object
|
||||||
|
)
|
||||||
|
return form_class(
|
||||||
|
entity_slug=self.kwargs['entity_slug'],
|
||||||
|
user_model=self.request.dealer.user,
|
||||||
|
**self.get_form_kwargs()
|
||||||
|
)
|
||||||
|
|
||||||
|
def get_context_data(self, itemtxs_formset=None, **kwargs):
|
||||||
|
context = super().get_context_data(**kwargs)
|
||||||
|
invoice_model: InvoiceModel = self.object
|
||||||
|
title = f'Invoice {invoice_model.invoice_number}'
|
||||||
|
context['page_title'] = title
|
||||||
|
context['header_title'] = title
|
||||||
|
|
||||||
|
ledger_model: LedgerModel = self.object.ledger
|
||||||
|
|
||||||
|
if not invoice_model.is_configured():
|
||||||
|
messages.add_message(
|
||||||
|
request=self.request,
|
||||||
|
message=f'Invoice {invoice_model.invoice_number} must have all accounts configured.',
|
||||||
|
level=messages.ERROR,
|
||||||
|
extra_tags='is-danger'
|
||||||
|
)
|
||||||
|
|
||||||
|
if not invoice_model.is_paid():
|
||||||
|
if ledger_model.locked:
|
||||||
|
messages.add_message(self.request,
|
||||||
|
messages.ERROR,
|
||||||
|
f'Warning! This invoice is locked. Must unlock before making any changes.',
|
||||||
|
extra_tags='is-danger')
|
||||||
|
|
||||||
|
if ledger_model.locked:
|
||||||
|
messages.add_message(self.request,
|
||||||
|
messages.ERROR,
|
||||||
|
f'Warning! This Invoice is Locked. Must unlock before making any changes.',
|
||||||
|
extra_tags='is-danger')
|
||||||
|
|
||||||
|
if not ledger_model.is_posted():
|
||||||
|
messages.add_message(self.request,
|
||||||
|
messages.INFO,
|
||||||
|
f'This Invoice has not been posted. Must post to see ledger changes.',
|
||||||
|
extra_tags='is-info')
|
||||||
|
|
||||||
|
if not itemtxs_formset:
|
||||||
|
itemtxs_qs = invoice_model.itemtransactionmodel_set.all().select_related('item_model')
|
||||||
|
itemtxs_qs, itemtxs_agg = invoice_model.get_itemtxs_data(queryset=itemtxs_qs)
|
||||||
|
invoice_itemtxs_formset_class = get_invoice_itemtxs_formset_class(invoice_model)
|
||||||
|
itemtxs_formset = invoice_itemtxs_formset_class(
|
||||||
|
entity_slug=self.kwargs['entity_slug'],
|
||||||
|
user_model=self.request.dealer.user,
|
||||||
|
invoice_model=invoice_model,
|
||||||
|
queryset=itemtxs_qs
|
||||||
|
)
|
||||||
|
else:
|
||||||
|
itemtxs_qs, itemtxs_agg = invoice_model.get_itemtxs_data(queryset=itemtxs_formset.queryset)
|
||||||
|
|
||||||
|
context['itemtxs_formset'] = itemtxs_formset
|
||||||
|
context['total_amount__sum'] = itemtxs_agg['total_amount__sum']
|
||||||
|
return context
|
||||||
|
|
||||||
|
def get_success_url(self):
|
||||||
|
entity_slug = self.kwargs['entity_slug']
|
||||||
|
invoice_pk = self.kwargs['invoice_pk']
|
||||||
|
return reverse('invoice_detail',
|
||||||
|
kwargs={
|
||||||
|
'dealer_slug': self.request.dealer.slug,
|
||||||
|
'entity_slug': entity_slug,
|
||||||
|
'pk': invoice_pk
|
||||||
|
})
|
||||||
|
|
||||||
|
# def get_queryset(self):
|
||||||
|
# qs = super().get_queryset()
|
||||||
|
# return qs.prefetch_related('itemtransactionmodel_set')
|
||||||
|
def get_queryset(self):
|
||||||
|
if self.queryset is None:
|
||||||
|
self.queryset = InvoiceModel.objects.for_entity(
|
||||||
|
entity_slug=self.kwargs['entity_slug'],
|
||||||
|
user_model=self.request.user
|
||||||
|
).select_related('customer', 'ledger').order_by('-created')
|
||||||
|
return super().get_queryset().prefetch_related('itemtransactionmodel_set')
|
||||||
|
|
||||||
|
|
||||||
|
def form_valid(self, form):
|
||||||
|
invoice_model: InvoiceModel = form.save(commit=False)
|
||||||
|
if invoice_model.can_migrate():
|
||||||
|
invoice_model.migrate_state(
|
||||||
|
user_model=self.request.dealer.user,
|
||||||
|
entity_slug=self.kwargs['entity_slug']
|
||||||
|
)
|
||||||
|
messages.add_message(self.request,
|
||||||
|
messages.SUCCESS,
|
||||||
|
f'Invoice {self.object.invoice_number} successfully updated.',
|
||||||
|
extra_tags='is-success')
|
||||||
|
return super().form_valid(form)
|
||||||
|
|
||||||
|
def get(self, request, entity_slug, invoice_pk, *args, **kwargs):
|
||||||
|
if self.action_update_items:
|
||||||
|
return HttpResponseRedirect(
|
||||||
|
redirect_to=reverse('invoice_update',
|
||||||
|
kwargs={
|
||||||
|
'dealer_slug': request.dealer.slug,
|
||||||
|
'entity_slug': entity_slug,
|
||||||
|
'pk': invoice_pk
|
||||||
|
})
|
||||||
|
)
|
||||||
|
return super(InvoiceModelUpdateView, self).get(request, *args, **kwargs)
|
||||||
|
|
||||||
|
def post(self, request, entity_slug, invoice_pk, *args, **kwargs):
|
||||||
|
if self.action_update_items:
|
||||||
|
if not request.user.is_authenticated:
|
||||||
|
return HttpResponseForbidden()
|
||||||
|
|
||||||
|
queryset = self.get_queryset()
|
||||||
|
invoice_model = self.get_object(queryset=queryset)
|
||||||
|
self.object = invoice_model
|
||||||
|
invoice_itemtxs_formset_class = get_invoice_itemtxs_formset_class(invoice_model)
|
||||||
|
itemtxs_formset = invoice_itemtxs_formset_class(request.POST,
|
||||||
|
user_model=self.request.dealer.user,
|
||||||
|
invoice_model=invoice_model,
|
||||||
|
entity_slug=entity_slug)
|
||||||
|
|
||||||
|
if not invoice_model.can_edit_items():
|
||||||
|
messages.add_message(
|
||||||
|
request,
|
||||||
|
message=f'Cannot update items once Invoice is {invoice_model.get_invoice_status_display()}',
|
||||||
|
level=messages.ERROR,
|
||||||
|
extra_tags='is-danger'
|
||||||
|
)
|
||||||
|
context = self.get_context_data(itemtxs_formset=itemtxs_formset)
|
||||||
|
return self.render_to_response(context=context)
|
||||||
|
|
||||||
|
if itemtxs_formset.has_changed():
|
||||||
|
if itemtxs_formset.is_valid():
|
||||||
|
itemtxs_list = itemtxs_formset.save(commit=False)
|
||||||
|
entity_qs = EntityModel.objects.for_user(user_model=self.request.dealer.user)
|
||||||
|
entity_model: EntityModel = get_object_or_404(entity_qs, slug__exact=entity_slug)
|
||||||
|
|
||||||
|
for itemtxs in itemtxs_list:
|
||||||
|
itemtxs.invoice_model_id = invoice_model.uuid
|
||||||
|
itemtxs.clean()
|
||||||
|
|
||||||
|
itemtxs_list = itemtxs_formset.save()
|
||||||
|
itemtxs_qs = invoice_model.update_amount_due()
|
||||||
|
invoice_model.get_state(commit=True)
|
||||||
|
invoice_model.clean()
|
||||||
|
invoice_model.save(
|
||||||
|
update_fields=['amount_due',
|
||||||
|
'amount_receivable',
|
||||||
|
'amount_unearned',
|
||||||
|
'amount_earned',
|
||||||
|
'updated']
|
||||||
|
)
|
||||||
|
|
||||||
|
invoice_model.migrate_state(
|
||||||
|
entity_slug=entity_slug,
|
||||||
|
user_model=self.request.user,
|
||||||
|
raise_exception=False,
|
||||||
|
itemtxs_qs=itemtxs_qs
|
||||||
|
)
|
||||||
|
|
||||||
|
messages.add_message(request,
|
||||||
|
message=f'Items for Invoice {invoice_model.invoice_number} saved.',
|
||||||
|
level=messages.SUCCESS,
|
||||||
|
extra_tags='is-success')
|
||||||
|
return HttpResponseRedirect(
|
||||||
|
redirect_to=reverse('django_ledger:invoice-update',
|
||||||
|
kwargs={
|
||||||
|
'entity_slug': entity_slug,
|
||||||
|
'invoice_pk': invoice_pk
|
||||||
|
})
|
||||||
|
)
|
||||||
|
|
||||||
|
# if not valid, return formset with errors...
|
||||||
|
return self.render_to_response(context=self.get_context_data(itemtxs_formset=itemtxs_formset))
|
||||||
|
return super(InvoiceModelUpdateView, self).post(request, **kwargs)
|
||||||
|
|||||||
@ -18,16 +18,17 @@ from django_ledger.models import (
|
|||||||
AccountModel,
|
AccountModel,
|
||||||
PurchaseOrderModel,
|
PurchaseOrderModel,
|
||||||
EstimateModel,
|
EstimateModel,
|
||||||
BillModel
|
BillModel,
|
||||||
)
|
)
|
||||||
from . import models
|
from . import models
|
||||||
from django.utils.timezone import now
|
from django.utils.timezone import now
|
||||||
from django.db import transaction
|
from django.db import transaction
|
||||||
from django_q.tasks import async_task
|
from django_q.tasks import async_task
|
||||||
|
|
||||||
#logging
|
# logging
|
||||||
import logging
|
import logging
|
||||||
logger=logging.getLogger(__name__)
|
|
||||||
|
logger = logging.getLogger(__name__)
|
||||||
|
|
||||||
User = get_user_model()
|
User = get_user_model()
|
||||||
|
|
||||||
@ -94,7 +95,9 @@ def create_car_location(sender, instance, created, **kwargs):
|
|||||||
try:
|
try:
|
||||||
if created:
|
if created:
|
||||||
# Log that the signal was triggered for a new car
|
# Log that the signal was triggered for a new car
|
||||||
logger.debug(f"Post-save signal triggered for new Car (VIN: {instance.vin}). Attempting to create CarLocation.")
|
logger.debug(
|
||||||
|
f"Post-save signal triggered for new Car (VIN: {instance.vin}). Attempting to create CarLocation."
|
||||||
|
)
|
||||||
|
|
||||||
if instance.dealer is None:
|
if instance.dealer is None:
|
||||||
# Log the critical data integrity error before raising
|
# Log the critical data integrity error before raising
|
||||||
@ -122,7 +125,7 @@ def create_car_location(sender, instance, created, **kwargs):
|
|||||||
logger.error(
|
logger.error(
|
||||||
f"Failed to create CarLocation for car (VIN: {instance.vin}). "
|
f"Failed to create CarLocation for car (VIN: {instance.vin}). "
|
||||||
f"An unexpected error occurred: {e}",
|
f"An unexpected error occurred: {e}",
|
||||||
exc_info=True
|
exc_info=True,
|
||||||
)
|
)
|
||||||
print(f"Failed to create CarLocation for car {instance.vin}: {e}")
|
print(f"Failed to create CarLocation for car {instance.vin}: {e}")
|
||||||
|
|
||||||
@ -171,7 +174,7 @@ def create_ledger_entity(sender, instance, created, **kwargs):
|
|||||||
entity.create_uom(name=u[1], unit_abbr=u[0])
|
entity.create_uom(name=u[1], unit_abbr=u[0])
|
||||||
|
|
||||||
# Create COA accounts, background task
|
# Create COA accounts, background task
|
||||||
async_task(create_coa_accounts,instance)
|
async_task(create_coa_accounts, instance)
|
||||||
|
|
||||||
# create_settings(instance.pk)
|
# create_settings(instance.pk)
|
||||||
# create_accounts_for_make(instance.pk)
|
# create_accounts_for_make(instance.pk)
|
||||||
@ -195,10 +198,8 @@ def create_dealer_groups(sender, instance, created, **kwargs):
|
|||||||
if created:
|
if created:
|
||||||
# async_task("inventory.tasks.create_groups",instance.slug)
|
# async_task("inventory.tasks.create_groups",instance.slug)
|
||||||
def create_groups():
|
def create_groups():
|
||||||
for group_name in ["Inventory", "Accountant", "Sales","Manager"]:
|
for group_name in ["Inventory", "Accountant", "Sales", "Manager"]:
|
||||||
group= Group.objects.create(
|
group = Group.objects.create(name=f"{instance.slug}_{group_name}")
|
||||||
name=f"{instance.slug}_{group_name}"
|
|
||||||
)
|
|
||||||
group_manager = models.CustomGroup.objects.create(
|
group_manager = models.CustomGroup.objects.create(
|
||||||
name=group_name, dealer=instance, group=group
|
name=group_name, dealer=instance, group=group
|
||||||
)
|
)
|
||||||
@ -263,18 +264,19 @@ def create_item_model(sender, instance, created, **kwargs):
|
|||||||
uom_model=uom,
|
uom_model=uom,
|
||||||
coa_model=coa,
|
coa_model=coa,
|
||||||
)
|
)
|
||||||
|
instance.item_model = inventory
|
||||||
|
inventory.save()
|
||||||
# inventory = entity.create_item_inventory(
|
# inventory = entity.create_item_inventory(
|
||||||
# name=instance.vin,
|
# name=instance.vin,
|
||||||
# uom_model=uom,
|
# uom_model=uom,
|
||||||
# item_type=ItemModel.ITEM_TYPE_LUMP_SUM
|
# item_type=ItemModel.ITEM_TYPE_LUMP_SUM
|
||||||
# )
|
# )
|
||||||
instance.item_model = inventory
|
# inventory.additional_info = {}
|
||||||
inventory.additional_info = {}
|
# inventory.additional_info.update({"car_info": instance.to_dict()})
|
||||||
inventory.additional_info.update({"car_info": instance.to_dict()})
|
# inventory.save()
|
||||||
inventory.save()
|
# else:
|
||||||
else:
|
# instance.item_model.additional_info.update({"car_info": instance.to_dict()})
|
||||||
instance.item_model.additional_info.update({"car_info": instance.to_dict()})
|
# instance.item_model.save()
|
||||||
instance.item_model.save()
|
|
||||||
|
|
||||||
|
|
||||||
# # update price - CarFinance
|
# # update price - CarFinance
|
||||||
@ -292,7 +294,8 @@ def update_item_model_cost(sender, instance, created, **kwargs):
|
|||||||
:param kwargs: Additional keyword arguments passed during the signal invocation.
|
:param kwargs: Additional keyword arguments passed during the signal invocation.
|
||||||
:return: None
|
:return: None
|
||||||
"""
|
"""
|
||||||
if created and not instance.is_sold:
|
# if created and not instance.is_sold:
|
||||||
|
if created:
|
||||||
entity = instance.car.dealer.entity
|
entity = instance.car.dealer.entity
|
||||||
coa = entity.get_default_coa()
|
coa = entity.get_default_coa()
|
||||||
inventory_account = (
|
inventory_account = (
|
||||||
@ -369,14 +372,14 @@ def update_item_model_cost(sender, instance, created, **kwargs):
|
|||||||
instance.car.item_model.default_amount = instance.marked_price
|
instance.car.item_model.default_amount = instance.marked_price
|
||||||
if not isinstance(instance.car.item_model.additional_info, dict):
|
if not isinstance(instance.car.item_model.additional_info, dict):
|
||||||
instance.car.item_model.additional_info = {}
|
instance.car.item_model.additional_info = {}
|
||||||
instance.car.item_model.additional_info.update({"car_finance": instance.to_dict()})
|
# instance.car.item_model.additional_info.update({"car_finance": instance.to_dict()})
|
||||||
instance.car.item_model.additional_info.update(
|
# instance.car.item_model.additional_info.update(
|
||||||
{
|
# {
|
||||||
"additional_services": [
|
# "additional_services": [
|
||||||
service.to_dict() for service in instance.additional_services.all()
|
# service.to_dict() for service in instance.additional_services.all()
|
||||||
]
|
# ]
|
||||||
}
|
# }
|
||||||
)
|
# )
|
||||||
instance.car.item_model.save()
|
instance.car.item_model.save()
|
||||||
print(f"Inventory item updated with CarFinance data for Car: {instance.car}")
|
print(f"Inventory item updated with CarFinance data for Car: {instance.car}")
|
||||||
|
|
||||||
@ -552,13 +555,13 @@ def track_lead_status_change(sender, instance, **kwargs):
|
|||||||
new_status=instance.status,
|
new_status=instance.status,
|
||||||
changed_by=instance.staff, # Assuming the assigned staff made the change
|
changed_by=instance.staff, # Assuming the assigned staff made the change
|
||||||
)
|
)
|
||||||
# --- Single-line log for successful status change and history creation ---
|
# --- Single-line log for successful status change and history creation ---
|
||||||
logger.info(
|
logger.info(
|
||||||
f"Lead ID: {instance.pk} status changed from '{old_lead.status}' to '{instance.status}'. "
|
f"Lead ID: {instance.pk} status changed from '{old_lead.status}' to '{instance.status}'. "
|
||||||
f"LeadStatusHistory recorded by Staff: {instance.staff.username if instance.staff else 'N/A'}."
|
f"LeadStatusHistory recorded by Staff: {instance.staff.username if instance.staff else 'N/A'}."
|
||||||
)
|
)
|
||||||
except models.Lead.DoesNotExist:
|
except models.Lead.DoesNotExist:
|
||||||
# --- Single-line log for expected Lead.DoesNotExist (e.g., during initial object creation) ---
|
# --- Single-line log for expected Lead.DoesNotExist (e.g., during initial object creation) ---
|
||||||
logger.debug(
|
logger.debug(
|
||||||
f"Lead ID: {instance.pk} not found in database when checking for status change. "
|
f"Lead ID: {instance.pk} not found in database when checking for status change. "
|
||||||
f"This might occur during initial object creation. Skipping status history tracking."
|
f"This might occur during initial object creation. Skipping status history tracking."
|
||||||
@ -759,20 +762,20 @@ def create_dealer_settings(sender, instance, created, **kwargs):
|
|||||||
# entity = instance.entity
|
# entity = instance.entity
|
||||||
# coa = entity.get_default_coa()
|
# coa = entity.get_default_coa()
|
||||||
|
|
||||||
# for make in models.CarMake.objects.all():
|
# for make in models.CarMake.objects.all():
|
||||||
# last_account = entity.get_all_accounts().filter(role=roles.ASSET_CA_RECEIVABLES).order_by('-created').first()
|
# last_account = entity.get_all_accounts().filter(role=roles.ASSET_CA_RECEIVABLES).order_by('-created').first()
|
||||||
# if len(last_account.code) == 4:
|
# if len(last_account.code) == 4:
|
||||||
# code = f"{int(last_account.code)}{1:03d}"
|
# code = f"{int(last_account.code)}{1:03d}"
|
||||||
# elif len(last_account.code) > 4:
|
# elif len(last_account.code) > 4:
|
||||||
# code = f"{int(last_account.code)+1}"
|
# code = f"{int(last_account.code)+1}"
|
||||||
# entity.create_account(
|
# entity.create_account(
|
||||||
# name=make.name,
|
# name=make.name,
|
||||||
# code=code,
|
# code=code,
|
||||||
# role=roles.ASSET_CA_RECEIVABLES,
|
# role=roles.ASSET_CA_RECEIVABLES,
|
||||||
# coa_model=coa,
|
# coa_model=coa,
|
||||||
# balance_type="credit",
|
# balance_type="credit",
|
||||||
# active=True
|
# active=True
|
||||||
# )
|
# )
|
||||||
|
|
||||||
|
|
||||||
# @receiver(post_save, sender=VendorModel)
|
# @receiver(post_save, sender=VendorModel)
|
||||||
@ -935,42 +938,60 @@ def update_finance_cost(sender, instance, created, **kwargs):
|
|||||||
# else:
|
# else:
|
||||||
# save_journal(instance,ledger,vendor)
|
# save_journal(instance,ledger,vendor)
|
||||||
|
|
||||||
|
|
||||||
@receiver(post_save, sender=PurchaseOrderModel)
|
@receiver(post_save, sender=PurchaseOrderModel)
|
||||||
def create_po_item_upload(sender,instance,created,**kwargs):
|
def create_po_item_upload(sender, instance, created, **kwargs):
|
||||||
if instance.po_status == "fulfilled":
|
if instance.po_status == "fulfilled":
|
||||||
for item in instance.get_itemtxs_data()[0]:
|
for item in instance.get_itemtxs_data()[0]:
|
||||||
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)
|
@receiver(post_save, sender=models.Staff)
|
||||||
def add_service_to_staff(sender,instance,created,**kwargs):
|
def add_service_to_staff(sender, instance, created, **kwargs):
|
||||||
if created:
|
if created:
|
||||||
for service in Service.objects.all():
|
for service in Service.objects.all():
|
||||||
instance.staff_member.services_offered.add(service)
|
instance.staff_member.services_offered.add(service)
|
||||||
|
|
||||||
|
|
||||||
##########################################################
|
##########################################################
|
||||||
######################Notification########################
|
######################Notification########################
|
||||||
##########################################################
|
##########################################################
|
||||||
|
|
||||||
@receiver(post_save, sender=PurchaseOrderModel)
|
|
||||||
def create_po_fulfilled_notification(sender,instance,created,**kwargs):
|
# @receiver(post_save, sender=PurchaseOrderModel)
|
||||||
if instance.po_status == "fulfilled":
|
# def create_po_fulfilled_notification(sender, instance, created, **kwargs):
|
||||||
dealer = models.Dealer.objects.get(entity=instance.entity)
|
# if instance.po_status == "fulfilled":
|
||||||
accountants = models.CustomGroup.objects.filter(dealer=dealer,name="Inventory").first().group.user_set.exclude(email=dealer.user.email).distinct()
|
# dealer = models.Dealer.objects.get(entity=instance.entity)
|
||||||
for accountant in accountants:
|
# accountants = (
|
||||||
models.Notification.objects.create(
|
# models.CustomGroup.objects.filter(dealer=dealer, name="Inventory")
|
||||||
user=accountant,
|
# .first()
|
||||||
message=f"""
|
# .group.user_set.exclude(email=dealer.user.email)
|
||||||
New Purchase Order {instance.po_number} has been added to dealer {dealer.name}.
|
# .distinct()
|
||||||
<a href="{instance.get_absolute_url()}" target="_blank">View</a>
|
# )
|
||||||
""",
|
# for accountant in accountants:
|
||||||
)
|
# models.Notification.objects.create(
|
||||||
|
# user=accountant,
|
||||||
|
# message=f"""
|
||||||
|
# New Purchase Order {instance.po_number} has been added to dealer {dealer.name}.
|
||||||
|
# <a href="{instance.get_absolute_url()}" target="_blank">View</a>
|
||||||
|
# """,
|
||||||
|
# )
|
||||||
|
|
||||||
|
|
||||||
@receiver(post_save, sender=models.Car)
|
@receiver(post_save, sender=models.Car)
|
||||||
def car_created_notification(sender, instance, created, **kwargs):
|
def car_created_notification(sender, instance, created, **kwargs):
|
||||||
if created:
|
if created:
|
||||||
|
accountants = (
|
||||||
accountants = models.CustomGroup.objects.filter(dealer=instance.dealer,name__in=["Manager","Accountant"]).first().group.user_set.all().distinct()
|
models.CustomGroup.objects.filter(
|
||||||
|
dealer=instance.dealer, name__in=["Manager", "Accountant"]
|
||||||
|
)
|
||||||
|
.first()
|
||||||
|
.group.user_set.all()
|
||||||
|
.distinct()
|
||||||
|
)
|
||||||
for accountant in accountants:
|
for accountant in accountants:
|
||||||
models.Notification.objects.create(
|
models.Notification.objects.create(
|
||||||
user=accountant,
|
user=accountant,
|
||||||
@ -981,29 +1002,30 @@ def car_created_notification(sender, instance, created, **kwargs):
|
|||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
@receiver(post_save, sender=PurchaseOrderModel)
|
@receiver(post_save, sender=PurchaseOrderModel)
|
||||||
def po_fullfilled_notification(sender, instance, created, **kwargs):
|
def po_fullfilled_notification(sender, instance, created, **kwargs):
|
||||||
if instance.is_fulfilled():
|
if instance.is_fulfilled():
|
||||||
dealer = models.Dealer.objects.get(entity=instance.entity)
|
dealer = models.Dealer.objects.get(entity=instance.entity)
|
||||||
recipients = User.objects.filter(
|
recipients = User.objects.filter(
|
||||||
groups__customgroup__dealer=instance.dealer,
|
groups__customgroup__dealer=dealer,
|
||||||
groups__customgroup__name__in=["Manager", "Inventory"]
|
groups__customgroup__name__in=["Manager", "Inventory"],
|
||||||
).distinct()
|
).distinct()
|
||||||
for recipient in recipients:
|
for recipient in recipients:
|
||||||
models.Notification.objects.create(
|
models.Notification.objects.create(
|
||||||
user=recipient,
|
user=recipient,
|
||||||
message=f"""
|
message=f"""
|
||||||
New Purchase Order has been added.
|
New Purchase Order has been added.
|
||||||
<a href="{reverse('purchase_order_detail',kwargs={'dealer_slug':dealer.slug,'pk':instance.pk})}" target="_blank">View</a>
|
<a href="{reverse("purchase_order_detail", kwargs={"dealer_slug": dealer.slug, "pk": instance.pk})}" target="_blank">View</a>
|
||||||
""",
|
""",
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
@receiver(post_save, sender=models.Vendor)
|
@receiver(post_save, sender=models.Vendor)
|
||||||
def vendor_created_notification(sender, instance, created, **kwargs):
|
def vendor_created_notification(sender, instance, created, **kwargs):
|
||||||
if created:
|
if created:
|
||||||
recipients = User.objects.filter(
|
recipients = User.objects.filter(
|
||||||
groups__customgroup__dealer=instance.dealer,
|
groups__customgroup__dealer=instance.dealer,
|
||||||
groups__customgroup__name__in=["Manager", "Inventory"]
|
groups__customgroup__name__in=["Manager", "Inventory"],
|
||||||
).distinct()
|
).distinct()
|
||||||
|
|
||||||
for recipient in recipients:
|
for recipient in recipients:
|
||||||
@ -1014,19 +1036,27 @@ def vendor_created_notification(sender, instance, created, **kwargs):
|
|||||||
""",
|
""",
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
@receiver(post_save, sender=models.SaleOrder)
|
@receiver(post_save, sender=models.SaleOrder)
|
||||||
def sale_order_created_notification(sender, instance, created, **kwargs):
|
def sale_order_created_notification(sender, instance, created, **kwargs):
|
||||||
if created:
|
if created:
|
||||||
recipients = models.CustomGroup.objects.filter(dealer=instance.dealer,name="Accountant").first().group.user_set.exclude(email=instance.dealer.user.email).distinct()
|
recipients = (
|
||||||
|
models.CustomGroup.objects.filter(dealer=instance.dealer, name="Accountant")
|
||||||
|
.first()
|
||||||
|
.group.user_set.exclude(email=instance.dealer.user.email)
|
||||||
|
.distinct()
|
||||||
|
)
|
||||||
|
|
||||||
for recipient in recipients:
|
for recipient in recipients:
|
||||||
models.Notification.objects.create(
|
models.Notification.objects.create(
|
||||||
user=recipient,
|
user=recipient,
|
||||||
message=f"""
|
message=f"""
|
||||||
New Sale Order has been added for estimate:{instance.estimate}.
|
New Sale Order has been added for estimate:{instance.estimate}.
|
||||||
<a href="{reverse('estimate_detail',kwargs={'dealer_slug':instance.dealer.slug,'pk':instance.estimate.pk})}" target="_blank">View</a>
|
<a href="{reverse("estimate_detail", kwargs={"dealer_slug": instance.dealer.slug, "pk": instance.estimate.pk})}" target="_blank">View</a>
|
||||||
""",
|
""",
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
@receiver(post_save, sender=models.Lead)
|
@receiver(post_save, sender=models.Lead)
|
||||||
def lead_created_notification(sender, instance, created, **kwargs):
|
def lead_created_notification(sender, instance, created, **kwargs):
|
||||||
if created:
|
if created:
|
||||||
@ -1035,22 +1065,31 @@ def lead_created_notification(sender, instance, created, **kwargs):
|
|||||||
user=instance.staff.user,
|
user=instance.staff.user,
|
||||||
message=f"""
|
message=f"""
|
||||||
New Lead has been added.
|
New Lead has been added.
|
||||||
<a href="{reverse('lead_detail',kwargs={'dealer_slug':instance.dealer.slug,'slug':instance.slug})}" target="_blank">View</a>
|
<a href="{reverse("lead_detail", kwargs={"dealer_slug": instance.dealer.slug, "slug": instance.slug})}" target="_blank">View</a>
|
||||||
""",
|
""",
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
@receiver(post_save, sender=EstimateModel)
|
@receiver(post_save, sender=EstimateModel)
|
||||||
def estimate_in_review_notification(sender, instance, created, **kwargs):
|
def estimate_in_review_notification(sender, instance, created, **kwargs):
|
||||||
if instance.is_review():
|
if instance.is_review():
|
||||||
dealer = models.Dealer.objects.get(entity=instance.entity)
|
dealer = models.Dealer.objects.get(entity=instance.entity)
|
||||||
recipients = models.CustomGroup.objects.filter(dealer=dealer,name="Manager").first().group.user_set.exclude(email=dealer.user.email).distinct()
|
recipients = (
|
||||||
|
models.CustomGroup.objects.filter(dealer=dealer, name="Manager")
|
||||||
|
.first()
|
||||||
|
.group.user_set.exclude(email=dealer.user.email)
|
||||||
|
.distinct()
|
||||||
|
)
|
||||||
for recipient in recipients:
|
for recipient in recipients:
|
||||||
models.Notification.objects.create(
|
models.Notification.objects.create(
|
||||||
user=recipient,
|
user=recipient,
|
||||||
message=f"""
|
message=f"""
|
||||||
Estimate {instance.estimate_number} is in review.
|
Estimate {instance.estimate_number} is in review.
|
||||||
Please review and approve it at your earliest convenience.
|
Please review and approve it at your earliest convenience.
|
||||||
<a href="{reverse('estimate_detail', kwargs={'dealer_slug': dealer.slug, 'pk': instance.pk})}" target="_blank">View</a>
|
<a href="{reverse("estimate_detail", kwargs={"dealer_slug": dealer.slug, "pk": instance.pk})}" target="_blank">View</a>
|
||||||
""")
|
""",
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
@receiver(post_save, sender=EstimateModel)
|
@receiver(post_save, sender=EstimateModel)
|
||||||
def estimate_in_approve_notification(sender, instance, created, **kwargs):
|
def estimate_in_approve_notification(sender, instance, created, **kwargs):
|
||||||
@ -1058,46 +1097,59 @@ def estimate_in_approve_notification(sender, instance, created, **kwargs):
|
|||||||
dealer = models.Dealer.objects.get(entity=instance.entity)
|
dealer = models.Dealer.objects.get(entity=instance.entity)
|
||||||
|
|
||||||
recipient = models.ExtraInfo.objects.filter(
|
recipient = models.ExtraInfo.objects.filter(
|
||||||
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),
|
||||||
object_id=instance.pk,
|
object_id=instance.pk,
|
||||||
).first()
|
).first()
|
||||||
|
if not recipient:
|
||||||
|
return
|
||||||
models.Notification.objects.create(
|
models.Notification.objects.create(
|
||||||
user=recipient.related_object.user,
|
user=recipient.related_object.user,
|
||||||
message=f"""
|
message=f"""
|
||||||
Estimate {instance.estimate_number} has been approved.
|
Estimate {instance.estimate_number} has been approved.
|
||||||
<a href="{reverse('estimate_detail', kwargs={'dealer_slug': dealer.slug, 'pk': instance.pk})}" target="_blank">View</a>
|
<a href="{reverse("estimate_detail", kwargs={"dealer_slug": dealer.slug, "pk": instance.pk})}" target="_blank">View</a>
|
||||||
"""
|
""",
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
@receiver(post_save, sender=BillModel)
|
@receiver(post_save, sender=BillModel)
|
||||||
def bill_model_in_approve_notification(sender, instance, created, **kwargs):
|
def bill_model_in_approve_notification(sender, instance, created, **kwargs):
|
||||||
if instance.is_review():
|
if instance.is_review():
|
||||||
dealer = models.Dealer.objects.get(entity=instance.ledger.entity)
|
dealer = models.Dealer.objects.get(entity=instance.ledger.entity)
|
||||||
recipients = models.CustomGroup.objects.filter(dealer=dealer,name="Manager").first().group.user_set.exclude(email=dealer.user.email).distinct()
|
recipients = (
|
||||||
|
models.CustomGroup.objects.filter(dealer=dealer, name="Manager")
|
||||||
|
.first()
|
||||||
|
.group.user_set.exclude(email=dealer.user.email)
|
||||||
|
.distinct()
|
||||||
|
)
|
||||||
|
|
||||||
for recipient in recipients:
|
for recipient in recipients:
|
||||||
models.Notification.objects.create(
|
models.Notification.objects.create(
|
||||||
user=recipient,
|
user=recipient,
|
||||||
message=f"""
|
message=f"""
|
||||||
Bill {instance.bill_number} is in review,please review and approve it
|
Bill {instance.bill_number} is in review,please review and approve it
|
||||||
<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>.
|
||||||
"""
|
""",
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
@receiver(post_save, sender=BillModel)
|
@receiver(post_save, sender=BillModel)
|
||||||
def bill_model_after_approve_notification(sender, instance, created, **kwargs):
|
def bill_model_after_approve_notification(sender, instance, created, **kwargs):
|
||||||
if instance.is_approved():
|
if instance.is_approved():
|
||||||
dealer = models.Dealer.objects.get(entity=instance.ledger.entity)
|
dealer = models.Dealer.objects.get(entity=instance.ledger.entity)
|
||||||
recipients = models.CustomGroup.objects.filter(dealer=dealer,name="Accountant").first().group.user_set.exclude(email=dealer.user.email).distinct()
|
recipients = (
|
||||||
|
models.CustomGroup.objects.filter(dealer=dealer, name="Accountant")
|
||||||
|
.first()
|
||||||
|
.group.user_set.exclude(email=dealer.user.email)
|
||||||
|
.distinct()
|
||||||
|
)
|
||||||
|
|
||||||
for recipient in recipients:
|
for recipient in recipients:
|
||||||
models.Notification.objects.create(
|
models.Notification.objects.create(
|
||||||
user=recipient,
|
user=recipient,
|
||||||
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 complete the bill payment.
|
please complete the bill payment.
|
||||||
"""
|
""",
|
||||||
)
|
)
|
||||||
|
|||||||
@ -4,13 +4,15 @@ from django_ledger.io import roles
|
|||||||
from django_q.tasks import async_task
|
from django_q.tasks import async_task
|
||||||
from django.core.mail import send_mail
|
from django.core.mail import send_mail
|
||||||
from appointment.models import StaffMember
|
from appointment.models import StaffMember
|
||||||
from django.contrib.auth.models import User,Group, Permission
|
from allauth.account.models import EmailAddress
|
||||||
from inventory.models import DealerSettings, Dealer
|
from inventory.models import DealerSettings, Dealer
|
||||||
from django.utils.translation import gettext_lazy as _
|
from django.utils.translation import gettext_lazy as _
|
||||||
|
from django.contrib.auth.models import User, Group, Permission
|
||||||
|
|
||||||
logger = logging.getLogger(__name__)
|
logger = logging.getLogger(__name__)
|
||||||
logging.basicConfig(level=logging.INFO)
|
logging.basicConfig(level=logging.INFO)
|
||||||
|
|
||||||
|
|
||||||
def create_settings(pk):
|
def create_settings(pk):
|
||||||
instance = Dealer.objects.get(pk=pk)
|
instance = Dealer.objects.get(pk=pk)
|
||||||
|
|
||||||
@ -219,7 +221,7 @@ def create_coa_accounts(instance):
|
|||||||
"role": roles.LIABILITY_CL_TAXES_PAYABLE,
|
"role": roles.LIABILITY_CL_TAXES_PAYABLE,
|
||||||
"balance_type": roles.CREDIT,
|
"balance_type": roles.CREDIT,
|
||||||
"locked": False,
|
"locked": False,
|
||||||
"default": True, # Default for LIABILITY_CL_TAXES_PAYABLE
|
"default": False, # Default for LIABILITY_CL_TAXES_PAYABLE
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"code": "2070",
|
"code": "2070",
|
||||||
@ -238,6 +240,14 @@ def create_coa_accounts(instance):
|
|||||||
"locked": False,
|
"locked": False,
|
||||||
"default": True, # Default for LIABILITY_CL_DEFERRED_REVENUE
|
"default": True, # Default for LIABILITY_CL_DEFERRED_REVENUE
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
"code": "2200",
|
||||||
|
"name": "Tax Payable",
|
||||||
|
"role": roles.LIABILITY_CL_TAXES_PAYABLE,
|
||||||
|
"balance_type": roles.CREDIT,
|
||||||
|
"locked": False,
|
||||||
|
"default": True,
|
||||||
|
},
|
||||||
{
|
{
|
||||||
"code": "2210",
|
"code": "2210",
|
||||||
"name": "Long-term Bank Loans",
|
"name": "Long-term Bank Loans",
|
||||||
@ -1126,12 +1136,13 @@ def create_make_accounts(entity, coa, makes, name, role, balance_type):
|
|||||||
)
|
)
|
||||||
return acc
|
return acc
|
||||||
|
|
||||||
|
|
||||||
def send_email(from_, to_, subject, message):
|
def send_email(from_, to_, subject, message):
|
||||||
subject = subject
|
subject = subject
|
||||||
message = message
|
message = message
|
||||||
from_email = from_
|
from_email = from_
|
||||||
recipient_list = [to_]
|
recipient_list = [to_]
|
||||||
async_task(send_mail,subject, message, from_email, recipient_list)
|
async_task(send_mail, subject, message, from_email, recipient_list)
|
||||||
|
|
||||||
|
|
||||||
def create_user_dealer(email, password, name, arabic_name, phone, crn, vrn, address):
|
def create_user_dealer(email, password, name, arabic_name, phone, crn, vrn, address):
|
||||||
@ -1139,6 +1150,15 @@ def create_user_dealer(email, password, name, arabic_name, phone, crn, vrn, addr
|
|||||||
user = User.objects.create(username=email, email=email)
|
user = User.objects.create(username=email, email=email)
|
||||||
user.set_password(password)
|
user.set_password(password)
|
||||||
user.save()
|
user.save()
|
||||||
|
|
||||||
|
#TODO remove this later
|
||||||
|
EmailAddress.objects.create(
|
||||||
|
user=user,
|
||||||
|
email=user.email,
|
||||||
|
verified=True,
|
||||||
|
primary=True
|
||||||
|
)
|
||||||
|
|
||||||
group = Group.objects.create(name=f"{user.pk}-Admin")
|
group = Group.objects.create(name=f"{user.pk}-Admin")
|
||||||
user.groups.add(group)
|
user.groups.add(group)
|
||||||
for perm in Permission.objects.filter(
|
for perm in Permission.objects.filter(
|
||||||
@ -1147,7 +1167,7 @@ def create_user_dealer(email, password, name, arabic_name, phone, crn, vrn, addr
|
|||||||
group.permissions.add(perm)
|
group.permissions.add(perm)
|
||||||
|
|
||||||
StaffMember.objects.create(user=user)
|
StaffMember.objects.create(user=user)
|
||||||
Dealer.objects.create(
|
dealer = Dealer.objects.create(
|
||||||
user=user,
|
user=user,
|
||||||
name=name,
|
name=name,
|
||||||
arabic_name=arabic_name,
|
arabic_name=arabic_name,
|
||||||
@ -1156,6 +1176,7 @@ def create_user_dealer(email, password, name, arabic_name, phone, crn, vrn, addr
|
|||||||
phone_number=phone,
|
phone_number=phone,
|
||||||
address=address,
|
address=address,
|
||||||
)
|
)
|
||||||
|
return dealer
|
||||||
|
|
||||||
|
|
||||||
# def create_groups(dealer_slug):
|
# def create_groups(dealer_slug):
|
||||||
@ -1174,4 +1195,3 @@ def create_user_dealer(email, password, name, arabic_name, phone, crn, vrn, addr
|
|||||||
# instance.user.groups.add(group)
|
# instance.user.groups.add(group)
|
||||||
|
|
||||||
# transaction.on_commit(run)
|
# transaction.on_commit(run)
|
||||||
|
|
||||||
|
|||||||
@ -54,7 +54,6 @@ def period_navigation(context, base_url: str):
|
|||||||
kwargs["dealer_slug"] = dealer_slug
|
kwargs["dealer_slug"] = dealer_slug
|
||||||
kwargs["entity_slug"] = entity_slug
|
kwargs["entity_slug"] = entity_slug
|
||||||
|
|
||||||
|
|
||||||
if context["view"].kwargs.get("ledger_pk"):
|
if context["view"].kwargs.get("ledger_pk"):
|
||||||
kwargs["ledger_pk"] = context["view"].kwargs.get("ledger_pk")
|
kwargs["ledger_pk"] = context["view"].kwargs.get("ledger_pk")
|
||||||
|
|
||||||
@ -103,7 +102,7 @@ def period_navigation(context, base_url: str):
|
|||||||
if "coa_slug" in kwargs:
|
if "coa_slug" in kwargs:
|
||||||
KWARGS_CURRENT_MONTH["coa_slug"] = kwargs["coa_slug"]
|
KWARGS_CURRENT_MONTH["coa_slug"] = kwargs["coa_slug"]
|
||||||
|
|
||||||
ctx["current_month_url"] = reverse(f"{base_url}-month" ,kwargs=KWARGS_CURRENT_MONTH)
|
ctx["current_month_url"] = reverse(f"{base_url}-month", kwargs=KWARGS_CURRENT_MONTH)
|
||||||
|
|
||||||
quarter_urls = list()
|
quarter_urls = list()
|
||||||
ctx["quarter"] = context.get("quarter")
|
ctx["quarter"] = context.get("quarter")
|
||||||
@ -454,7 +453,7 @@ def po_item_table1(context, queryset):
|
|||||||
@register.inclusion_tag(
|
@register.inclusion_tag(
|
||||||
"purchase_orders/includes/po_item_formset.html", takes_context=True
|
"purchase_orders/includes/po_item_formset.html", takes_context=True
|
||||||
)
|
)
|
||||||
def po_item_formset_table(context, po_model, itemtxs_formset,user):
|
def po_item_formset_table(context, po_model, itemtxs_formset, user):
|
||||||
# print(len(itemtxs_formset.forms))
|
# print(len(itemtxs_formset.forms))
|
||||||
for form in itemtxs_formset.forms:
|
for form in itemtxs_formset.forms:
|
||||||
form.fields["item_model"].queryset = form.fields["item_model"].queryset.filter(
|
form.fields["item_model"].queryset = form.fields["item_model"].queryset.filter(
|
||||||
@ -473,10 +472,11 @@ def po_item_formset_table(context, po_model, itemtxs_formset,user):
|
|||||||
|
|
||||||
@register.inclusion_tag("bill/tags/bill_item_formset.html", takes_context=True)
|
@register.inclusion_tag("bill/tags/bill_item_formset.html", takes_context=True)
|
||||||
def bill_item_formset_table(context, item_formset):
|
def bill_item_formset_table(context, item_formset):
|
||||||
|
bill = BillModel.objects.get(uuid=context["view"].kwargs["bill_pk"])
|
||||||
for item in item_formset:
|
for item in item_formset:
|
||||||
if item:
|
if item:
|
||||||
item.initial['quantity'] = item.instance.po_quantity
|
item.initial["quantity"] = item.instance.po_quantity
|
||||||
item.initial['unit_cost'] = item.instance.po_unit_cost
|
item.initial["unit_cost"] = item.instance.po_unit_cost
|
||||||
# print(item.instance.po_quantity)
|
# print(item.instance.po_quantity)
|
||||||
# print(item.instance.po_unit_cost)
|
# print(item.instance.po_unit_cost)
|
||||||
# print(item.instance.po_total_amount)
|
# print(item.instance.po_total_amount)
|
||||||
@ -485,6 +485,7 @@ def bill_item_formset_table(context, item_formset):
|
|||||||
return {
|
return {
|
||||||
"dealer_slug": context["view"].kwargs["dealer_slug"],
|
"dealer_slug": context["view"].kwargs["dealer_slug"],
|
||||||
"entity_slug": context["view"].kwargs["entity_slug"],
|
"entity_slug": context["view"].kwargs["entity_slug"],
|
||||||
|
"bill": bill,
|
||||||
"bill_pk": context["view"].kwargs["bill_pk"],
|
"bill_pk": context["view"].kwargs["bill_pk"],
|
||||||
"total_amount__sum": context["total_amount__sum"],
|
"total_amount__sum": context["total_amount__sum"],
|
||||||
"item_formset": item_formset,
|
"item_formset": item_formset,
|
||||||
@ -661,7 +662,6 @@ def inventory_table(context, queryset):
|
|||||||
return ctx
|
return ctx
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
@register.filter
|
@register.filter
|
||||||
def count_checked(permissions, group_permission_ids):
|
def count_checked(permissions, group_permission_ids):
|
||||||
"""Count how many permissions are checked from the allowed list"""
|
"""Count how many permissions are checked from the allowed list"""
|
||||||
@ -669,10 +669,17 @@ def count_checked(permissions, group_permission_ids):
|
|||||||
return 0
|
return 0
|
||||||
return sum(1 for perm in permissions if perm.id in group_permission_ids)
|
return sum(1 for perm in permissions if perm.id in group_permission_ids)
|
||||||
|
|
||||||
|
|
||||||
# @register.filter
|
# @register.filter
|
||||||
# def count_checked(permissions, group_permission_ids):
|
# def count_checked(permissions, group_permission_ids):
|
||||||
# """Count how many permissions are checked from the allowed list"""
|
# """Count how many permissions are checked from the allowed list"""
|
||||||
# return sum(1 for perm in permissions if perm.id in group_permission_ids)
|
# return sum(1 for perm in permissions if perm.id in group_permission_ids)
|
||||||
|
|
||||||
|
@register.inclusion_tag('sales/tags/invoice_item_formset.html', takes_context=True)
|
||||||
|
def invoice_item_formset_table(context, itemtxs_formset):
|
||||||
|
return {
|
||||||
|
'entity_slug': context['view'].kwargs['entity_slug'],
|
||||||
|
'invoice_model': context['invoice'],
|
||||||
|
'total_amount__sum': context['total_amount__sum'],
|
||||||
|
'itemtxs_formset': itemtxs_formset,
|
||||||
|
}
|
||||||
|
|||||||
@ -472,5 +472,3 @@ class AuthenticationTest(TestCase):
|
|||||||
# self.assertEqual(finance_data["total_additionals"], Decimal("180"))
|
# self.assertEqual(finance_data["total_additionals"], Decimal("180"))
|
||||||
# self.assertEqual(finance_data["additionals"][0]["name"], "Service")
|
# self.assertEqual(finance_data["additionals"][0]["name"], "Service")
|
||||||
# self.assertEqual(finance_data["vat"], Decimal("0.20"))
|
# self.assertEqual(finance_data["vat"], Decimal("0.20"))
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
@ -12,7 +12,6 @@ urlpatterns = [
|
|||||||
path("signup/", views.dealer_signup, name="account_signup"),
|
path("signup/", views.dealer_signup, name="account_signup"),
|
||||||
path("", views.HomeView.as_view(), name="home"),
|
path("", views.HomeView.as_view(), name="home"),
|
||||||
path("<slug:dealer_slug>/", views.HomeView.as_view(), name="home"),
|
path("<slug:dealer_slug>/", views.HomeView.as_view(), name="home"),
|
||||||
|
|
||||||
# Tasks
|
# Tasks
|
||||||
path("legal/", views.terms_and_privacy, name="terms_and_privacy"),
|
path("legal/", views.terms_and_privacy, name="terms_and_privacy"),
|
||||||
# path('tasks/<int:task_id>/detail/', views.task_detail, name='task_detail'),
|
# path('tasks/<int:task_id>/detail/', views.task_detail, name='task_detail'),
|
||||||
@ -20,7 +19,11 @@ urlpatterns = [
|
|||||||
# path("user/<int:pk>/settings/", views.UserSettingsView.as_view(), name="user_settings"),
|
# path("user/<int:pk>/settings/", views.UserSettingsView.as_view(), name="user_settings"),
|
||||||
path("<slug:dealer_slug>/pricing/", views.pricing_page, name="pricing_page"),
|
path("<slug:dealer_slug>/pricing/", views.pricing_page, name="pricing_page"),
|
||||||
path("<slug:dealer_slug>/submit_plan/", views.submit_plan, name="submit_plan"),
|
path("<slug:dealer_slug>/submit_plan/", views.submit_plan, name="submit_plan"),
|
||||||
path("<slug:dealer_slug>/payment-callback/", views.payment_callback, name="payment_callback"),
|
path(
|
||||||
|
"<slug:dealer_slug>/payment-callback/",
|
||||||
|
views.payment_callback,
|
||||||
|
name="payment_callback",
|
||||||
|
),
|
||||||
#
|
#
|
||||||
path(
|
path(
|
||||||
"<slug:dealer_slug>/dealers/activity/",
|
"<slug:dealer_slug>/dealers/activity/",
|
||||||
@ -32,7 +35,11 @@ urlpatterns = [
|
|||||||
views.DealerSettingsView,
|
views.DealerSettingsView,
|
||||||
name="dealer_settings",
|
name="dealer_settings",
|
||||||
),
|
),
|
||||||
path("<slug:dealer_slug>/dealers/assign-car-makes/", views.assign_car_makes, name="assign_car_makes"),
|
path(
|
||||||
|
"<slug:dealer_slug>/dealers/assign-car-makes/",
|
||||||
|
views.assign_car_makes,
|
||||||
|
name="assign_car_makes",
|
||||||
|
),
|
||||||
path(
|
path(
|
||||||
"dashboards/manager/",
|
"dashboards/manager/",
|
||||||
views.ManagerDashboard.as_view(),
|
views.ManagerDashboard.as_view(),
|
||||||
@ -59,26 +66,34 @@ urlpatterns = [
|
|||||||
# 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(
|
||||||
"<slug:dealer_slug>/customers/create/", views.CustomerCreateView.as_view(), name="customer_create"
|
"<slug:dealer_slug>/customers/create/",
|
||||||
|
views.CustomerCreateView.as_view(),
|
||||||
|
name="customer_create",
|
||||||
|
),
|
||||||
|
path(
|
||||||
|
"<slug:dealer_slug>/customers/",
|
||||||
|
views.CustomerListView.as_view(),
|
||||||
|
name="customer_list",
|
||||||
),
|
),
|
||||||
path("<slug:dealer_slug>/customers/", views.CustomerListView.as_view(), name="customer_list"),
|
|
||||||
path(
|
path(
|
||||||
"<slug:dealer_slug>/customers/<slug:slug>/",
|
"<slug:dealer_slug>/customers/<slug:slug>/",
|
||||||
views.CustomerDetailView.as_view(),
|
views.CustomerDetailView.as_view(),
|
||||||
name="customer_detail",
|
name="customer_detail",
|
||||||
),
|
),
|
||||||
path(
|
# path(
|
||||||
"<slug:dealer_slug>/customers/<slug:slug>/add-note/",
|
# "<slug:dealer_slug>/customers/<slug:slug>/add-note/",
|
||||||
views.add_note_to_customer,
|
# views.add_note_to_customer,
|
||||||
name="add_note_to_customer",
|
# name="add_note_to_customer",
|
||||||
),
|
# ),
|
||||||
path(
|
path(
|
||||||
"<slug:dealer_slug>/customers/<slug:slug>/update/",
|
"<slug:dealer_slug>/customers/<slug:slug>/update/",
|
||||||
views.CustomerUpdateView.as_view(),
|
views.CustomerUpdateView.as_view(),
|
||||||
name="customer_update",
|
name="customer_update",
|
||||||
),
|
),
|
||||||
path(
|
path(
|
||||||
"<slug:dealer_slug>/customers/<slug:slug>/delete/", views.delete_customer, name="customer_delete"
|
"<slug:dealer_slug>/customers/<slug:slug>/delete/",
|
||||||
|
views.delete_customer,
|
||||||
|
name="customer_delete",
|
||||||
),
|
),
|
||||||
path(
|
path(
|
||||||
"<slug:dealer_slug>/customers/<slug:slug>/opportunities/create/",
|
"<slug:dealer_slug>/customers/<slug:slug>/opportunities/create/",
|
||||||
@ -94,21 +109,39 @@ urlpatterns = [
|
|||||||
views.LeadDetailView.as_view(),
|
views.LeadDetailView.as_view(),
|
||||||
name="lead_detail",
|
name="lead_detail",
|
||||||
),
|
),
|
||||||
path("<slug:dealer_slug>/update-lead-actions/", views.update_lead_actions, name="update_lead_actions"),
|
path(
|
||||||
path("<slug:dealer_slug>/crm/leads/lead_tracking/", views.lead_tracking, name="lead_tracking"),
|
"<slug:dealer_slug>/update-lead-actions/",
|
||||||
|
views.update_lead_actions,
|
||||||
|
name="update_lead_actions",
|
||||||
|
),
|
||||||
|
path(
|
||||||
|
"<slug:dealer_slug>/crm/leads/lead_tracking/",
|
||||||
|
views.lead_tracking,
|
||||||
|
name="lead_tracking",
|
||||||
|
),
|
||||||
path("<slug:dealer_slug>/crm/leads/lead_view/", views.lead_view, name="lead_view"),
|
path("<slug:dealer_slug>/crm/leads/lead_view/", views.lead_view, name="lead_view"),
|
||||||
path("<slug:dealer_slug>/crm/leads/", views.LeadListView.as_view(), name="lead_list"),
|
path(
|
||||||
|
"<slug:dealer_slug>/crm/leads/", views.LeadListView.as_view(), name="lead_list"
|
||||||
|
),
|
||||||
path(
|
path(
|
||||||
"<slug:dealer_slug>/crm/leads/<slug:slug>/update/",
|
"<slug:dealer_slug>/crm/leads/<slug:slug>/update/",
|
||||||
views.LeadUpdateView.as_view(),
|
views.LeadUpdateView.as_view(),
|
||||||
name="lead_update",
|
name="lead_update",
|
||||||
),
|
),
|
||||||
path("<slug:dealer_slug>/crm/leads/<slug:slug>/delete/", views.LeadDeleteView, name="lead_delete"),
|
|
||||||
path(
|
path(
|
||||||
"<slug:dealer_slug>/crm/leads/<slug:slug>/lead-convert/", views.lead_convert, name="lead_convert"
|
"<slug:dealer_slug>/crm/leads/<slug:slug>/delete/",
|
||||||
|
views.LeadDeleteView,
|
||||||
|
name="lead_delete",
|
||||||
),
|
),
|
||||||
path(
|
path(
|
||||||
"<slug:dealer_slug>/crm/leads/<int:pk>/delete-note/", views.delete_note, name="delete_note_to_lead"
|
"<slug:dealer_slug>/crm/leads/<slug:slug>/lead-convert/",
|
||||||
|
views.lead_convert,
|
||||||
|
name="lead_convert",
|
||||||
|
),
|
||||||
|
path(
|
||||||
|
"<slug:dealer_slug>/crm/leads/<int:pk>/delete-note/",
|
||||||
|
views.delete_note,
|
||||||
|
name="delete_note_to_lead",
|
||||||
),
|
),
|
||||||
path(
|
path(
|
||||||
"<slug:dealer_slug>/crm/<int:pk>/update-note/",
|
"<slug:dealer_slug>/crm/<int:pk>/update-note/",
|
||||||
@ -165,11 +198,11 @@ urlpatterns = [
|
|||||||
views.lead_transfer,
|
views.lead_transfer,
|
||||||
name="lead_transfer",
|
name="lead_transfer",
|
||||||
),
|
),
|
||||||
path(
|
# path(
|
||||||
"<slug:dealer_slug>/crm/opportunities/<slug:slug>/add_note/",
|
# "<slug:dealer_slug>/crm/opportunities/<slug:slug>/add_note/",
|
||||||
views.add_note_to_opportunity,
|
# views.add_note_to_opportunity,
|
||||||
name="add_note_to_opportunity",
|
# name="add_note_to_opportunity",
|
||||||
),
|
# ),
|
||||||
path(
|
path(
|
||||||
"<slug:dealer_slug>/crm/opportunities/create/",
|
"<slug:dealer_slug>/crm/opportunities/create/",
|
||||||
views.OpportunityCreateView.as_view(),
|
views.OpportunityCreateView.as_view(),
|
||||||
@ -216,9 +249,11 @@ urlpatterns = [
|
|||||||
# Notifications
|
# Notifications
|
||||||
path("notifications/stream/", views.sse_stream, name="sse_stream"),
|
path("notifications/stream/", views.sse_stream, name="sse_stream"),
|
||||||
path("notifications/fetch/", views.fetch_notifications, name="fetch_notifications"),
|
path("notifications/fetch/", views.fetch_notifications, name="fetch_notifications"),
|
||||||
|
path(
|
||||||
path("notifications/list/", views.NotificationListView.as_view(), name="notifications_history"),
|
"notifications/list/",
|
||||||
|
views.NotificationListView.as_view(),
|
||||||
|
name="notifications_history",
|
||||||
|
),
|
||||||
path(
|
path(
|
||||||
"notifications/<int:notification_id>/mark_as_read/",
|
"notifications/<int:notification_id>/mark_as_read/",
|
||||||
views.mark_notification_as_read,
|
views.mark_notification_as_read,
|
||||||
@ -235,9 +270,19 @@ urlpatterns = [
|
|||||||
#######################################################
|
#######################################################
|
||||||
# Vendor URLs
|
# Vendor URLs
|
||||||
#######################################################
|
#######################################################
|
||||||
path("<slug:dealer_slug>/vendors/create/", views.VendorCreateView.as_view(), name="vendor_create"),
|
path(
|
||||||
path("<slug:dealer_slug>/vendors", views.VendorListView.as_view(), name="vendor_list"),
|
"<slug:dealer_slug>/vendors/create/",
|
||||||
path("<slug:dealer_slug>/vendors/<slug:slug>/", views.vendorDetailView, name="vendor_detail"),
|
views.VendorCreateView.as_view(),
|
||||||
|
name="vendor_create",
|
||||||
|
),
|
||||||
|
path(
|
||||||
|
"<slug:dealer_slug>/vendors", views.VendorListView.as_view(), name="vendor_list"
|
||||||
|
),
|
||||||
|
path(
|
||||||
|
"<slug:dealer_slug>/vendors/<slug:slug>/",
|
||||||
|
views.vendorDetailView,
|
||||||
|
name="vendor_detail",
|
||||||
|
),
|
||||||
path(
|
path(
|
||||||
"<slug:dealer_slug>/vendors/<slug:slug>/update/",
|
"<slug:dealer_slug>/vendors/<slug:slug>/update/",
|
||||||
views.VendorUpdateView.as_view(),
|
views.VendorUpdateView.as_view(),
|
||||||
@ -447,26 +492,99 @@ urlpatterns = [
|
|||||||
# ),
|
# ),
|
||||||
# Users URLs
|
# Users URLs
|
||||||
path("<slug:dealer_slug>/user/", views.UserListView.as_view(), name="user_list"),
|
path("<slug:dealer_slug>/user/", views.UserListView.as_view(), name="user_list"),
|
||||||
path("<slug:dealer_slug>/user/create/", views.UserCreateView.as_view(), name="user_create"),
|
path(
|
||||||
path("<slug:dealer_slug>/user/<slug:slug>/", views.UserDetailView.as_view(), name="user_detail"),
|
"<slug:dealer_slug>/user/create/",
|
||||||
path("<slug:dealer_slug>/user/<slug:slug>/groups/", views.UserGroupView, name="user_groups"),
|
views.UserCreateView.as_view(),
|
||||||
path("<slug:dealer_slug>/user/<slug:slug>/update/", views.UserUpdateView.as_view(), name="user_update"),
|
name="user_create",
|
||||||
path("<slug:dealer_slug>/user/<slug:slug>/confirm/", views.UserDeleteview, name="user_delete"),
|
),
|
||||||
path("<slug:dealer_slug>/group/create/", views.GroupCreateView.as_view(), name="group_create"),
|
path(
|
||||||
path("<slug:dealer_slug>/group/<int:pk>/update/", views.GroupUpdateView.as_view(), name="group_update"),
|
"<slug:dealer_slug>/user/<slug:slug>/",
|
||||||
path("<slug:dealer_slug>/group/<int:pk>/", views.GroupDetailView.as_view(), name="group_detail"),
|
views.UserDetailView.as_view(),
|
||||||
|
name="user_detail",
|
||||||
|
),
|
||||||
|
path(
|
||||||
|
"<slug:dealer_slug>/user/<slug:slug>/groups/",
|
||||||
|
views.UserGroupView,
|
||||||
|
name="user_groups",
|
||||||
|
),
|
||||||
|
path(
|
||||||
|
"<slug:dealer_slug>/user/<slug:slug>/update/",
|
||||||
|
views.UserUpdateView.as_view(),
|
||||||
|
name="user_update",
|
||||||
|
),
|
||||||
|
path(
|
||||||
|
"<slug:dealer_slug>/user/<slug:slug>/confirm/",
|
||||||
|
views.UserDeleteview,
|
||||||
|
name="user_delete",
|
||||||
|
),
|
||||||
|
path(
|
||||||
|
"<slug:dealer_slug>/group/create/",
|
||||||
|
views.GroupCreateView.as_view(),
|
||||||
|
name="group_create",
|
||||||
|
),
|
||||||
|
path(
|
||||||
|
"<slug:dealer_slug>/group/<int:pk>/update/",
|
||||||
|
views.GroupUpdateView.as_view(),
|
||||||
|
name="group_update",
|
||||||
|
),
|
||||||
|
path(
|
||||||
|
"<slug:dealer_slug>/group/<int:pk>/",
|
||||||
|
views.GroupDetailView.as_view(),
|
||||||
|
name="group_detail",
|
||||||
|
),
|
||||||
path("<slug:dealer_slug>/group/", views.GroupListView.as_view(), name="group_list"),
|
path("<slug:dealer_slug>/group/", views.GroupListView.as_view(), name="group_list"),
|
||||||
path("<slug:dealer_slug>/group/<int:pk>/confirm/", views.GroupDeleteview, name="group_delete"),
|
path(
|
||||||
path("<slug:dealer_slug>/group/<int:pk>/permission/", views.GroupPermissionView, name="group_permission"),
|
"<slug:dealer_slug>/group/<int:pk>/confirm/",
|
||||||
path("<slug:dealer_slug>/organizations/create/", views.OrganizationCreateView.as_view(), name="organization_create"),
|
views.GroupDeleteview,
|
||||||
path("<slug:dealer_slug>/organizations/", views.OrganizationListView.as_view(), name="organization_list"),
|
name="group_delete",
|
||||||
path("<slug:dealer_slug>/organizations/<slug:slug>/", views.OrganizationDetailView.as_view(), name="organization_detail"),
|
),
|
||||||
path("<slug:dealer_slug>/organizations/<slug:slug>/update/", views.OrganizationUpdateView.as_view(), name="organization_update"),
|
path(
|
||||||
path("<slug:dealer_slug>/organizations/<slug:slug>/delete/", views.OrganizationDeleteView, name="organization_delete"),
|
"<slug:dealer_slug>/group/<int:pk>/permission/",
|
||||||
path("representatives/", views.RepresentativeListView.as_view(), name="representative_list"),
|
views.GroupPermissionView,
|
||||||
path("representatives/<int:pk>/", views.RepresentativeDetailView.as_view(), name="representative_detail"),
|
name="group_permission",
|
||||||
path("representatives/create/", views.RepresentativeCreateView.as_view(),name="representative_create"),
|
),
|
||||||
path("representatives/<int:pk>/update/",
|
path(
|
||||||
|
"<slug:dealer_slug>/organizations/create/",
|
||||||
|
views.OrganizationCreateView.as_view(),
|
||||||
|
name="organization_create",
|
||||||
|
),
|
||||||
|
path(
|
||||||
|
"<slug:dealer_slug>/organizations/",
|
||||||
|
views.OrganizationListView.as_view(),
|
||||||
|
name="organization_list",
|
||||||
|
),
|
||||||
|
path(
|
||||||
|
"<slug:dealer_slug>/organizations/<slug:slug>/",
|
||||||
|
views.OrganizationDetailView.as_view(),
|
||||||
|
name="organization_detail",
|
||||||
|
),
|
||||||
|
path(
|
||||||
|
"<slug:dealer_slug>/organizations/<slug:slug>/update/",
|
||||||
|
views.OrganizationUpdateView.as_view(),
|
||||||
|
name="organization_update",
|
||||||
|
),
|
||||||
|
path(
|
||||||
|
"<slug:dealer_slug>/organizations/<slug:slug>/delete/",
|
||||||
|
views.OrganizationDeleteView,
|
||||||
|
name="organization_delete",
|
||||||
|
),
|
||||||
|
path(
|
||||||
|
"representatives/",
|
||||||
|
views.RepresentativeListView.as_view(),
|
||||||
|
name="representative_list",
|
||||||
|
),
|
||||||
|
path(
|
||||||
|
"representatives/<int:pk>/",
|
||||||
|
views.RepresentativeDetailView.as_view(),
|
||||||
|
name="representative_detail",
|
||||||
|
),
|
||||||
|
path(
|
||||||
|
"representatives/create/",
|
||||||
|
views.RepresentativeCreateView.as_view(),
|
||||||
|
name="representative_create",
|
||||||
|
),
|
||||||
|
path(
|
||||||
|
"representatives/<int:pk>/update/",
|
||||||
views.RepresentativeUpdateView.as_view(),
|
views.RepresentativeUpdateView.as_view(),
|
||||||
name="representative_update",
|
name="representative_update",
|
||||||
),
|
),
|
||||||
@ -475,9 +593,15 @@ urlpatterns = [
|
|||||||
views.RepresentativeDeleteView.as_view(),
|
views.RepresentativeDeleteView.as_view(),
|
||||||
name="representative_delete",
|
name="representative_delete",
|
||||||
),
|
),
|
||||||
path("<slug:dealer_slug>/ledgers/<slug:entity_slug>/", views.LedgerModelListView.as_view(), name="ledger_list"),
|
|
||||||
path(
|
path(
|
||||||
"<slug:dealer_slug>/ledgers/<slug:entity_slug>/create/", views.LedgerModelCreateView.as_view(), name="ledger_create"
|
"<slug:dealer_slug>/ledgers/<slug:entity_slug>/",
|
||||||
|
views.LedgerModelListView.as_view(),
|
||||||
|
name="ledger_list",
|
||||||
|
),
|
||||||
|
path(
|
||||||
|
"<slug:dealer_slug>/ledgers/<slug:entity_slug>/create/",
|
||||||
|
views.LedgerModelCreateView.as_view(),
|
||||||
|
name="ledger_create",
|
||||||
),
|
),
|
||||||
path(
|
path(
|
||||||
"<slug:dealer_slug>/ledgers/<slug:entity_slug>/detail/<uuid:pk>/",
|
"<slug:dealer_slug>/ledgers/<slug:entity_slug>/detail/<uuid:pk>/",
|
||||||
@ -698,7 +822,6 @@ urlpatterns = [
|
|||||||
views.update_estimate_additionals,
|
views.update_estimate_additionals,
|
||||||
name="update_estimate_additionals",
|
name="update_estimate_additionals",
|
||||||
),
|
),
|
||||||
|
|
||||||
###############################################
|
###############################################
|
||||||
# Invoice
|
# Invoice
|
||||||
###############################################
|
###############################################
|
||||||
@ -713,10 +836,15 @@ urlpatterns = [
|
|||||||
name="invoice_create",
|
name="invoice_create",
|
||||||
),
|
),
|
||||||
path(
|
path(
|
||||||
"<slug:dealer_slug>/sales/invoices/<uuid:pk>/",
|
"<slug:dealer_slug>/sales/<slug:entity_slug>/invoices/<uuid:pk>/",
|
||||||
views.InvoiceDetailView.as_view(),
|
views.InvoiceDetailView.as_view(),
|
||||||
name="invoice_detail",
|
name="invoice_detail",
|
||||||
),
|
),
|
||||||
|
# path(
|
||||||
|
# "<slug:dealer_slug>/sales/<slug:entity_slug>/invoices/<uuid:pk>/update",
|
||||||
|
# views.InvoiceDetailView.as_view(),
|
||||||
|
# name="invoice_update",
|
||||||
|
# ),
|
||||||
path(
|
path(
|
||||||
"<slug:dealer_slug>/sales/invoices/<uuid:pk>/preview/",
|
"<slug:dealer_slug>/sales/invoices/<uuid:pk>/preview/",
|
||||||
views.InvoicePreviewView.as_view(),
|
views.InvoicePreviewView.as_view(),
|
||||||
@ -753,7 +881,17 @@ urlpatterns = [
|
|||||||
views.PaymentCreateView,
|
views.PaymentCreateView,
|
||||||
name="payment_create",
|
name="payment_create",
|
||||||
),
|
),
|
||||||
# path("sales/payments/create/", views.PaymentCreateView, name="payment_create"),
|
# path(
|
||||||
|
# "<slug:dealer_slug>/sales/payments/<slug:entity_slug>/<uuid:invoice_pk>/create/",
|
||||||
|
# views.InvoiceModelUpdateView.as_view(),
|
||||||
|
# name="invoice_update",
|
||||||
|
# ),
|
||||||
|
# path(
|
||||||
|
# "<slug:dealer_slug>/sales/payments/<slug:entity_slug>/<uuid:invoice_pk>/create/",
|
||||||
|
# views.InvoiceModelUpdateView.as_view(),
|
||||||
|
# name="payment_create",
|
||||||
|
# ),
|
||||||
|
# path("<slug:dealer_slug>/sales/payments/create/", views.PaymentCreateView, name="payment_create"),
|
||||||
path(
|
path(
|
||||||
"<slug:dealer_slug>/sales/payments/<uuid:pk>/payment_details/",
|
"<slug:dealer_slug>/sales/payments/<uuid:pk>/payment_details/",
|
||||||
views.PaymentDetailView,
|
views.PaymentDetailView,
|
||||||
@ -771,7 +909,9 @@ urlpatterns = [
|
|||||||
# path('sales/journal/<uuid:pk>/create/', views.JournalEntryCreateView.as_view(), name='journal_create'),
|
# path('sales/journal/<uuid:pk>/create/', views.JournalEntryCreateView.as_view(), name='journal_create'),
|
||||||
# Items
|
# Items
|
||||||
path(
|
path(
|
||||||
"<slug:dealer_slug>/items/services/", views.ItemServiceListView.as_view(), name="item_service_list"
|
"<slug:dealer_slug>/items/services/",
|
||||||
|
views.ItemServiceListView.as_view(),
|
||||||
|
name="item_service_list",
|
||||||
),
|
),
|
||||||
path(
|
path(
|
||||||
"<slug:dealer_slug>/items/services/create/",
|
"<slug:dealer_slug>/items/services/create/",
|
||||||
@ -838,7 +978,7 @@ urlpatterns = [
|
|||||||
),
|
),
|
||||||
############################################################
|
############################################################
|
||||||
############################################################
|
############################################################
|
||||||
#BILL MARK AS
|
# BILL MARK AS
|
||||||
path(
|
path(
|
||||||
"<slug:dealer_slug>/items/bills/<slug:entity_slug>/actions/<uuid:bill_pk>/mark-as-draft/",
|
"<slug:dealer_slug>/items/bills/<slug:entity_slug>/actions/<uuid:bill_pk>/mark-as-draft/",
|
||||||
views.BillModelActionMarkAsDraftView.as_view(),
|
views.BillModelActionMarkAsDraftView.as_view(),
|
||||||
@ -884,7 +1024,6 @@ urlpatterns = [
|
|||||||
views.BillModelActionForceMigrateView.as_view(),
|
views.BillModelActionForceMigrateView.as_view(),
|
||||||
name="bill-action-force-migrate",
|
name="bill-action-force-migrate",
|
||||||
),
|
),
|
||||||
|
|
||||||
# orders
|
# orders
|
||||||
path("orders/", views.OrderListView.as_view(), name="order_list_view"),
|
path("orders/", views.OrderListView.as_view(), name="order_list_view"),
|
||||||
# BALANCE SHEET Reports...
|
# BALANCE SHEET Reports...
|
||||||
@ -1013,7 +1152,11 @@ urlpatterns = [
|
|||||||
),
|
),
|
||||||
# Admin Management...
|
# Admin Management...
|
||||||
path("<slug:dealer_slug>/management/", views.management_view, name="management"),
|
path("<slug:dealer_slug>/management/", views.management_view, name="management"),
|
||||||
path("<slug:dealer_slug>/management/user_management/", views.user_management, name="user_management"),
|
path(
|
||||||
|
"<slug:dealer_slug>/management/user_management/",
|
||||||
|
views.user_management,
|
||||||
|
name="user_management",
|
||||||
|
),
|
||||||
path(
|
path(
|
||||||
"<slug:dealer_slug>/management/<str:content_type>/<slug:slug>/activate_account/",
|
"<slug:dealer_slug>/management/<str:content_type>/<slug:slug>/activate_account/",
|
||||||
views.activate_account,
|
views.activate_account,
|
||||||
@ -1107,6 +1250,22 @@ urlpatterns = [
|
|||||||
views.PurchaseOrderMarkAsVoidView.as_view(),
|
views.PurchaseOrderMarkAsVoidView.as_view(),
|
||||||
name="po-action-mark-as-void",
|
name="po-action-mark-as-void",
|
||||||
),
|
),
|
||||||
|
|
||||||
|
# reports
|
||||||
|
path(
|
||||||
|
"<slug:dealer_slug>/purchase-report/",
|
||||||
|
views.purchase_report_view,
|
||||||
|
name="po-report",
|
||||||
|
),
|
||||||
|
path('purchase-report/<slug:dealer_slug>/csv/', views.purchase_report_csv_export, name='purchase-report-csv-export'),
|
||||||
|
|
||||||
|
path(
|
||||||
|
"<slug:dealer_slug>/car-sale-report/",
|
||||||
|
views.car_sale_report_view,
|
||||||
|
name="car-sale-report",
|
||||||
|
),
|
||||||
|
path('car-sale-report/<slug:dealer_slug>/csv/', views.car_sale_report_csv_export, name='car-sale-report-csv-export'),
|
||||||
|
|
||||||
]
|
]
|
||||||
|
|
||||||
handler404 = "inventory.views.custom_page_not_found_view"
|
handler404 = "inventory.views.custom_page_not_found_view"
|
||||||
|
|||||||
@ -14,6 +14,7 @@ from django_q.tasks import async_task
|
|||||||
from django.core.mail import send_mail
|
from django.core.mail import send_mail
|
||||||
from plans.models import AbstractOrder
|
from plans.models import AbstractOrder
|
||||||
from django_ledger.models import (
|
from django_ledger.models import (
|
||||||
|
EstimateModel,
|
||||||
InvoiceModel,
|
InvoiceModel,
|
||||||
BillModel,
|
BillModel,
|
||||||
VendorModel,
|
VendorModel,
|
||||||
@ -25,9 +26,12 @@ from django.utils.translation import gettext_lazy as _
|
|||||||
from django.contrib.contenttypes.models import ContentType
|
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
|
||||||
|
from django.db import transaction
|
||||||
import logging
|
import logging
|
||||||
logger=logging.getLogger(__name__)
|
from django_ledger.io import roles
|
||||||
|
|
||||||
|
logger = logging.getLogger(__name__)
|
||||||
|
|
||||||
|
|
||||||
def make_random_password(
|
def make_random_password(
|
||||||
length=10, allowed_chars="abcdefghjkmnpqrstuvwxyzABCDEFGHJKLMNPQRSTUVWXYZ23456789"
|
length=10, allowed_chars="abcdefghjkmnpqrstuvwxyzABCDEFGHJKLMNPQRSTUVWXYZ23456789"
|
||||||
@ -453,30 +457,35 @@ def get_financial_values(model):
|
|||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
def set_invoice_payment(dealer, entity, invoice, amount, payment_method):
|
# def set_invoice_payment(dealer, entity, invoice, amount, payment_method):
|
||||||
"""
|
# """
|
||||||
Processes and applies a payment for a specified invoice. This function calculates
|
# Processes and applies a payment for a specified invoice. This function calculates
|
||||||
finance details, handles associated account transactions, and updates the invoice
|
# finance details, handles associated account transactions, and updates the invoice
|
||||||
status accordingly.
|
# status accordingly.
|
||||||
|
|
||||||
:param dealer: Dealer object responsible for processing the payment
|
# :param dealer: Dealer object responsible for processing the payment
|
||||||
:type dealer: Dealer
|
# :type dealer: Dealer
|
||||||
:param entity: Entity object associated with the invoice and payment
|
# :param entity: Entity object associated with the invoice and payment
|
||||||
:type entity: Entity
|
# :type entity: Entity
|
||||||
:param invoice: The invoice object for which the payment is being made
|
# :param invoice: The invoice object for which the payment is being made
|
||||||
:type invoice: Invoice
|
# :type invoice: Invoice
|
||||||
:param amount: The amount being paid towards the invoice
|
# :param amount: The amount being paid towards the invoice
|
||||||
:type amount: Decimal
|
# :type amount: Decimal
|
||||||
:param payment_method: The payment method used for the transaction
|
# :param payment_method: The payment method used for the transaction
|
||||||
:type payment_method: str
|
# :type payment_method: str
|
||||||
:return: None
|
# :return: None
|
||||||
"""
|
# """
|
||||||
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)
|
# if invoice.can_migrate():
|
||||||
invoice.save()
|
# invoice.migrate_state(
|
||||||
|
# user_model=dealer.user,
|
||||||
|
# entity_slug=entity.slug
|
||||||
|
# )
|
||||||
|
# invoice.make_payment(amount)
|
||||||
|
# invoice.save()
|
||||||
|
|
||||||
|
|
||||||
def set_bill_payment(dealer, entity, bill, amount, payment_method):
|
def set_bill_payment(dealer, entity, bill, amount, payment_method):
|
||||||
@ -997,12 +1006,25 @@ 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)
|
if isinstance(model, InvoiceModel):
|
||||||
|
self.dealer = models.Dealer.objects.get(entity=model.ce_model.entity)
|
||||||
|
self.extra_info = models.ExtraInfo.objects.get(
|
||||||
|
dealer=self.dealer,
|
||||||
|
content_type=ContentType.objects.get_for_model(model.ce_model),
|
||||||
|
object_id=model.ce_model.pk,
|
||||||
|
)
|
||||||
|
elif isinstance(model, EstimateModel):
|
||||||
|
self.dealer = models.Dealer.objects.get(entity=model.entity)
|
||||||
|
self.extra_info = models.ExtraInfo.objects.get(
|
||||||
|
dealer=self.dealer,
|
||||||
|
content_type=ContentType.objects.get_for_model(model),
|
||||||
|
object_id=model.pk,
|
||||||
|
)
|
||||||
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(dealer=self.dealer,is_active=True).first()
|
vat = models.VatRate.objects.filter(dealer=self.dealer,is_active=True).first()
|
||||||
@ -1010,77 +1032,62 @@ class CarFinanceCalculator:
|
|||||||
raise ObjectDoesNotExist("No active VAT rate found")
|
raise ObjectDoesNotExist("No active VAT rate found")
|
||||||
return vat.rate
|
return vat.rate
|
||||||
|
|
||||||
|
def _get_additional_services(self):
|
||||||
|
return [x for item in self.item_transactions
|
||||||
|
for x in item.item_model.car.additional_services
|
||||||
|
]
|
||||||
def _get_item_transactions(self):
|
def _get_item_transactions(self):
|
||||||
return self.model.get_itemtxs_data()[0].all()
|
return self.model.get_itemtxs_data()[0].all()
|
||||||
|
|
||||||
|
def get_items(self):
|
||||||
|
return self._get_item_transactions()
|
||||||
@staticmethod
|
@staticmethod
|
||||||
def _get_quantity(item):
|
def _get_quantity(item):
|
||||||
return item.ce_quantity or item.quantity
|
return item.ce_quantity or item.quantity
|
||||||
|
|
||||||
def _get_nested_value(self, item, *keys):
|
# def _get_nested_value(self, item, *keys):
|
||||||
current = item.item_model.additional_info
|
# current = item.item_model.additional_info
|
||||||
for key in keys:
|
# for key in keys:
|
||||||
current = current.get(key, {})
|
# current = current.get(key, {})
|
||||||
return current
|
# return current
|
||||||
|
|
||||||
def _get_car_data(self, item):
|
def _get_car_data(self, item):
|
||||||
quantity = self._get_quantity(item)
|
quantity = self._get_quantity(item)
|
||||||
car_finance = self._get_nested_value(item, self.CAR_FINANCE_KEY)
|
car = item.item_model.car
|
||||||
car_info = self._get_nested_value(item, self.CAR_INFO_KEY)
|
unit_price = Decimal(car.finances.marked_price)
|
||||||
unit_price = Decimal(car_finance.get("marked_price", 0))
|
|
||||||
return {
|
return {
|
||||||
"item_number": item.item_model.item_number,
|
"item_number": item.item_model.item_number,
|
||||||
"vin": car_info.get("vin"),
|
"vin": car.vin, #car_info.get("vin"),
|
||||||
"make": car_info.get("make"),
|
"make": car.id_car_make ,#car_info.get("make"),
|
||||||
"model": car_info.get("model"),
|
"model": car.id_car_model ,#car_info.get("model"),
|
||||||
"year": car_info.get("year"),
|
"year": car.year ,# car_info.get("year"),
|
||||||
"logo": getattr(item.item_model.car.id_car_make, "logo", ""),
|
"logo": car.logo, # getattr(car.id_car_make, "logo", ""),
|
||||||
"trim": car_info.get("trim"),
|
"trim": car.id_car_trim ,# car_info.get("trim"),
|
||||||
"mileage": car_info.get("mileage"),
|
"mileage": car.mileage ,# car_info.get("mileage"),
|
||||||
"cost_price": car_finance.get("cost_price"),
|
"cost_price": car.finances.cost_price,
|
||||||
"selling_price": car_finance.get("selling_price"),
|
"selling_price": car.finances.selling_price,
|
||||||
"marked_price": car_finance.get("marked_price"),
|
"marked_price": car.finances.marked_price,
|
||||||
"discount": car_finance.get("discount_amount"),
|
"discount": car.finances.discount_amount,
|
||||||
"quantity": quantity,
|
"quantity": quantity,
|
||||||
"unit_price": unit_price,
|
"unit_price": unit_price,
|
||||||
"total": unit_price * Decimal(quantity),
|
"total": unit_price * Decimal(quantity),
|
||||||
"total_vat": car_finance.get("total_vat"),
|
"total_vat": car.finances.total_vat,
|
||||||
"additional_services": self._get_nested_value(
|
"additional_services": car.additional_services,# self._get_nested_value(
|
||||||
item, self.ADDITIONAL_SERVICES_KEY
|
#item, self.ADDITIONAL_SERVICES_KEY
|
||||||
),
|
#),
|
||||||
}
|
}
|
||||||
|
|
||||||
def _get_additional_services(self):
|
|
||||||
return [
|
|
||||||
{
|
|
||||||
"name": service.get("name"),
|
|
||||||
"price": service.get("price"),
|
|
||||||
"taxable": service.get("taxable"),
|
|
||||||
"price_": service.get("price_"),
|
|
||||||
}
|
|
||||||
for item in self.item_transactions
|
|
||||||
for service in self._get_nested_value(item, self.ADDITIONAL_SERVICES_KEY)
|
|
||||||
or []
|
|
||||||
]
|
|
||||||
|
|
||||||
def calculate_totals(self):
|
def calculate_totals(self):
|
||||||
total_price = sum(
|
total_price = sum(
|
||||||
Decimal(self._get_nested_value(item, self.CAR_FINANCE_KEY, "marked_price"))
|
Decimal(item.item_model.car.finances.marked_price)
|
||||||
* int(self._get_quantity(item))
|
|
||||||
for item in self.item_transactions
|
for item in self.item_transactions
|
||||||
)
|
)
|
||||||
total_additionals = sum(
|
total_additionals = sum(
|
||||||
Decimal(x.get("price_")) for x in self._get_additional_services()
|
Decimal(item.price_) for item in self._get_additional_services())
|
||||||
)
|
|
||||||
|
|
||||||
total_discount = self.extra_info.data.get("discount",0)
|
total_discount = self.extra_info.data.get("discount",0)
|
||||||
|
|
||||||
# total_discount = sum(
|
|
||||||
# Decimal(
|
|
||||||
# self._get_nested_value(item, self.CAR_FINANCE_KEY, "discount_amount")
|
|
||||||
# )
|
|
||||||
# for item in self.item_transactions
|
|
||||||
# )
|
|
||||||
total_price_discounted = total_price
|
total_price_discounted = total_price
|
||||||
if total_discount:
|
if total_discount:
|
||||||
total_price_discounted = total_price - Decimal(total_discount)
|
total_price_discounted = total_price - Decimal(total_discount)
|
||||||
@ -1089,11 +1096,11 @@ class CarFinanceCalculator:
|
|||||||
return {
|
return {
|
||||||
"total_price_before_discount": round(
|
"total_price_before_discount": round(
|
||||||
total_price, 2
|
total_price, 2
|
||||||
), # total_price_before_discount,
|
),
|
||||||
"total_price": round(total_price_discounted, 2), # total_price_discounted,
|
"total_price": round(total_price_discounted, 2),
|
||||||
"total_vat_amount": round(total_vat_amount, 2), # total_vat_amount,
|
"total_vat_amount": round(total_vat_amount, 2),
|
||||||
"total_discount": round(Decimal(total_discount)),
|
"total_discount": round(Decimal(total_discount)),
|
||||||
"total_additionals": round(total_additionals, 2), # total_additionals,
|
"total_additionals": round(total_additionals, 2),
|
||||||
"grand_total": round(
|
"grand_total": round(
|
||||||
total_price_discounted + total_vat_amount + total_additionals, 2
|
total_price_discounted + total_vat_amount + total_additionals, 2
|
||||||
),
|
),
|
||||||
@ -1113,9 +1120,167 @@ class CarFinanceCalculator:
|
|||||||
"total_discount": totals["total_discount"],
|
"total_discount": totals["total_discount"],
|
||||||
"total_additionals": totals["total_additionals"],
|
"total_additionals": totals["total_additionals"],
|
||||||
"grand_total": totals["grand_total"],
|
"grand_total": totals["grand_total"],
|
||||||
"additionals": self.additional_services,
|
"additionals": self._get_additional_services(),
|
||||||
"vat": self.vat_rate,
|
"vat": self.vat_rate,
|
||||||
}
|
}
|
||||||
|
# class CarFinanceCalculator:
|
||||||
|
# """
|
||||||
|
# Class responsible for calculating car financing details.
|
||||||
|
|
||||||
|
# This class provides methods and attributes required for calculating various
|
||||||
|
# aspects related to car financing, such as VAT calculation, pricing, discounts,
|
||||||
|
# and additional services. It processes data about cars, computes totals (e.g.,
|
||||||
|
# price, VAT, discounts), and aggregates the financial data for reporting or
|
||||||
|
# further processing.
|
||||||
|
|
||||||
|
# :ivar model: The data model passed to the calculator for retrieving transaction data.
|
||||||
|
# :type model: Any
|
||||||
|
# :ivar vat_rate: The current active VAT rate retrieved from the database.
|
||||||
|
# :type vat_rate: Decimal
|
||||||
|
# :ivar item_transactions: A collection of item transactions retrieved from the model.
|
||||||
|
# :type item_transactions: list
|
||||||
|
# :ivar additional_services: A list of additional services with details (e.g., name, price, taxable status).
|
||||||
|
# :type additional_services: list
|
||||||
|
# """
|
||||||
|
|
||||||
|
# VAT_OBJ_NAME = "vat_rate"
|
||||||
|
# CAR_FINANCE_KEY = "car_finance"
|
||||||
|
# CAR_INFO_KEY = "car_info"
|
||||||
|
# ADDITIONAL_SERVICES_KEY = "additional_services"
|
||||||
|
|
||||||
|
# def __init__(self, model):
|
||||||
|
# if isinstance(model, InvoiceModel):
|
||||||
|
# self.dealer = models.Dealer.objects.get(entity=model.ce_model.entity)
|
||||||
|
# self.extra_info = models.ExtraInfo.objects.get(
|
||||||
|
# dealer=self.dealer,
|
||||||
|
# content_type=ContentType.objects.get_for_model(model.ce_model),
|
||||||
|
# object_id=model.ce_model.pk,
|
||||||
|
# )
|
||||||
|
# elif isinstance(model, EstimateModel):
|
||||||
|
# self.dealer = models.Dealer.objects.get(entity=model.entity)
|
||||||
|
# self.extra_info = models.ExtraInfo.objects.get(
|
||||||
|
# dealer=self.dealer,
|
||||||
|
# content_type=ContentType.objects.get_for_model(model),
|
||||||
|
# object_id=model.pk,
|
||||||
|
# )
|
||||||
|
# self.model = model
|
||||||
|
# self.vat_rate = self._get_vat_rate()
|
||||||
|
# self.item_transactions = self._get_item_transactions()
|
||||||
|
# self.additional_services = self._get_additional_services()
|
||||||
|
|
||||||
|
|
||||||
|
# def _get_vat_rate(self):
|
||||||
|
# vat = models.VatRate.objects.filter(dealer=self.dealer, is_active=True).first()
|
||||||
|
# if not vat:
|
||||||
|
# raise ObjectDoesNotExist("No active VAT rate found")
|
||||||
|
# return vat.rate
|
||||||
|
|
||||||
|
# def _get_item_transactions(self):
|
||||||
|
# return self.model.get_itemtxs_data()[0].all()
|
||||||
|
|
||||||
|
# @staticmethod
|
||||||
|
# def _get_quantity(item):
|
||||||
|
# return item.ce_quantity or item.quantity
|
||||||
|
|
||||||
|
# def _get_nested_value(self, item, *keys):
|
||||||
|
# current = item.item_model.additional_info
|
||||||
|
# for key in keys:
|
||||||
|
# current = current.get(key, {})
|
||||||
|
# return current
|
||||||
|
|
||||||
|
# def _get_car_data(self, item):
|
||||||
|
# quantity = self._get_quantity(item)
|
||||||
|
# car_finance = self._get_nested_value(item, self.CAR_FINANCE_KEY)
|
||||||
|
# car_info = self._get_nested_value(item, self.CAR_INFO_KEY)
|
||||||
|
# unit_price = Decimal(car_finance.get("marked_price", 0))
|
||||||
|
# return {
|
||||||
|
# "item_number": item.item_model.item_number,
|
||||||
|
# "vin": car_info.get("vin"),
|
||||||
|
# "make": car_info.get("make"),
|
||||||
|
# "model": car_info.get("model"),
|
||||||
|
# "year": car_info.get("year"),
|
||||||
|
# "logo": getattr(item.item_model.car.id_car_make, "logo", ""),
|
||||||
|
# "trim": car_info.get("trim"),
|
||||||
|
# "mileage": car_info.get("mileage"),
|
||||||
|
# "cost_price": car_finance.get("cost_price"),
|
||||||
|
# "selling_price": car_finance.get("selling_price"),
|
||||||
|
# "marked_price": car_finance.get("marked_price"),
|
||||||
|
# "discount": car_finance.get("discount_amount"),
|
||||||
|
# "quantity": quantity,
|
||||||
|
# "unit_price": unit_price,
|
||||||
|
# "total": unit_price * Decimal(quantity),
|
||||||
|
# "total_vat": car_finance.get("total_vat"),
|
||||||
|
# "additional_services": self._get_nested_value(
|
||||||
|
# item, self.ADDITIONAL_SERVICES_KEY
|
||||||
|
# ),
|
||||||
|
# }
|
||||||
|
|
||||||
|
# def _get_additional_services(self):
|
||||||
|
# return [
|
||||||
|
# {
|
||||||
|
# "name": service.get("name"),
|
||||||
|
# "price": service.get("price"),
|
||||||
|
# "taxable": service.get("taxable"),
|
||||||
|
# "price_": service.get("price_"),
|
||||||
|
# }
|
||||||
|
# for item in self.item_transactions
|
||||||
|
# for service in self._get_nested_value(item, self.ADDITIONAL_SERVICES_KEY)
|
||||||
|
# or []
|
||||||
|
# ]
|
||||||
|
|
||||||
|
# def calculate_totals(self):
|
||||||
|
# total_price = sum(
|
||||||
|
# Decimal(self._get_nested_value(item, self.CAR_FINANCE_KEY, "marked_price"))
|
||||||
|
# * int(self._get_quantity(item))
|
||||||
|
# for item in self.item_transactions
|
||||||
|
# )
|
||||||
|
# total_additionals = sum(
|
||||||
|
# Decimal(x.get("price_")) for x in self._get_additional_services()
|
||||||
|
# )
|
||||||
|
|
||||||
|
# total_discount = self.extra_info.data.get("discount", 0)
|
||||||
|
|
||||||
|
# # total_discount = sum(
|
||||||
|
# # Decimal(
|
||||||
|
# # self._get_nested_value(item, self.CAR_FINANCE_KEY, "discount_amount")
|
||||||
|
# # )
|
||||||
|
# # for item in self.item_transactions
|
||||||
|
# # )
|
||||||
|
# total_price_discounted = total_price
|
||||||
|
# if total_discount:
|
||||||
|
# total_price_discounted = total_price - Decimal(total_discount)
|
||||||
|
# total_vat_amount = total_price_discounted * self.vat_rate
|
||||||
|
|
||||||
|
# return {
|
||||||
|
# "total_price_before_discount": round(
|
||||||
|
# total_price, 2
|
||||||
|
# ), # total_price_before_discount,
|
||||||
|
# "total_price": round(total_price_discounted, 2), # total_price_discounted,
|
||||||
|
# "total_vat_amount": round(total_vat_amount, 2), # total_vat_amount,
|
||||||
|
# "total_discount": round(Decimal(total_discount)),
|
||||||
|
# "total_additionals": round(total_additionals, 2), # total_additionals,
|
||||||
|
# "grand_total": round(
|
||||||
|
# total_price_discounted + total_vat_amount + total_additionals, 2
|
||||||
|
# ),
|
||||||
|
# }
|
||||||
|
|
||||||
|
# def get_finance_data(self):
|
||||||
|
# totals = self.calculate_totals()
|
||||||
|
# return {
|
||||||
|
# "cars": [self._get_car_data(item) for item in self.item_transactions],
|
||||||
|
# "quantity": sum(
|
||||||
|
# self._get_quantity(item) for item in self.item_transactions
|
||||||
|
# ),
|
||||||
|
# "total_price": totals["total_price"],
|
||||||
|
# "total_price_before_discount": totals["total_price_before_discount"],
|
||||||
|
# "total_vat": totals["total_vat_amount"] + totals["total_price"],
|
||||||
|
# "total_vat_amount": totals["total_vat_amount"],
|
||||||
|
# "total_discount": totals["total_discount"],
|
||||||
|
# "total_additionals": totals["total_additionals"],
|
||||||
|
# "grand_total": totals["grand_total"],
|
||||||
|
# "additionals": self.additional_services,
|
||||||
|
# "vat": self.vat_rate,
|
||||||
|
# }
|
||||||
|
|
||||||
|
|
||||||
def get_item_transactions(txs):
|
def get_item_transactions(txs):
|
||||||
@ -1172,135 +1337,224 @@ def get_local_name(self):
|
|||||||
return getattr(self, "name", None)
|
return getattr(self, "name", None)
|
||||||
|
|
||||||
|
|
||||||
def handle_account_process(invoice, amount, finance_data):
|
|
||||||
|
@transaction.atomic
|
||||||
|
def set_invoice_payment(dealer, entity, invoice, amount, payment_method):
|
||||||
"""
|
"""
|
||||||
Processes accounting transactions based on an invoice, financial data,
|
Records the customer payment (`make_payment`) and posts the full
|
||||||
and related entity accounts configuration. This function handles the
|
accounting (sales + VAT + COGS + Inventory).
|
||||||
creation of accounts if they do not already exist, and processes journal
|
|
||||||
entries and transactions.
|
|
||||||
|
|
||||||
:param invoice: The invoice object to process transactions for.
|
|
||||||
:type invoice: InvoiceModel
|
|
||||||
:param amount: Total monetary value for the transaction.
|
|
||||||
:type amount: Decimal
|
|
||||||
:param finance_data: Dictionary containing financial details such as
|
|
||||||
'grand_total', 'total_vat_amount', and other related data.
|
|
||||||
:type finance_data: dict
|
|
||||||
:return: None
|
|
||||||
"""
|
"""
|
||||||
for i in invoice.get_itemtxs_data()[0]:
|
invoice.make_payment(amount)
|
||||||
# car = models.Car.objects.get(vin=invoice.get_itemtxs_data()[0].first().item_model.name)
|
invoice.save()
|
||||||
car = i.item_model.car
|
|
||||||
entity = invoice.ledger.entity
|
|
||||||
coa = entity.get_default_coa()
|
|
||||||
|
|
||||||
cash_account = (
|
_post_sale_and_cogs(invoice, dealer)
|
||||||
entity.get_all_accounts()
|
|
||||||
.filter(role_default=True, role=roles.ASSET_CA_CASH)
|
|
||||||
.first()
|
|
||||||
)
|
|
||||||
inventory_account = car.get_inventory_account()
|
|
||||||
revenue_account = car.get_revenue_account()
|
|
||||||
cogs_account = car.get_cogs_account()
|
|
||||||
|
|
||||||
# make_account = entity.get_all_accounts().filter(name=car.id_car_make.name,role=roles.COGS).first()
|
def _post_sale_and_cogs(invoice, dealer):
|
||||||
# if not make_account:
|
"""
|
||||||
# last_account = entity.get_all_accounts().filter(role=roles.COGS).order_by('-created').first()
|
For every car line on the invoice:
|
||||||
# if len(last_account.code) == 4:
|
1) Cash / A-R / VAT / Revenue journal
|
||||||
# code = f"{int(last_account.code)}{1:03d}"
|
2) COGS / Inventory journal
|
||||||
# elif len(last_account.code) > 4:
|
"""
|
||||||
# code = f"{int(last_account.code)+1}"
|
entity = invoice.ledger.entity
|
||||||
|
calc = CarFinanceCalculator(invoice)
|
||||||
|
data = calc.get_finance_data()
|
||||||
|
|
||||||
# make_account = entity.create_account(
|
cash_acc = entity.get_all_accounts().filter(role_default=True, role=roles.ASSET_CA_CASH).first()
|
||||||
# name=car.id_car_make.name,
|
ar_acc = entity.get_all_accounts().filter(role_default=True, role=roles.ASSET_CA_RECEIVABLES).first()
|
||||||
# code=code,
|
vat_acc = entity.get_all_accounts().filter(role_default=True, role=roles.LIABILITY_CL_TAXES_PAYABLE).first()
|
||||||
# role=roles.COGS,
|
car_rev = entity.get_all_accounts().filter(role_default=True, role=roles.INCOME_OPERATIONAL).first()
|
||||||
# coa_model=coa,
|
add_rev = entity.get_all_accounts().filter(role_default=True, role=roles.INCOME_OPERATIONAL).first()
|
||||||
# balance_type="debit",
|
cogs_acc = entity.get_all_accounts().filter(role_default=True, role=roles.COGS).first()
|
||||||
# active=True
|
inv_acc = entity.get_all_accounts().filter(role_default=True, role=roles.ASSET_CA_INVENTORY).first()
|
||||||
# )
|
|
||||||
|
|
||||||
# # get or create additional services account
|
for car_data in data['cars']:
|
||||||
# additional_services_account = entity.get_default_coa_accounts().filter(name="Additional Services",role=roles.COGS).first()
|
car = invoice.get_itemtxs_data()[0].filter(
|
||||||
# if not additional_services_account:
|
item_model__car__vin=car_data['vin']
|
||||||
# last_account = entity.get_all_accounts().filter(role=roles.COGS).order_by('-created').first()
|
).first().item_model.car
|
||||||
# if len(last_account.code) == 4:
|
qty = Decimal(car_data['quantity'])
|
||||||
# code = f"{int(last_account.code)}{1:03d}"
|
|
||||||
# elif len(last_account.code) > 4:
|
|
||||||
# code = f"{int(last_account.code)+1}"
|
|
||||||
|
|
||||||
# additional_services_account = entity.create_account(
|
net_car_price = Decimal(car_data['total'])
|
||||||
# name="Additional Services",
|
net_add_price = Decimal(data['total_additionals'])
|
||||||
# code=code,
|
vat_amount = Decimal(data['total_vat_amount']) * qty
|
||||||
# role=roles.COGS,
|
grand_total = net_car_price + net_add_price + vat_amount
|
||||||
# coa_model=coa,
|
cost_total = Decimal(car_data['cost_price']) * qty
|
||||||
# balance_type="debit",
|
|
||||||
# active=True
|
|
||||||
# )
|
|
||||||
|
|
||||||
# inventory_account = entity.get_default_coa_accounts().filter(role=roles.ASSET_CA_INVENTORY).first()
|
# ------------------------------------------------------------------
|
||||||
|
# 2A. Journal: Cash / A-R / VAT / Sales
|
||||||
|
# ------------------------------------------------------------------
|
||||||
|
|
||||||
# vat_payable_account = entity.get_default_coa_accounts().get(name="VAT Payable", active=True)
|
je_sale = JournalEntryModel.objects.create(
|
||||||
|
|
||||||
journal = JournalEntryModel.objects.create(
|
|
||||||
posted=False,
|
|
||||||
description=f"Payment for Invoice {invoice.invoice_number}",
|
|
||||||
ledger=invoice.ledger,
|
ledger=invoice.ledger,
|
||||||
|
description=f"Sale {car.vin}",
|
||||||
|
origin=f"Invoice {invoice.invoice_number}",
|
||||||
locked=False,
|
locked=False,
|
||||||
origin=f"Sale of {car.id_car_make.name}{car.vin}: Invoice {invoice.invoice_number}",
|
posted=False
|
||||||
)
|
)
|
||||||
|
# Dr Cash (what the customer paid)
|
||||||
TransactionModel.objects.create(
|
TransactionModel.objects.create(
|
||||||
journal_entry=journal,
|
journal_entry=je_sale,
|
||||||
account=cash_account,
|
account=cash_acc,
|
||||||
amount=Decimal(finance_data.get("grand_total")),
|
amount=grand_total,
|
||||||
tx_type="debit",
|
tx_type='debit'
|
||||||
description="",
|
|
||||||
)
|
)
|
||||||
|
|
||||||
|
# # Cr A/R (clear the receivable)
|
||||||
|
# TransactionModel.objects.create(
|
||||||
|
# journal_entry=je_sale,
|
||||||
|
# account=ar_acc,
|
||||||
|
# amount=grand_total,
|
||||||
|
# tx_type='credit'
|
||||||
|
# )
|
||||||
|
|
||||||
|
# Cr VAT Payable
|
||||||
TransactionModel.objects.create(
|
TransactionModel.objects.create(
|
||||||
journal_entry=journal,
|
journal_entry=je_sale,
|
||||||
account=revenue_account,
|
account=vat_acc,
|
||||||
amount=Decimal(finance_data.get("grand_total")),
|
amount=vat_amount,
|
||||||
tx_type="credit",
|
tx_type='credit'
|
||||||
description="",
|
|
||||||
)
|
)
|
||||||
|
|
||||||
journal_cogs = JournalEntryModel.objects.create(
|
# Cr Sales – Car
|
||||||
posted=False,
|
TransactionModel.objects.create(
|
||||||
description=f"COGS of {car.id_car_make.name}{car.vin}: Invoice {invoice.invoice_number}",
|
journal_entry=je_sale,
|
||||||
|
account=car_rev,
|
||||||
|
amount=net_car_price,
|
||||||
|
tx_type='credit'
|
||||||
|
)
|
||||||
|
|
||||||
|
if net_add_price > 0:
|
||||||
|
# Cr Sales – Additional Services
|
||||||
|
TransactionModel.objects.create(
|
||||||
|
journal_entry=je_sale,
|
||||||
|
account=add_rev,
|
||||||
|
amount=net_add_price,
|
||||||
|
tx_type='credit'
|
||||||
|
)
|
||||||
|
|
||||||
|
# ------------------------------------------------------------------
|
||||||
|
# 2B. Journal: COGS / Inventory reduction
|
||||||
|
# ------------------------------------------------------------------
|
||||||
|
je_cogs = JournalEntryModel.objects.create(
|
||||||
ledger=invoice.ledger,
|
ledger=invoice.ledger,
|
||||||
|
description=f"COGS {car.vin}",
|
||||||
|
origin=f"Invoice {invoice.invoice_number}",
|
||||||
locked=False,
|
locked=False,
|
||||||
origin="Payment",
|
posted=False
|
||||||
)
|
)
|
||||||
|
|
||||||
|
# Dr COGS
|
||||||
TransactionModel.objects.create(
|
TransactionModel.objects.create(
|
||||||
journal_entry=journal_cogs,
|
journal_entry=je_cogs,
|
||||||
account=cogs_account,
|
account=cogs_acc,
|
||||||
amount=Decimal(car.finances.cost_price),
|
amount=cost_total,
|
||||||
tx_type="debit",
|
tx_type='debit'
|
||||||
description="",
|
|
||||||
)
|
)
|
||||||
|
|
||||||
|
# Cr Inventory
|
||||||
TransactionModel.objects.create(
|
TransactionModel.objects.create(
|
||||||
journal_entry=journal_cogs,
|
journal_entry=je_cogs,
|
||||||
account=inventory_account,
|
account=inv_acc,
|
||||||
amount=Decimal(car.finances.cost_price),
|
amount=cost_total,
|
||||||
tx_type="credit",
|
tx_type='credit'
|
||||||
description="",
|
|
||||||
)
|
)
|
||||||
try:
|
# ------------------------------------------------------------------
|
||||||
car.item_model.for_inventory = False
|
# 2C. Update car state flags inside the same transaction
|
||||||
logger.debug(f"Set item_model.for_inventory to False for car {car.vin}.")
|
# ------------------------------------------------------------------
|
||||||
except Exception as e:
|
entity.get_items_inventory().filter(name=car.vin).update(for_inventory=False)
|
||||||
logger.error(
|
# car.item_model.for_inventory = False
|
||||||
f"Error updating item_model.for_inventory for car {car.vin} (Invoice {invoice.invoice_number}): {e}",
|
# car.item_model.save(update_fields=['for_inventory'])
|
||||||
exc_info=True
|
car.finances.selling_price = grand_total
|
||||||
)
|
|
||||||
|
|
||||||
|
|
||||||
car.finances.is_sold = True
|
car.finances.is_sold = True
|
||||||
car.finances.save()
|
car.finances.save()
|
||||||
car.item_model.save()
|
# def handle_account_process(invoice, amount, finance_data):
|
||||||
|
# """
|
||||||
|
# Processes accounting transactions based on an invoice, financial data,
|
||||||
|
# and related entity accounts configuration. This function handles the
|
||||||
|
# creation of accounts if they do not already exist, and processes journal
|
||||||
|
# entries and transactions.
|
||||||
|
|
||||||
|
# :param invoice: The invoice object to process transactions for.
|
||||||
|
# :type invoice: InvoiceModel
|
||||||
|
# :param amount: Total monetary value for the transaction.
|
||||||
|
# :type amount: Decimal
|
||||||
|
# :param finance_data: Dictionary containing financial details such as
|
||||||
|
# 'grand_total', 'total_vat_amount', and other related data.
|
||||||
|
# :type finance_data: dict
|
||||||
|
# :return: None
|
||||||
|
# """
|
||||||
|
# for i in invoice.get_itemtxs_data()[0]:
|
||||||
|
# # car = models.Car.objects.get(vin=invoice.get_itemtxs_data()[0].first().item_model.name)
|
||||||
|
# car = i.item_model.car
|
||||||
|
# entity = invoice.ledger.entity
|
||||||
|
# coa = entity.get_default_coa()
|
||||||
|
|
||||||
|
# cash_account = (
|
||||||
|
# entity.get_all_accounts()
|
||||||
|
# .filter(role_default=True, role=roles.ASSET_CA_CASH)
|
||||||
|
# .first()
|
||||||
|
# )
|
||||||
|
# inventory_account = car.get_inventory_account()
|
||||||
|
# revenue_account = car.get_revenue_account()
|
||||||
|
# cogs_account = car.get_cogs_account()
|
||||||
|
|
||||||
|
# journal = JournalEntryModel.objects.create(
|
||||||
|
# posted=False,
|
||||||
|
# description=f"Payment for Invoice {invoice.invoice_number}",
|
||||||
|
# ledger=invoice.ledger,
|
||||||
|
# locked=False,
|
||||||
|
# origin=f"Sale of {car.id_car_make.name}{car.vin}: Invoice {invoice.invoice_number}",
|
||||||
|
# )
|
||||||
|
|
||||||
|
# TransactionModel.objects.create(
|
||||||
|
# journal_entry=journal,
|
||||||
|
# account=cash_account,
|
||||||
|
# amount=Decimal(finance_data.get("grand_total")),
|
||||||
|
# tx_type="debit",
|
||||||
|
# description="",
|
||||||
|
# )
|
||||||
|
|
||||||
|
# TransactionModel.objects.create(
|
||||||
|
# journal_entry=journal,
|
||||||
|
# account=revenue_account,
|
||||||
|
# amount=Decimal(finance_data.get("grand_total")),
|
||||||
|
# tx_type="credit",
|
||||||
|
# description="",
|
||||||
|
# )
|
||||||
|
|
||||||
|
# journal_cogs = JournalEntryModel.objects.create(
|
||||||
|
# posted=False,
|
||||||
|
# description=f"COGS of {car.id_car_make.name}{car.vin}: Invoice {invoice.invoice_number}",
|
||||||
|
# ledger=invoice.ledger,
|
||||||
|
# locked=False,
|
||||||
|
# origin="Payment",
|
||||||
|
# )
|
||||||
|
# TransactionModel.objects.create(
|
||||||
|
# journal_entry=journal_cogs,
|
||||||
|
# account=cogs_account,
|
||||||
|
# amount=Decimal(car.finances.cost_price),
|
||||||
|
# tx_type="debit",
|
||||||
|
# description="",
|
||||||
|
# )
|
||||||
|
|
||||||
|
# TransactionModel.objects.create(
|
||||||
|
# journal_entry=journal_cogs,
|
||||||
|
# account=inventory_account,
|
||||||
|
# amount=Decimal(car.finances.cost_price),
|
||||||
|
# tx_type="credit",
|
||||||
|
# description="",
|
||||||
|
# )
|
||||||
|
# try:
|
||||||
|
# car.item_model.for_inventory = False
|
||||||
|
# logger.debug(f"Set item_model.for_inventory to False for car {car.vin}.")
|
||||||
|
# except Exception as e:
|
||||||
|
# logger.error(
|
||||||
|
# f"Error updating item_model.for_inventory for car {car.vin} (Invoice {invoice.invoice_number}): {e}",
|
||||||
|
# exc_info=True,
|
||||||
|
# )
|
||||||
|
|
||||||
|
# car.finances.is_sold = True
|
||||||
|
# car.finances.save()
|
||||||
|
# car.item_model.save()
|
||||||
|
|
||||||
# TransactionModel.objects.create(
|
# TransactionModel.objects.create(
|
||||||
# journal_entry=journal,
|
# journal_entry=journal,
|
||||||
|
|||||||
2296
inventory/views.py
|
After Width: | Height: | Size: 450 B |
|
After Width: | Height: | Size: 1.2 KiB |
BIN
static/images/logos/staff/customer2.jpg
Normal file
|
After Width: | Height: | Size: 30 KiB |
BIN
static/images/logos/users/customer1.jpg
Normal file
|
After Width: | Height: | Size: 50 KiB |
BIN
static/images/user-logo.png
Normal file
|
After Width: | Height: | Size: 814 B |
51
static/js/formSubmitHandler.js
Normal file
@ -0,0 +1,51 @@
|
|||||||
|
// static/js/formSubmitHandler.js
|
||||||
|
|
||||||
|
document.addEventListener('DOMContentLoaded', function() {
|
||||||
|
// Initialize all forms with submit buttons
|
||||||
|
const forms = document.querySelectorAll('form');
|
||||||
|
|
||||||
|
forms.forEach(form => {
|
||||||
|
const submitBtn = form.querySelector('button[type="submit"]');
|
||||||
|
|
||||||
|
if (submitBtn) {
|
||||||
|
// Store original button HTML
|
||||||
|
if (!submitBtn.dataset.originalHtml) {
|
||||||
|
submitBtn.dataset.originalHtml = submitBtn.innerHTML;
|
||||||
|
}
|
||||||
|
|
||||||
|
form.addEventListener('submit', function(e) {
|
||||||
|
// Only proceed if form is valid
|
||||||
|
if (form.checkValidity()) {
|
||||||
|
disableSubmitButton(submitBtn);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Disable and show loading state on submit button
|
||||||
|
* @param {HTMLElement} button - The submit button element
|
||||||
|
*/
|
||||||
|
function disableSubmitButton(button) {
|
||||||
|
button.disabled = true;
|
||||||
|
button.innerHTML = `
|
||||||
|
<span class="submit-spinner">
|
||||||
|
<i class="fas fa-spinner fa-spin me-1"></i>
|
||||||
|
</span>
|
||||||
|
<span class="submit-text">${button.dataset.savingText || 'Processing...'}</span>
|
||||||
|
`;
|
||||||
|
button.classList.add('submitting');
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Reset submit button to original state
|
||||||
|
* @param {HTMLElement} button - The submit button element
|
||||||
|
*/
|
||||||
|
function resetSubmitButton(button) {
|
||||||
|
if (button.dataset.originalHtml) {
|
||||||
|
button.innerHTML = button.dataset.originalHtml;
|
||||||
|
}
|
||||||
|
button.disabled = false;
|
||||||
|
button.classList.remove('submitting');
|
||||||
|
}
|
||||||
BIN
static/user-logo.png
Normal file
|
After Width: | Height: | Size: 814 B |
@ -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;
|
||||||
|
|||||||
|
After Width: | Height: | Size: 450 B |
|
After Width: | Height: | Size: 740 B |
|
After Width: | Height: | Size: 746 B |
|
After Width: | Height: | Size: 530 B |
|
After Width: | Height: | Size: 312 B |
|
After Width: | Height: | Size: 1.2 KiB |
BIN
staticfiles/images/logos/staff/customer2.jpg
Normal file
|
After Width: | Height: | Size: 30 KiB |
BIN
staticfiles/images/logos/staff/customer3.jpg
Normal file
|
After Width: | Height: | Size: 39 KiB |
BIN
staticfiles/images/logos/staff/customer4.jpg
Normal file
|
After Width: | Height: | Size: 46 KiB |
BIN
staticfiles/images/logos/staff/customer4_VhAPbzd.jpg
Normal file
|
After Width: | Height: | Size: 46 KiB |
BIN
staticfiles/images/logos/staff/customer4_yoUBW1d.jpg
Normal file
|
After Width: | Height: | Size: 46 KiB |
BIN
staticfiles/images/logos/users/customer1.jpg
Normal file
|
After Width: | Height: | Size: 50 KiB |
BIN
staticfiles/images/user-logo.png
Normal file
|
After Width: | Height: | Size: 814 B |
51
staticfiles/js/formSubmitHandler.js
Normal file
@ -0,0 +1,51 @@
|
|||||||
|
// static/js/formSubmitHandler.js
|
||||||
|
|
||||||
|
document.addEventListener('DOMContentLoaded', function() {
|
||||||
|
// Initialize all forms with submit buttons
|
||||||
|
const forms = document.querySelectorAll('form');
|
||||||
|
|
||||||
|
forms.forEach(form => {
|
||||||
|
const submitBtn = form.querySelector('button[type="submit"]');
|
||||||
|
|
||||||
|
if (submitBtn) {
|
||||||
|
// Store original button HTML
|
||||||
|
if (!submitBtn.dataset.originalHtml) {
|
||||||
|
submitBtn.dataset.originalHtml = submitBtn.innerHTML;
|
||||||
|
}
|
||||||
|
|
||||||
|
form.addEventListener('submit', function(e) {
|
||||||
|
// Only proceed if form is valid
|
||||||
|
if (form.checkValidity()) {
|
||||||
|
disableSubmitButton(submitBtn);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Disable and show loading state on submit button
|
||||||
|
* @param {HTMLElement} button - The submit button element
|
||||||
|
*/
|
||||||
|
function disableSubmitButton(button) {
|
||||||
|
button.disabled = true;
|
||||||
|
button.innerHTML = `
|
||||||
|
<span class="submit-spinner">
|
||||||
|
<i class="fas fa-spinner fa-spin me-1"></i>
|
||||||
|
</span>
|
||||||
|
<span class="submit-text">${button.dataset.savingText || 'Processing...'}</span>
|
||||||
|
`;
|
||||||
|
button.classList.add('submitting');
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Reset submit button to original state
|
||||||
|
* @param {HTMLElement} button - The submit button element
|
||||||
|
*/
|
||||||
|
function resetSubmitButton(button) {
|
||||||
|
if (button.dataset.originalHtml) {
|
||||||
|
button.innerHTML = button.dataset.originalHtml;
|
||||||
|
}
|
||||||
|
button.disabled = false;
|
||||||
|
button.classList.remove('submitting');
|
||||||
|
}
|
||||||
BIN
staticfiles/sounds/tone.wav
Normal file
BIN
staticfiles/user-logo.png
Normal file
|
After Width: | Height: | Size: 814 B |
2
t1.py
@ -19,4 +19,4 @@ def get_models_for_make():
|
|||||||
|
|
||||||
models = get_models_for_make()
|
models = get_models_for_make()
|
||||||
for model in models:
|
for model in models:
|
||||||
print(model["Model_Name"])
|
print(model["Model_Name"])
|
||||||
|
|||||||
@ -1,35 +1,60 @@
|
|||||||
{% load static i18n %}
|
{% load static i18n %}
|
||||||
<html>
|
<html>
|
||||||
<head>
|
<head>
|
||||||
<meta charset="utf-8" />
|
<meta charset="utf-8" />
|
||||||
<meta http-equiv="X-UA-Compatible" content="IE=edge" />
|
<meta http-equiv="X-UA-Compatible" content="IE=edge" />
|
||||||
<title>Access Forbidden</title>
|
<title>Access Forbidden</title>
|
||||||
<meta name="description" content="" />
|
<meta name="description" content="" />
|
||||||
<meta name="viewport" content="width=device-width, initial-scale=1" />
|
<meta name="viewport" content="width=device-width, initial-scale=1" />
|
||||||
<link rel="apple-touch-icon" sizes="180x180" href="{% static 'assets/img/favicons/apple-touch-icon.png' %}" />
|
<link rel="apple-touch-icon"
|
||||||
<link rel="icon" type="image/png" sizes="32x32" href="{% static 'assets/img/favicons/favicon-32x32.png' %}" />
|
sizes="180x180"
|
||||||
<link rel="icon" type="image/png" sizes="16x16" href="{% static 'assets/img/favicons/favicon-16x16.png' %}" />
|
href="{% static 'assets/img/favicons/apple-touch-icon.png' %}" />
|
||||||
<link rel="shortcut icon" type="image/x-icon" href="{% static 'assets/img/favicons/favicon.ico' %}" />
|
<link rel="icon"
|
||||||
<link rel="manifest" href="{% static 'assets/img/favicons/manifest.json' %}" />
|
type="image/png"
|
||||||
<meta name="msapplication-TileImage" content="{% static 'assets/img/favicons/mstile-150x150.png' %}" />
|
sizes="32x32"
|
||||||
<meta name="theme-color" content="#ffffff" />
|
href="{% static 'assets/img/favicons/favicon-32x32.png' %}" />
|
||||||
<script src="{% static 'vendors/simplebar/simplebar.min.js' %}"></script>
|
<link rel="icon"
|
||||||
<script src="{% static 'assets/js/config.js' %}"></script>
|
type="image/png"
|
||||||
|
sizes="16x16"
|
||||||
<!-- =============================================== -->
|
href="{% static 'assets/img/favicons/favicon-16x16.png' %}" />
|
||||||
<!-- Stylesheets -->
|
<link rel="shortcut icon"
|
||||||
<!-- =============================================== -->
|
type="image/x-icon"
|
||||||
<link rel="preconnect" href="https://fonts.googleapis.com" />
|
href="{% static 'assets/img/favicons/favicon.ico' %}" />
|
||||||
<link rel="preconnect" href="https://fonts.gstatic.com" crossorigin="" />
|
<link rel="manifest"
|
||||||
<link href="https://fonts.googleapis.com/css2?family=Nunito+Sans:wght@300;400;600;700;800;900&display=swap" rel="stylesheet" />
|
href="{% static 'assets/img/favicons/manifest.json' %}" />
|
||||||
<link href="{% static 'vendors/simplebar/simplebar.min.css' %}" rel="stylesheet" />
|
<meta name="msapplication-TileImage"
|
||||||
<link rel="stylesheet" href="https://unicons.iconscout.com/release/v4.0.8/css/line.css" />
|
content="{% static 'assets/img/favicons/mstile-150x150.png' %}" />
|
||||||
<link href="{% static 'assets/css/theme-rtl.min.css' %}" type="text/css" rel="stylesheet" id="style-rtl" />
|
<meta name="theme-color" content="#ffffff" />
|
||||||
<link href="{% static 'assets/css/theme.min.css' %}" type="text/css" rel="stylesheet" id="style-default" />
|
<script src="{% static 'vendors/simplebar/simplebar.min.js' %}"></script>
|
||||||
<link href="{% static 'assets/css/user-rtl.min.css' %}" type="text/css" rel="stylesheet" id="user-style-rtl" />
|
<script src="{% static 'assets/js/config.js' %}"></script>
|
||||||
<link href="{% static 'assets/css/user.min.css' %}" type="text/css" rel="stylesheet" id="user-style-default" />
|
<!-- =============================================== -->
|
||||||
|
<!-- Stylesheets -->
|
||||||
<style>
|
<!-- =============================================== -->
|
||||||
|
<link rel="preconnect" href="https://fonts.googleapis.com" />
|
||||||
|
<link rel="preconnect" href="https://fonts.gstatic.com" crossorigin="" />
|
||||||
|
<link href="https://fonts.googleapis.com/css2?family=Nunito+Sans:wght@300;400;600;700;800;900&display=swap"
|
||||||
|
rel="stylesheet" />
|
||||||
|
<link href="{% static 'vendors/simplebar/simplebar.min.css' %}"
|
||||||
|
rel="stylesheet" />
|
||||||
|
<link rel="stylesheet"
|
||||||
|
href="https://unicons.iconscout.com/release/v4.0.8/css/line.css" />
|
||||||
|
<link href="{% static 'assets/css/theme-rtl.min.css' %}"
|
||||||
|
type="text/css"
|
||||||
|
rel="stylesheet"
|
||||||
|
id="style-rtl" />
|
||||||
|
<link href="{% static 'assets/css/theme.min.css' %}"
|
||||||
|
type="text/css"
|
||||||
|
rel="stylesheet"
|
||||||
|
id="style-default" />
|
||||||
|
<link href="{% static 'assets/css/user-rtl.min.css' %}"
|
||||||
|
type="text/css"
|
||||||
|
rel="stylesheet"
|
||||||
|
id="user-style-rtl" />
|
||||||
|
<link href="{% static 'assets/css/user.min.css' %}"
|
||||||
|
type="text/css"
|
||||||
|
rel="stylesheet"
|
||||||
|
id="user-style-default" />
|
||||||
|
<style>
|
||||||
body, html {
|
body, html {
|
||||||
height: 100%;
|
height: 100%;
|
||||||
margin: 0;
|
margin: 0;
|
||||||
@ -51,42 +76,48 @@
|
|||||||
.text-center {
|
.text-center {
|
||||||
text-align: center;
|
text-align: center;
|
||||||
}
|
}
|
||||||
</style>
|
</style>
|
||||||
</head>
|
</head>
|
||||||
|
<body>
|
||||||
<body>
|
<main class="main" id="top">
|
||||||
<main class="main" id="top">
|
<div class="px-3">
|
||||||
<div class="px-3">
|
<div class="row min-vh-100 flex-center p-5">
|
||||||
<div class="row min-vh-100 flex-center p-5">
|
<div class="col-12 col-xl-10 col-xxl-8">
|
||||||
<div class="col-12 col-xl-10 col-xxl-8">
|
<div class="row justify-content-center align-items-center g-5">
|
||||||
<div class="row justify-content-center align-items-center g-5">
|
<div class="col-12 col-lg-6 text-center order-lg-1">
|
||||||
<div class="col-12 col-lg-6 text-center order-lg-1">
|
<img class="img-fluid w-md-50 w-lg-100 d-light-none"
|
||||||
<img class="img-fluid w-md-50 w-lg-100 d-light-none" src="{% static 'images/spot-illustrations/dark_403-illustration.png' %}" alt="" width="540" />
|
src="{% static 'images/spot-illustrations/dark_403-illustration.png' %}"
|
||||||
</div>
|
alt=""
|
||||||
<div class="col-12 col-lg-6 text-center text-lg-start">
|
width="540" />
|
||||||
<img class="img-fluid mb-6 w-50 w-lg-75 d-dark-none" src="{% static 'images/spot-illustrations/403.png' %}" alt="" />
|
</div>
|
||||||
<h2 class="text-body-secondary fw-bolder mb-3">Access Forbidden!</h2>
|
<div class="col-12 col-lg-6 text-center text-lg-start">
|
||||||
<p class="text-body mb-5">
|
<img class="img-fluid mb-6 w-50 w-lg-75 d-dark-none"
|
||||||
Halt! Thou art endeavouring to trespass upon a realm not granted unto thee.<br class="d-none d-md-block d-lg-none" />granted unto thee.
|
src="{% static 'images/spot-illustrations/403.png' %}"
|
||||||
</p><a class="btn btn-lg btn-phoenix-primary" href="{% url 'home' %}">Go Home</a>
|
alt="" />
|
||||||
</div>
|
<h2 class="text-body-secondary fw-bolder mb-3">Access Forbidden!</h2>
|
||||||
|
<p class="text-body mb-5">
|
||||||
|
Halt! Thou art endeavouring to trespass upon a realm not granted unto thee.
|
||||||
|
<br class="d-none d-md-block d-lg-none" />
|
||||||
|
granted unto thee.
|
||||||
|
</p>
|
||||||
|
<a class="btn btn-lg btn-phoenix-primary" href="{% url 'home' %}">Go Home</a>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</main>
|
||||||
</div>
|
<script src="{% static 'vendors/bootstrap/bootstrap.min.js' %}"></script>
|
||||||
</div>
|
<script src="{% static 'js/phoenix.js' %}"></script>
|
||||||
</main>
|
<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/anchorjs/anchor.min.js' %}"></script>
|
||||||
<script src="{% static 'js/phoenix.js' %}"></script>
|
<script src="{% static 'vendors/is/is.min.js' %}"></script>
|
||||||
<script src="{% static 'vendors/popper/popper.min.js' %}"></script>
|
<script src="{% static 'vendors/fontawesome/all.min.js' %}"></script>
|
||||||
<script src="{% static 'vendors/bootstrap/bootstrap.min.js' %}"></script>
|
<script src="{% static 'vendors/lodash/lodash.min.js' %}"></script>
|
||||||
<script src="{% static 'vendors/anchorjs/anchor.min.js' %}"></script>
|
<script src="{% static 'vendors/list.js/list.min.js' %}"></script>
|
||||||
<script src="{% static 'vendors/is/is.min.js' %}"></script>
|
<script src="{% static 'vendors/feather-icons/feather.min.js' %}"></script>
|
||||||
<script src="{% static 'vendors/fontawesome/all.min.js' %}"></script>
|
<script src="{% static 'vendors/dayjs/dayjs.min.js' %}"></script>
|
||||||
<script src="{% static 'vendors/lodash/lodash.min.js' %}"></script>
|
<script src="{% static 'assets/js/phoenix.js' %}"></script>
|
||||||
<script src="{% static 'vendors/list.js/list.min.js' %}"></script>
|
</body>
|
||||||
<script src="{% static 'vendors/feather-icons/feather.min.js' %}"></script>
|
</html>
|
||||||
<script src="{% static 'vendors/dayjs/dayjs.min.js' %}"></script>
|
|
||||||
<script src="{% static 'assets/js/phoenix.js' %}"></script>
|
|
||||||
</body>
|
|
||||||
</html>
|
|
||||||
|
|||||||
@ -1,35 +1,60 @@
|
|||||||
{% load static i18n %}
|
{% load static i18n %}
|
||||||
<html>
|
<html>
|
||||||
<head>
|
<head>
|
||||||
<meta charset="utf-8" />
|
<meta charset="utf-8" />
|
||||||
<meta http-equiv="X-UA-Compatible" content="IE=edge" />
|
<meta http-equiv="X-UA-Compatible" content="IE=edge" />
|
||||||
<title>Access Forbidden</title>
|
<title>Access Forbidden</title>
|
||||||
<meta name="description" content="" />
|
<meta name="description" content="" />
|
||||||
<meta name="viewport" content="width=device-width, initial-scale=1" />
|
<meta name="viewport" content="width=device-width, initial-scale=1" />
|
||||||
<link rel="apple-touch-icon" sizes="180x180" href="{% static 'assets/img/favicons/apple-touch-icon.png' %}" />
|
<link rel="apple-touch-icon"
|
||||||
<link rel="icon" type="image/png" sizes="32x32" href="{% static 'assets/img/favicons/favicon-32x32.png' %}" />
|
sizes="180x180"
|
||||||
<link rel="icon" type="image/png" sizes="16x16" href="{% static 'assets/img/favicons/favicon-16x16.png' %}" />
|
href="{% static 'assets/img/favicons/apple-touch-icon.png' %}" />
|
||||||
<link rel="shortcut icon" type="image/x-icon" href="{% static 'assets/img/favicons/favicon.ico' %}" />
|
<link rel="icon"
|
||||||
<link rel="manifest" href="{% static 'assets/img/favicons/manifest.json' %}" />
|
type="image/png"
|
||||||
<meta name="msapplication-TileImage" content="{% static 'assets/img/favicons/mstile-150x150.png' %}" />
|
sizes="32x32"
|
||||||
<meta name="theme-color" content="#ffffff" />
|
href="{% static 'assets/img/favicons/favicon-32x32.png' %}" />
|
||||||
<script src="{% static 'vendors/simplebar/simplebar.min.js' %}"></script>
|
<link rel="icon"
|
||||||
<script src="{% static 'assets/js/config.js' %}"></script>
|
type="image/png"
|
||||||
|
sizes="16x16"
|
||||||
<!-- =============================================== -->
|
href="{% static 'assets/img/favicons/favicon-16x16.png' %}" />
|
||||||
<!-- Stylesheets -->
|
<link rel="shortcut icon"
|
||||||
<!-- =============================================== -->
|
type="image/x-icon"
|
||||||
<link rel="preconnect" href="https://fonts.googleapis.com" />
|
href="{% static 'assets/img/favicons/favicon.ico' %}" />
|
||||||
<link rel="preconnect" href="https://fonts.gstatic.com" crossorigin="" />
|
<link rel="manifest"
|
||||||
<link href="https://fonts.googleapis.com/css2?family=Nunito+Sans:wght@300;400;600;700;800;900&display=swap" rel="stylesheet" />
|
href="{% static 'assets/img/favicons/manifest.json' %}" />
|
||||||
<link href="{% static 'vendors/simplebar/simplebar.min.css' %}" rel="stylesheet" />
|
<meta name="msapplication-TileImage"
|
||||||
<link rel="stylesheet" href="https://unicons.iconscout.com/release/v4.0.8/css/line.css" />
|
content="{% static 'assets/img/favicons/mstile-150x150.png' %}" />
|
||||||
<link href="{% static 'assets/css/theme-rtl.min.css' %}" type="text/css" rel="stylesheet" id="style-rtl" />
|
<meta name="theme-color" content="#ffffff" />
|
||||||
<link href="{% static 'assets/css/theme.min.css' %}" type="text/css" rel="stylesheet" id="style-default" />
|
<script src="{% static 'vendors/simplebar/simplebar.min.js' %}"></script>
|
||||||
<link href="{% static 'assets/css/user-rtl.min.css' %}" type="text/css" rel="stylesheet" id="user-style-rtl" />
|
<script src="{% static 'assets/js/config.js' %}"></script>
|
||||||
<link href="{% static 'assets/css/user.min.css' %}" type="text/css" rel="stylesheet" id="user-style-default" />
|
<!-- =============================================== -->
|
||||||
|
<!-- Stylesheets -->
|
||||||
<style>
|
<!-- =============================================== -->
|
||||||
|
<link rel="preconnect" href="https://fonts.googleapis.com" />
|
||||||
|
<link rel="preconnect" href="https://fonts.gstatic.com" crossorigin="" />
|
||||||
|
<link href="https://fonts.googleapis.com/css2?family=Nunito+Sans:wght@300;400;600;700;800;900&display=swap"
|
||||||
|
rel="stylesheet" />
|
||||||
|
<link href="{% static 'vendors/simplebar/simplebar.min.css' %}"
|
||||||
|
rel="stylesheet" />
|
||||||
|
<link rel="stylesheet"
|
||||||
|
href="https://unicons.iconscout.com/release/v4.0.8/css/line.css" />
|
||||||
|
<link href="{% static 'assets/css/theme-rtl.min.css' %}"
|
||||||
|
type="text/css"
|
||||||
|
rel="stylesheet"
|
||||||
|
id="style-rtl" />
|
||||||
|
<link href="{% static 'assets/css/theme.min.css' %}"
|
||||||
|
type="text/css"
|
||||||
|
rel="stylesheet"
|
||||||
|
id="style-default" />
|
||||||
|
<link href="{% static 'assets/css/user-rtl.min.css' %}"
|
||||||
|
type="text/css"
|
||||||
|
rel="stylesheet"
|
||||||
|
id="user-style-rtl" />
|
||||||
|
<link href="{% static 'assets/css/user.min.css' %}"
|
||||||
|
type="text/css"
|
||||||
|
rel="stylesheet"
|
||||||
|
id="user-style-default" />
|
||||||
|
<style>
|
||||||
body, html {
|
body, html {
|
||||||
height: 100%;
|
height: 100%;
|
||||||
margin: 0;
|
margin: 0;
|
||||||
@ -51,40 +76,48 @@
|
|||||||
.text-center {
|
.text-center {
|
||||||
text-align: center;
|
text-align: center;
|
||||||
}
|
}
|
||||||
</style>
|
</style>
|
||||||
</head>
|
</head>
|
||||||
|
<body>
|
||||||
<body>
|
<main class="main" id="top">
|
||||||
<main class="main" id="top">
|
<div class="px-3">
|
||||||
<div class="px-3">
|
<div class="row min-vh-100 flex-center p-5">
|
||||||
<div class="row min-vh-100 flex-center p-5">
|
<div class="col-12 col-xl-10 col-xxl-8">
|
||||||
<div class="col-12 col-xl-10 col-xxl-8">
|
<div class="row justify-content-center align-items-center g-5">
|
||||||
<div class="row justify-content-center align-items-center g-5">
|
<div class="col-12 col-lg-6 text-center order-lg-1">
|
||||||
<div class="col-12 col-lg-6 text-center order-lg-1">
|
<img class="img-fluid w-md-50 w-lg-100 d-light-none"
|
||||||
<img class="img-fluid w-md-50 w-lg-100 d-light-none" src="{% static 'images/spot-illustrations/dark_500-illustration.png' %}" alt="" width="540" />
|
src="{% static 'images/spot-illustrations/dark_500-illustration.png' %}"
|
||||||
</div>
|
alt=""
|
||||||
<div class="col-12 col-lg-6 text-center text-lg-start">
|
width="540" />
|
||||||
<img class="img-fluid mb-6 w-50 w-lg-75 d-dark-none" src="{% static 'images/spot-illustrations/500.png' %}" alt="" />
|
</div>
|
||||||
<h2 class="text-body-secondary fw-bolder mb-3">Page Missing!</h2>
|
<div class="col-12 col-lg-6 text-center text-lg-start">
|
||||||
<p class="text-body mb-5">But no worries! Our ostrich is looking everywhere <br class="d-none d-sm-block" />while you wait safely. </p><a class="btn btn-lg btn-phoenix-primary" href="{% url 'home' %}">Go Home</a>
|
<img class="img-fluid mb-6 w-50 w-lg-75 d-dark-none"
|
||||||
</div>
|
src="{% static 'images/spot-illustrations/500.png' %}"
|
||||||
|
alt="" />
|
||||||
|
<h2 class="text-body-secondary fw-bolder mb-3">Page Missing!</h2>
|
||||||
|
<p class="text-body mb-5">
|
||||||
|
But no worries! Our ostrich is looking everywhere
|
||||||
|
<br class="d-none d-sm-block" />
|
||||||
|
while you wait safely.
|
||||||
|
</p>
|
||||||
|
<a class="btn btn-lg btn-phoenix-primary" href="{% url 'home' %}">Go Home</a>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</main>
|
||||||
</div>
|
<script src="{% static 'vendors/bootstrap/bootstrap.min.js' %}"></script>
|
||||||
</div>
|
<script src="{% static 'js/phoenix.js' %}"></script>
|
||||||
</main>
|
<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/anchorjs/anchor.min.js' %}"></script>
|
||||||
<script src="{% static 'js/phoenix.js' %}"></script>
|
<script src="{% static 'vendors/is/is.min.js' %}"></script>
|
||||||
<script src="{% static 'vendors/popper/popper.min.js' %}"></script>
|
<script src="{% static 'vendors/fontawesome/all.min.js' %}"></script>
|
||||||
<script src="{% static 'vendors/bootstrap/bootstrap.min.js' %}"></script>
|
<script src="{% static 'vendors/lodash/lodash.min.js' %}"></script>
|
||||||
<script src="{% static 'vendors/anchorjs/anchor.min.js' %}"></script>
|
<script src="{% static 'vendors/list.js/list.min.js' %}"></script>
|
||||||
<script src="{% static 'vendors/is/is.min.js' %}"></script>
|
<script src="{% static 'vendors/feather-icons/feather.min.js' %}"></script>
|
||||||
<script src="{% static 'vendors/fontawesome/all.min.js' %}"></script>
|
<script src="{% static 'vendors/dayjs/dayjs.min.js' %}"></script>
|
||||||
<script src="{% static 'vendors/lodash/lodash.min.js' %}"></script>
|
<script src="{% static 'assets/js/phoenix.js' %}"></script>
|
||||||
<script src="{% static 'vendors/list.js/list.min.js' %}"></script>
|
</body>
|
||||||
<script src="{% static 'vendors/feather-icons/feather.min.js' %}"></script>
|
</html>
|
||||||
<script src="{% static 'vendors/dayjs/dayjs.min.js' %}"></script>
|
|
||||||
<script src="{% static 'assets/js/phoenix.js' %}"></script>
|
|
||||||
</body>
|
|
||||||
</html>
|
|
||||||
|
|||||||
@ -1,35 +1,60 @@
|
|||||||
{% load static i18n %}
|
{% load static i18n %}
|
||||||
<html>
|
<html>
|
||||||
<head>
|
<head>
|
||||||
<meta charset="utf-8" />
|
<meta charset="utf-8" />
|
||||||
<meta http-equiv="X-UA-Compatible" content="IE=edge" />
|
<meta http-equiv="X-UA-Compatible" content="IE=edge" />
|
||||||
<title>Access Forbidden</title>
|
<title>Access Forbidden</title>
|
||||||
<meta name="description" content="" />
|
<meta name="description" content="" />
|
||||||
<meta name="viewport" content="width=device-width, initial-scale=1" />
|
<meta name="viewport" content="width=device-width, initial-scale=1" />
|
||||||
<link rel="apple-touch-icon" sizes="180x180" href="{% static 'assets/img/favicons/apple-touch-icon.png' %}" />
|
<link rel="apple-touch-icon"
|
||||||
<link rel="icon" type="image/png" sizes="32x32" href="{% static 'assets/img/favicons/favicon-32x32.png' %}" />
|
sizes="180x180"
|
||||||
<link rel="icon" type="image/png" sizes="16x16" href="{% static 'assets/img/favicons/favicon-16x16.png' %}" />
|
href="{% static 'assets/img/favicons/apple-touch-icon.png' %}" />
|
||||||
<link rel="shortcut icon" type="image/x-icon" href="{% static 'assets/img/favicons/favicon.ico' %}" />
|
<link rel="icon"
|
||||||
<link rel="manifest" href="{% static 'assets/img/favicons/manifest.json' %}" />
|
type="image/png"
|
||||||
<meta name="msapplication-TileImage" content="{% static 'assets/img/favicons/mstile-150x150.png' %}" />
|
sizes="32x32"
|
||||||
<meta name="theme-color" content="#ffffff" />
|
href="{% static 'assets/img/favicons/favicon-32x32.png' %}" />
|
||||||
<script src="{% static 'vendors/simplebar/simplebar.min.js' %}"></script>
|
<link rel="icon"
|
||||||
<script src="{% static 'assets/js/config.js' %}"></script>
|
type="image/png"
|
||||||
|
sizes="16x16"
|
||||||
<!-- =============================================== -->
|
href="{% static 'assets/img/favicons/favicon-16x16.png' %}" />
|
||||||
<!-- Stylesheets -->
|
<link rel="shortcut icon"
|
||||||
<!-- =============================================== -->
|
type="image/x-icon"
|
||||||
<link rel="preconnect" href="https://fonts.googleapis.com" />
|
href="{% static 'assets/img/favicons/favicon.ico' %}" />
|
||||||
<link rel="preconnect" href="https://fonts.gstatic.com" crossorigin="" />
|
<link rel="manifest"
|
||||||
<link href="https://fonts.googleapis.com/css2?family=Nunito+Sans:wght@300;400;600;700;800;900&display=swap" rel="stylesheet" />
|
href="{% static 'assets/img/favicons/manifest.json' %}" />
|
||||||
<link href="{% static 'vendors/simplebar/simplebar.min.css' %}" rel="stylesheet" />
|
<meta name="msapplication-TileImage"
|
||||||
<link rel="stylesheet" href="https://unicons.iconscout.com/release/v4.0.8/css/line.css" />
|
content="{% static 'assets/img/favicons/mstile-150x150.png' %}" />
|
||||||
<link href="{% static 'assets/css/theme-rtl.min.css' %}" type="text/css" rel="stylesheet" id="style-rtl" />
|
<meta name="theme-color" content="#ffffff" />
|
||||||
<link href="{% static 'assets/css/theme.min.css' %}" type="text/css" rel="stylesheet" id="style-default" />
|
<script src="{% static 'vendors/simplebar/simplebar.min.js' %}"></script>
|
||||||
<link href="{% static 'assets/css/user-rtl.min.css' %}" type="text/css" rel="stylesheet" id="user-style-rtl" />
|
<script src="{% static 'assets/js/config.js' %}"></script>
|
||||||
<link href="{% static 'assets/css/user.min.css' %}" type="text/css" rel="stylesheet" id="user-style-default" />
|
<!-- =============================================== -->
|
||||||
|
<!-- Stylesheets -->
|
||||||
<style>
|
<!-- =============================================== -->
|
||||||
|
<link rel="preconnect" href="https://fonts.googleapis.com" />
|
||||||
|
<link rel="preconnect" href="https://fonts.gstatic.com" crossorigin="" />
|
||||||
|
<link href="https://fonts.googleapis.com/css2?family=Nunito+Sans:wght@300;400;600;700;800;900&display=swap"
|
||||||
|
rel="stylesheet" />
|
||||||
|
<link href="{% static 'vendors/simplebar/simplebar.min.css' %}"
|
||||||
|
rel="stylesheet" />
|
||||||
|
<link rel="stylesheet"
|
||||||
|
href="https://unicons.iconscout.com/release/v4.0.8/css/line.css" />
|
||||||
|
<link href="{% static 'assets/css/theme-rtl.min.css' %}"
|
||||||
|
type="text/css"
|
||||||
|
rel="stylesheet"
|
||||||
|
id="style-rtl" />
|
||||||
|
<link href="{% static 'assets/css/theme.min.css' %}"
|
||||||
|
type="text/css"
|
||||||
|
rel="stylesheet"
|
||||||
|
id="style-default" />
|
||||||
|
<link href="{% static 'assets/css/user-rtl.min.css' %}"
|
||||||
|
type="text/css"
|
||||||
|
rel="stylesheet"
|
||||||
|
id="user-style-rtl" />
|
||||||
|
<link href="{% static 'assets/css/user.min.css' %}"
|
||||||
|
type="text/css"
|
||||||
|
rel="stylesheet"
|
||||||
|
id="user-style-default" />
|
||||||
|
<style>
|
||||||
body, html {
|
body, html {
|
||||||
height: 100%;
|
height: 100%;
|
||||||
margin: 0;
|
margin: 0;
|
||||||
@ -51,40 +76,44 @@
|
|||||||
.text-center {
|
.text-center {
|
||||||
text-align: center;
|
text-align: center;
|
||||||
}
|
}
|
||||||
</style>
|
</style>
|
||||||
</head>
|
</head>
|
||||||
|
<body>
|
||||||
<body>
|
<main class="main" id="top">
|
||||||
<main class="main" id="top">
|
<div class="px-3">
|
||||||
<div class="px-3">
|
<div class="row min-vh-100 flex-center p-5">
|
||||||
<div class="row min-vh-100 flex-center p-5">
|
<div class="col-12 col-xl-10 col-xxl-8">
|
||||||
<div class="col-12 col-xl-10 col-xxl-8">
|
<div class="row justify-content-center align-items-center g-5">
|
||||||
<div class="row justify-content-center align-items-center g-5">
|
<div class="col-12 col-lg-6 text-center order-lg-1">
|
||||||
<div class="col-12 col-lg-6 text-center order-lg-1">
|
<img class="img-fluid w-md-50 w-lg-100 d-light-none"
|
||||||
<img class="img-fluid w-md-50 w-lg-100 d-light-none" src="{% static 'images/spot-illustrations/dark_404-illustration.png' %}" alt="" width="540" />
|
src="{% static 'images/spot-illustrations/dark_404-illustration.png' %}"
|
||||||
</div>
|
alt=""
|
||||||
<div class="col-12 col-lg-6 text-center text-lg-start">
|
width="540" />
|
||||||
<img class="img-fluid mb-6 w-50 w-lg-75 d-dark-none" src="{% static 'images/spot-illustrations/404.png' %}" alt="" />
|
</div>
|
||||||
<h2 class="text-body-secondary fw-bolder mb-3">Unknow error!</h2>
|
<div class="col-12 col-lg-6 text-center text-lg-start">
|
||||||
<p class="text-body mb-5">But relax! Our cat is here to play you some music.</p><a class="btn btn-lg btn-phoenix-primary" href="{% url 'home' %}">Go Home</a>
|
<img class="img-fluid mb-6 w-50 w-lg-75 d-dark-none"
|
||||||
</div>
|
src="{% static 'images/spot-illustrations/404.png' %}"
|
||||||
|
alt="" />
|
||||||
|
<h2 class="text-body-secondary fw-bolder mb-3">Unknow error!</h2>
|
||||||
|
<p class="text-body mb-5">But relax! Our cat is here to play you some music.</p>
|
||||||
|
<a class="btn btn-lg btn-phoenix-primary" href="{% url 'home' %}">Go Home</a>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</main>
|
||||||
</div>
|
<script src="{% static 'vendors/bootstrap/bootstrap.min.js' %}"></script>
|
||||||
</div>
|
<script src="{% static 'js/phoenix.js' %}"></script>
|
||||||
</main>
|
<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/anchorjs/anchor.min.js' %}"></script>
|
||||||
<script src="{% static 'js/phoenix.js' %}"></script>
|
<script src="{% static 'vendors/is/is.min.js' %}"></script>
|
||||||
<script src="{% static 'vendors/popper/popper.min.js' %}"></script>
|
<script src="{% static 'vendors/fontawesome/all.min.js' %}"></script>
|
||||||
<script src="{% static 'vendors/bootstrap/bootstrap.min.js' %}"></script>
|
<script src="{% static 'vendors/lodash/lodash.min.js' %}"></script>
|
||||||
<script src="{% static 'vendors/anchorjs/anchor.min.js' %}"></script>
|
<script src="{% static 'vendors/list.js/list.min.js' %}"></script>
|
||||||
<script src="{% static 'vendors/is/is.min.js' %}"></script>
|
<script src="{% static 'vendors/feather-icons/feather.min.js' %}"></script>
|
||||||
<script src="{% static 'vendors/fontawesome/all.min.js' %}"></script>
|
<script src="{% static 'vendors/dayjs/dayjs.min.js' %}"></script>
|
||||||
<script src="{% static 'vendors/lodash/lodash.min.js' %}"></script>
|
<script src="{% static 'assets/js/phoenix.js' %}"></script>
|
||||||
<script src="{% static 'vendors/list.js/list.min.js' %}"></script>
|
</body>
|
||||||
<script src="{% static 'vendors/feather-icons/feather.min.js' %}"></script>
|
</html>
|
||||||
<script src="{% static 'vendors/dayjs/dayjs.min.js' %}"></script>
|
|
||||||
<script src="{% static 'assets/js/phoenix.js' %}"></script>
|
|
||||||
</body>
|
|
||||||
</html>
|
|
||||||
|
|||||||
@ -6,9 +6,9 @@
|
|||||||
{% endblock head_title %}
|
{% endblock head_title %}
|
||||||
{% block content %}
|
{% block content %}
|
||||||
{% element h1 %}
|
{% element h1 %}
|
||||||
{% translate "Account Inactive" %}
|
{% translate "Account Inactive" %}
|
||||||
{% endelement %}
|
{% endelement %}
|
||||||
{% element p %}
|
{% element p %}
|
||||||
{% translate "This account is inactive." %}
|
{% translate "This account is inactive." %}
|
||||||
{% endelement %}
|
{% endelement %}
|
||||||
{% endblock content %}
|
{% endblock content %}
|
||||||
|
|||||||
@ -6,43 +6,43 @@
|
|||||||
{% endblock head_title %}
|
{% endblock head_title %}
|
||||||
{% block content %}
|
{% block content %}
|
||||||
{% element h1 %}
|
{% element h1 %}
|
||||||
{% translate "Enter Email Verification Code" %}
|
{% translate "Enter Email Verification Code" %}
|
||||||
{% endelement %}
|
{% endelement %}
|
||||||
{% setvar email_link %}
|
{% setvar email_link %}
|
||||||
<a href="mailto:{{ email }}">{{ email }}</a>
|
<a href="mailto:{{ email }}">{{ email }}</a>
|
||||||
{% endsetvar %}
|
{% endsetvar %}
|
||||||
{% element p %}
|
{% element p %}
|
||||||
{% blocktranslate %}We’ve sent a code to {{ email_link }}. The code expires shortly, so please enter it soon.{% endblocktranslate %}
|
{% blocktranslate %}We’ve sent a code to {{ email_link }}. The code expires shortly, so please enter it soon.{% endblocktranslate %}
|
||||||
{% endelement %}
|
{% endelement %}
|
||||||
{% url 'account_email_verification_sent' as action_url %}
|
{% url 'account_email_verification_sent' as action_url %}
|
||||||
{% element form form=form method="post" action=action_url tags="entrance,email,verification" %}
|
{% element form form=form method="post" action=action_url tags="entrance,email,verification" %}
|
||||||
{% slot body %}
|
{% slot body %}
|
||||||
{% csrf_token %}
|
{% csrf_token %}
|
||||||
{% element fields form=form unlabeled=True %}
|
{% element fields form=form unlabeled=True %}
|
||||||
{% endelement %}
|
{% endelement %}
|
||||||
{{ redirect_field }}
|
{{ redirect_field }}
|
||||||
{% endslot %}
|
{% endslot %}
|
||||||
{% slot actions %}
|
{% slot actions %}
|
||||||
{% element button type="submit" tags="prominent,confirm" %}
|
{% element button type="submit" tags="prominent,confirm" %}
|
||||||
{% translate "Confirm" %}
|
{% translate "Confirm" %}
|
||||||
{% endelement %}
|
{% endelement %}
|
||||||
{% if cancel_url %}
|
{% if cancel_url %}
|
||||||
{% element button href=cancel_url tags="link,cancel" %}
|
{% element button href=cancel_url tags="link,cancel" %}
|
||||||
{% translate "Cancel" %}
|
{% translate "Cancel" %}
|
||||||
{% endelement %}
|
{% endelement %}
|
||||||
{% else %}
|
{% else %}
|
||||||
{% element button type="submit" form="logout-from-stage" tags="link,cancel" %}
|
{% element button type="submit" form="logout-from-stage" tags="link,cancel" %}
|
||||||
{% translate "Cancel" %}
|
{% translate "Cancel" %}
|
||||||
{% endelement %}
|
{% endelement %}
|
||||||
{% endif %}
|
{% endif %}
|
||||||
{% endslot %}
|
{% endslot %}
|
||||||
{% endelement %}
|
{% endelement %}
|
||||||
{% if not cancel_url %}
|
{% if not cancel_url %}
|
||||||
<form id="logout-from-stage"
|
<form id="logout-from-stage"
|
||||||
method="post"
|
method="post"
|
||||||
action="{% url 'account_logout' %}">
|
action="{% url 'account_logout' %}">
|
||||||
<input type="hidden" name="next" value="{% url 'account_login' %}">
|
<input type="hidden" name="next" value="{% url 'account_login' %}">
|
||||||
{% csrf_token %}
|
{% csrf_token %}
|
||||||
</form>
|
</form>
|
||||||
{% endif %}
|
{% endif %}
|
||||||
{% endblock content %}
|
{% endblock content %}
|
||||||
|
|||||||
@ -1,6 +1,5 @@
|
|||||||
{% extends "base.html" %}
|
{% extends "base.html" %}
|
||||||
{% load crispy_forms_filters %}
|
{% load crispy_forms_filters %}
|
||||||
|
|
||||||
{% load i18n %}
|
{% load i18n %}
|
||||||
{% load allauth account %}
|
{% load allauth account %}
|
||||||
{% block head_title %}
|
{% block head_title %}
|
||||||
@ -10,40 +9,49 @@
|
|||||||
<div class="row ">
|
<div class="row ">
|
||||||
<div class="row flex-center min-vh-50">
|
<div class="row flex-center min-vh-50">
|
||||||
<div class="col-sm-10 col-md-8 col-lg-5 col-xl-5 col-xxl-3">
|
<div class="col-sm-10 col-md-8 col-lg-5 col-xl-5 col-xxl-3">
|
||||||
<a class="d-flex flex-center text-decoration-none mb-4" href="{% url 'home' %}">
|
<a class="d-flex flex-center text-decoration-none mb-4"
|
||||||
|
href="{% url 'home' %}">
|
||||||
<div class="d-flex align-items-center fw-bolder fs-3 d-inline-block">
|
<div class="d-flex align-items-center fw-bolder fs-3 d-inline-block">
|
||||||
<div class="d-flex align-items-center fw-bolder fs-3 d-inline-block">
|
<div class="d-flex align-items-center fw-bolder fs-3 d-inline-block">
|
||||||
<img class="d-dark-none" src="{% static 'images/logos/logo-d.png' %}" alt="{% trans 'home' %}" width="58" />
|
<img class="d-dark-none"
|
||||||
<img class="d-light-none" src="{% static 'images/logos/logo.png' %}" alt="{% trans 'home' %}" width="58" />
|
src="{% static 'images/logos/logo-d.png' %}"
|
||||||
|
alt="{% trans 'home' %}"
|
||||||
|
width="58" />
|
||||||
|
<img class="d-light-none"
|
||||||
|
src="{% static 'images/logos/logo.png' %}"
|
||||||
|
alt="{% trans 'home' %}"
|
||||||
|
width="58" />
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</a>
|
</a>
|
||||||
<div class="text-center">
|
<div class="text-center">
|
||||||
<h3 class="mb-4">{% translate "Enter Sign-In Code" %}</h3>
|
<h3 class="mb-4">{% translate "Enter Sign-In Code" %}</h3>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
{% setvar email_link %}
|
{% setvar email_link %}
|
||||||
<a href="mailto:{{ email }}">{{ email }}</a>
|
<a href="mailto:{{ email }}">{{ email }}</a>
|
||||||
{% endsetvar %}
|
{% endsetvar %}
|
||||||
<p>
|
<p>
|
||||||
{% blocktranslate %}We’ve sent a code to {{ email_link }}. The code expires shortly, so please enter it soon.{% endblocktranslate %}
|
{% blocktranslate %}We’ve sent a code to {{ email_link }}. The code expires shortly, so please enter it soon.{% endblocktranslate %}
|
||||||
</p>
|
</p>
|
||||||
<form method="post" action="{% url 'account_confirm_login_code' %}" class="form needs-validation" novalidate>
|
<form method="post"
|
||||||
{% csrf_token %}
|
action="{% url 'account_confirm_login_code' %}"
|
||||||
{{ redirect_field }}
|
class="form needs-validation"
|
||||||
{{ form|crispy }}
|
novalidate>
|
||||||
<button type="submit" class="btn btn-phoenix-primary btn-sm w-100">{% trans "Sign In" %}</button>
|
{% csrf_token %}
|
||||||
</form>
|
{{ redirect_field }}
|
||||||
{% element button type="submit" form="logout-from-stage" tags="link" %}
|
{{ form|crispy }}
|
||||||
{% translate "Cancel" %}
|
<button type="submit" class="btn btn-phoenix-primary btn-sm w-100">{% trans "Sign In" %}</button>
|
||||||
{% endelement %}
|
</form>
|
||||||
<form id="logout-from-stage"
|
{% element button type="submit" form="logout-from-stage" tags="link" %}
|
||||||
method="post"
|
{% translate "Cancel" %}
|
||||||
action="{% url 'account_logout' %}">
|
{% endelement %}
|
||||||
<input type="hidden" name="next" value="{% url 'account_login' %}">
|
<form id="logout-from-stage"
|
||||||
{% csrf_token %}
|
method="post"
|
||||||
</form>
|
action="{% url 'account_logout' %}">
|
||||||
</div>
|
<input type="hidden" name="next" value="{% url 'account_login' %}">
|
||||||
</div>
|
{% csrf_token %}
|
||||||
|
</form>
|
||||||
</div>
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
{% endblock content %}
|
{% endblock content %}
|
||||||
|
|||||||
@ -8,22 +8,26 @@
|
|||||||
<div class="row ">
|
<div class="row ">
|
||||||
<div class="row flex-center min-vh-50">
|
<div class="row flex-center min-vh-50">
|
||||||
<div class="col-sm-10 col-md-8 col-lg-5 col-xl-5 col-xxl-3">
|
<div class="col-sm-10 col-md-8 col-lg-5 col-xl-5 col-xxl-3">
|
||||||
<a class="d-flex flex-center text-decoration-none mb-4" href="{% url 'home' %}">
|
<a class="d-flex flex-center text-decoration-none mb-4"
|
||||||
|
href="{% url 'home' %}">
|
||||||
<div class="d-flex align-items-center fw-bolder fs-3 d-inline-block">
|
<div class="d-flex align-items-center fw-bolder fs-3 d-inline-block">
|
||||||
<div class="d-flex align-items-center fw-bolder fs-3 d-inline-block">
|
<div class="d-flex align-items-center fw-bolder fs-3 d-inline-block">
|
||||||
<img class="d-dark-none" src="{% static 'images/logos/logo-d.png' %}" alt="{% trans 'home' %}" width="58" />
|
<img class="d-dark-none"
|
||||||
<img class="d-light-none" src="{% static 'images/logos/logo.png' %}" alt="{% trans 'home' %}" width="58" />
|
src="{% static 'images/logos/logo-d.png' %}"
|
||||||
|
alt="{% trans 'home' %}"
|
||||||
|
width="58" />
|
||||||
|
<img class="d-light-none"
|
||||||
|
src="{% static 'images/logos/logo.png' %}"
|
||||||
|
alt="{% trans 'home' %}"
|
||||||
|
width="58" />
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</a>
|
</a>
|
||||||
<div class="text-center">
|
<div class="text-center">
|
||||||
<h3 class="mb-4">{% trans "Email Addresses" %}</h3>
|
<h3 class="mb-4">{% trans "Email Addresses" %}</h3>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
{% if emailaddresses %}
|
{% if emailaddresses %}
|
||||||
<p>
|
<p>{% trans 'The following email addresses are associated with your account:' %}</p>
|
||||||
{% trans 'The following email addresses are associated with your account:' %}
|
|
||||||
</p>
|
|
||||||
{% url 'account_email' as email_url %}
|
{% url 'account_email' as email_url %}
|
||||||
<form action="{{ email_url }}" method="POST" class="form email list">
|
<form action="{{ email_url }}" method="POST" class="form email list">
|
||||||
{% csrf_token %}
|
{% csrf_token %}
|
||||||
@ -31,7 +35,12 @@
|
|||||||
{% for radio in emailaddress_radios %}
|
{% for radio in emailaddress_radios %}
|
||||||
{% with emailaddress=radio.emailaddress %}
|
{% with emailaddress=radio.emailaddress %}
|
||||||
<label for="{{ radio.id }}">
|
<label for="{{ radio.id }}">
|
||||||
<input type="radio" name="email" checked="{{ radio.checked }}" value="{{ emailaddress.email }}" id="{{ radio.id }}" class="form-check-input mb-3" />
|
<input type="radio"
|
||||||
|
name="email"
|
||||||
|
checked="{{ radio.checked }}"
|
||||||
|
value="{{ emailaddress.email }}"
|
||||||
|
id="{{ radio.id }}"
|
||||||
|
class="form-check-input mb-3" />
|
||||||
{{ emailaddress.email }}
|
{{ emailaddress.email }}
|
||||||
{% if emailaddress.verified %}
|
{% if emailaddress.verified %}
|
||||||
<span class="badge badge-phoenix badge-phoenix-success verified">{% translate "Verified" %}</span>
|
<span class="badge badge-phoenix badge-phoenix-success verified">{% translate "Verified" %}</span>
|
||||||
@ -41,36 +50,38 @@
|
|||||||
{% if emailaddress.primary %}
|
{% if emailaddress.primary %}
|
||||||
<span class="badge badge-phoenix badge-phoenix-primary email">{% translate "Primary" %}</span>
|
<span class="badge badge-phoenix badge-phoenix-primary email">{% translate "Primary" %}</span>
|
||||||
{% endif %}
|
{% endif %}
|
||||||
{% endwith %}
|
{% endwith %}
|
||||||
</label>
|
</label>
|
||||||
{% endfor %}
|
{% endfor %}
|
||||||
</div>
|
</div>
|
||||||
<div class="mt-2 mb-6">
|
<div class="mt-2 mb-6">
|
||||||
<button type="submit" name="action_primary" class="btn btn-sm btn-phoenix-primary">{% trans 'Make Primary' %}</button>
|
<button type="submit"
|
||||||
<button type="submit" name="action_send" class="btn btn-sm btn-phoenix-secondary">{% trans 'Re-send Verification' %}</button>
|
name="action_primary"
|
||||||
<button type="submit" name="action_remove" class="btn btn-sm btn-phoenix-danger delete">{% trans 'Remove' %}</button>
|
class="btn btn-sm btn-phoenix-primary">{% trans 'Make Primary' %}</button>
|
||||||
|
<button type="submit"
|
||||||
|
name="action_send"
|
||||||
|
class="btn btn-sm btn-phoenix-secondary">
|
||||||
|
{% trans 'Re-send Verification' %}
|
||||||
|
</button>
|
||||||
|
<button type="submit"
|
||||||
|
name="action_remove"
|
||||||
|
class="btn btn-sm btn-phoenix-danger delete">{% trans 'Remove' %}</button>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
|
||||||
</form>
|
</form>
|
||||||
{% else %}
|
{% else %}
|
||||||
{% include "account/snippets/warn_no_email.html" %}
|
{% include "account/snippets/warn_no_email.html" %}
|
||||||
{% endif %}
|
{% endif %}
|
||||||
{% if can_add_email %}
|
{% if can_add_email %}
|
||||||
<p class="fs-8 fw-bold text-start">
|
<p class="fs-8 fw-bold text-start">{% trans "Add Email Address" %}</p>
|
||||||
{% trans "Add Email Address" %}
|
|
||||||
</p>
|
|
||||||
|
|
||||||
{% url 'account_email' as action_url %}
|
{% url 'account_email' as action_url %}
|
||||||
<form action="{{ action_url }}" method="POST" class="form email add">
|
<form action="{{ action_url }}" method="POST" class="form email add">
|
||||||
{% csrf_token %}
|
{% csrf_token %}
|
||||||
{{ form|crispy }}
|
{{ form|crispy }}
|
||||||
<button class="btn btn-sm btn-phoenix-success w-100" type="submit" name="action_add">
|
<button class="btn btn-sm btn-phoenix-success w-100"
|
||||||
{% trans "Add Email" %}
|
type="submit"
|
||||||
</button>
|
name="action_add">{% trans "Add Email" %}</button>
|
||||||
</form>
|
</form>
|
||||||
{% endif %}
|
{% endif %}
|
||||||
|
|
||||||
<script>
|
<script>
|
||||||
(function() {
|
(function() {
|
||||||
var message = "{% trans 'Do you really want to remove the selected email address?' %}";
|
var message = "{% trans 'Do you really want to remove the selected email address?' %}";
|
||||||
|
|||||||
@ -6,63 +6,63 @@
|
|||||||
{% endblock head_title %}
|
{% endblock head_title %}
|
||||||
{% block content %}
|
{% block content %}
|
||||||
{% element h1 %}
|
{% element h1 %}
|
||||||
{% trans "Email Address" %}
|
{% trans "Email Address" %}
|
||||||
{% endelement %}
|
{% endelement %}
|
||||||
{% if not emailaddresses %}
|
{% if not emailaddresses %}
|
||||||
{% include "account/snippets/warn_no_email.html" %}
|
{% include "account/snippets/warn_no_email.html" %}
|
||||||
{% endif %}
|
{% endif %}
|
||||||
{% url 'account_email' as action_url %}
|
{% url 'account_email' as action_url %}
|
||||||
{% element form method="post" action=action_url %}
|
{% element form method="post" action=action_url %}
|
||||||
{% slot body %}
|
{% slot body %}
|
||||||
{% csrf_token %}
|
{% csrf_token %}
|
||||||
{% if current_emailaddress %}
|
{% if current_emailaddress %}
|
||||||
{% element field id="current_email" disabled=True type="email" value=current_emailaddress.email %}
|
{% element field id="current_email" disabled=True type="email" value=current_emailaddress.email %}
|
||||||
{% slot label %}
|
{% slot label %}
|
||||||
{% translate "Current email" %}:
|
{% translate "Current email" %}:
|
||||||
{% endslot %}
|
{% endslot %}
|
||||||
{% endelement %}
|
{% endelement %}
|
||||||
{% endif %}
|
{% endif %}
|
||||||
{% if new_emailaddress %}
|
{% if new_emailaddress %}
|
||||||
{% element field id="new_email" value=new_emailaddress.email disabled=True type="email" %}
|
{% element field id="new_email" value=new_emailaddress.email disabled=True type="email" %}
|
||||||
{% slot label %}
|
{% slot label %}
|
||||||
{% if not current_emailaddress %}
|
{% if not current_emailaddress %}
|
||||||
{% translate "Current email" %}:
|
{% translate "Current email" %}:
|
||||||
{% else %}
|
{% else %}
|
||||||
{% translate "Changing to" %}:
|
{% translate "Changing to" %}:
|
||||||
{% endif %}
|
|
||||||
{% endslot %}
|
|
||||||
{% slot help_text %}
|
|
||||||
{% blocktranslate %}Your email address is still pending verification.{% endblocktranslate %}
|
|
||||||
{% element button form="pending-email" type="submit" name="action_send" tags="minor,secondary" %}
|
|
||||||
{% trans 'Re-send Verification' %}
|
|
||||||
{% endelement %}
|
|
||||||
{% if current_emailaddress %}
|
|
||||||
{% element button form="pending-email" type="submit" name="action_remove" tags="danger,minor" %}
|
|
||||||
{% trans 'Cancel Change' %}
|
|
||||||
{% endelement %}
|
|
||||||
{% endif %}
|
|
||||||
{% endslot %}
|
|
||||||
{% endelement %}
|
|
||||||
{% endif %}
|
|
||||||
{% element field id=form.email.auto_id name="email" value=form.email.value errors=form.email.errors type="email" %}
|
|
||||||
{% slot label %}
|
|
||||||
{% translate "Change to" %}:
|
|
||||||
{% endslot %}
|
|
||||||
{% endelement %}
|
|
||||||
{% endslot %}
|
|
||||||
{% slot actions %}
|
|
||||||
{% element button name="action_add" type="submit" %}
|
|
||||||
{% trans "Change Email" %}
|
|
||||||
{% endelement %}
|
|
||||||
{% endslot %}
|
|
||||||
{% endelement %}
|
|
||||||
{% if new_emailaddress %}
|
|
||||||
<form style="display: none"
|
|
||||||
id="pending-email"
|
|
||||||
method="post"
|
|
||||||
action="{% url 'account_email' %}">
|
|
||||||
{% csrf_token %}
|
|
||||||
<input type="hidden" name="email" value="{{ new_emailaddress.email }}">
|
|
||||||
</form>
|
|
||||||
{% endif %}
|
{% endif %}
|
||||||
|
{% endslot %}
|
||||||
|
{% slot help_text %}
|
||||||
|
{% blocktranslate %}Your email address is still pending verification.{% endblocktranslate %}
|
||||||
|
{% element button form="pending-email" type="submit" name="action_send" tags="minor,secondary" %}
|
||||||
|
{% trans 'Re-send Verification' %}
|
||||||
|
{% endelement %}
|
||||||
|
{% if current_emailaddress %}
|
||||||
|
{% element button form="pending-email" type="submit" name="action_remove" tags="danger,minor" %}
|
||||||
|
{% trans 'Cancel Change' %}
|
||||||
|
{% endelement %}
|
||||||
|
{% endif %}
|
||||||
|
{% endslot %}
|
||||||
|
{% endelement %}
|
||||||
|
{% endif %}
|
||||||
|
{% element field id=form.email.auto_id name="email" value=form.email.value errors=form.email.errors type="email" %}
|
||||||
|
{% slot label %}
|
||||||
|
{% translate "Change to" %}:
|
||||||
|
{% endslot %}
|
||||||
|
{% endelement %}
|
||||||
|
{% endslot %}
|
||||||
|
{% slot actions %}
|
||||||
|
{% element button name="action_add" type="submit" %}
|
||||||
|
{% trans "Change Email" %}
|
||||||
|
{% endelement %}
|
||||||
|
{% endslot %}
|
||||||
|
{% endelement %}
|
||||||
|
{% if new_emailaddress %}
|
||||||
|
<form style="display: none"
|
||||||
|
id="pending-email"
|
||||||
|
method="post"
|
||||||
|
action="{% url 'account_email' %}">
|
||||||
|
{% csrf_token %}
|
||||||
|
<input type="hidden" name="email" value="{{ new_emailaddress.email }}">
|
||||||
|
</form>
|
||||||
|
{% endif %}
|
||||||
{% endblock content %}
|
{% endblock content %}
|
||||||
|
|||||||
@ -9,18 +9,24 @@
|
|||||||
<div class="row ">
|
<div class="row ">
|
||||||
<div class="row flex-center min-vh-50">
|
<div class="row flex-center min-vh-50">
|
||||||
<div class="col-sm-10 col-md-8 col-lg-5 col-xl-5 col-xxl-3">
|
<div class="col-sm-10 col-md-8 col-lg-5 col-xl-5 col-xxl-3">
|
||||||
<a class="d-flex flex-center text-decoration-none mb-4" href="{% url 'home' %}">
|
<a class="d-flex flex-center text-decoration-none mb-4"
|
||||||
|
href="{% url 'home' %}">
|
||||||
<div class="d-flex align-items-center fw-bolder fs-3 d-inline-block">
|
<div class="d-flex align-items-center fw-bolder fs-3 d-inline-block">
|
||||||
<div class="d-flex align-items-center fw-bolder fs-3 d-inline-block">
|
<div class="d-flex align-items-center fw-bolder fs-3 d-inline-block">
|
||||||
<img class="d-dark-none" src="{% static 'images/logos/logo-d.png' %}" alt="{% trans 'home' %}" width="58" />
|
<img class="d-dark-none"
|
||||||
<img class="d-light-none" src="{% static 'images/logos/logo.png' %}" alt="{% trans 'home' %}" width="58" />
|
src="{% static 'images/logos/logo-d.png' %}"
|
||||||
|
alt="{% trans 'home' %}"
|
||||||
|
width="58" />
|
||||||
|
<img class="d-light-none"
|
||||||
|
src="{% static 'images/logos/logo.png' %}"
|
||||||
|
alt="{% trans 'home' %}"
|
||||||
|
width="58" />
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</a>
|
</a>
|
||||||
<div class="text-center">
|
<div class="text-center">
|
||||||
<h3 class="mb-4">{% trans "Confirm Email Address" %}</h3>
|
<h3 class="mb-4">{% trans "Confirm Email Address" %}</h3>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
{% if confirmation %}
|
{% if confirmation %}
|
||||||
{% user_display confirmation.email_address.user as user_display %}
|
{% user_display confirmation.email_address.user as user_display %}
|
||||||
{% if can_confirm %}
|
{% if can_confirm %}
|
||||||
@ -31,11 +37,8 @@
|
|||||||
<form class="form" action="{{ action_url }}" method="post">
|
<form class="form" action="{{ action_url }}" method="post">
|
||||||
{% csrf_token %}
|
{% csrf_token %}
|
||||||
{{ redirect_field }}
|
{{ redirect_field }}
|
||||||
<button class="btn btn-sm btn-phoenix-primary" type="submit">
|
<button class="btn btn-sm btn-phoenix-primary" type="submit">{% trans 'Confirm' %}</button>
|
||||||
{% trans 'Confirm' %}
|
|
||||||
</button>
|
|
||||||
</form>
|
</form>
|
||||||
|
|
||||||
{% else %}
|
{% else %}
|
||||||
<p>
|
<p>
|
||||||
{% blocktrans %}Unable to confirm {{ email }} because it is already confirmed by a different account.{% endblocktrans %}
|
{% blocktrans %}Unable to confirm {{ email }} because it is already confirmed by a different account.{% endblocktrans %}
|
||||||
|
|||||||
@ -1,45 +1,66 @@
|
|||||||
<!DOCTYPE html>
|
<!DOCTYPE html>
|
||||||
<html lang="en-US" dir="ltr" data-navigation-type="default" data-navbar-horizontal-shape="default">
|
<html lang="en-US"
|
||||||
|
dir="ltr"
|
||||||
<head>
|
data-navigation-type="default"
|
||||||
<meta charset="utf-8">
|
data-navbar-horizontal-shape="default">
|
||||||
<meta http-equiv="X-UA-Compatible" content="IE=edge">
|
<head>
|
||||||
<meta name="viewport" content="width=device-width, initial-scale=1">
|
<meta charset="utf-8">
|
||||||
|
<meta http-equiv="X-UA-Compatible" content="IE=edge">
|
||||||
|
<meta name="viewport" content="width=device-width, initial-scale=1">
|
||||||
<!-- ===============================================-->
|
<!-- ===============================================-->
|
||||||
<!-- Document Title-->
|
<!-- Document Title-->
|
||||||
<!-- ===============================================-->
|
<!-- ===============================================-->
|
||||||
<title>Phoenix</title>
|
<title>Phoenix</title>
|
||||||
|
<!-- ===============================================-->
|
||||||
|
<!-- Favicons-->
|
||||||
<!-- ===============================================-->
|
<!-- ===============================================-->
|
||||||
<!-- Favicons-->
|
<link rel="apple-touch-icon"
|
||||||
<!-- ===============================================-->
|
sizes="180x180"
|
||||||
<link rel="apple-touch-icon" sizes="180x180" href="../../../assets/img/favicons/apple-touch-icon.png">
|
href="../../../assets/img/favicons/apple-touch-icon.png">
|
||||||
<link rel="icon" type="image/png" sizes="32x32" href="../../../assets/img/favicons/favicon-32x32.png">
|
<link rel="icon"
|
||||||
<link rel="icon" type="image/png" sizes="16x16" href="../../../assets/img/favicons/favicon-16x16.png">
|
type="image/png"
|
||||||
<link rel="shortcut icon" type="image/x-icon" href="../../../assets/img/favicons/favicon.ico">
|
sizes="32x32"
|
||||||
<link rel="manifest" href="../../../assets/img/favicons/manifest.json">
|
href="../../../assets/img/favicons/favicon-32x32.png">
|
||||||
<meta name="msapplication-TileImage" content="../../../assets/img/favicons/mstile-150x150.png">
|
<link rel="icon"
|
||||||
<meta name="theme-color" content="#ffffff">
|
type="image/png"
|
||||||
<script src="../../../vendors/simplebar/simplebar.min.js"></script>
|
sizes="16x16"
|
||||||
<script src="../../../assets/js/config.js"></script>
|
href="../../../assets/img/favicons/favicon-16x16.png">
|
||||||
|
<link rel="shortcut icon"
|
||||||
|
type="image/x-icon"
|
||||||
<!-- ===============================================-->
|
href="../../../assets/img/favicons/favicon.ico">
|
||||||
<!-- Stylesheets-->
|
<link rel="manifest" href="../../../assets/img/favicons/manifest.json">
|
||||||
<!-- ===============================================-->
|
<meta name="msapplication-TileImage"
|
||||||
<link rel="preconnect" href="https://fonts.googleapis.com">
|
content="../../../assets/img/favicons/mstile-150x150.png">
|
||||||
<link rel="preconnect" href="https://fonts.gstatic.com" crossorigin="">
|
<meta name="theme-color" content="#ffffff">
|
||||||
<link href="https://fonts.googleapis.com/css2?family=Nunito+Sans:wght@300;400;600;700;800;900&display=swap" rel="stylesheet">
|
<script src="../../../vendors/simplebar/simplebar.min.js"></script>
|
||||||
<link href="../../../vendors/simplebar/simplebar.min.css" rel="stylesheet">
|
<script src="../../../assets/js/config.js"></script>
|
||||||
<link rel="stylesheet" href="https://unicons.iconscout.com/release/v4.0.8/css/line.css">
|
<!-- ===============================================-->
|
||||||
<link href="../../../assets/css/theme-rtl.min.css" type="text/css" rel="stylesheet" id="style-rtl">
|
<!-- Stylesheets-->
|
||||||
<link href="../../../assets/css/theme.min.css" type="text/css" rel="stylesheet" id="style-default">
|
<!-- ===============================================-->
|
||||||
<link href="../../../assets/css/user-rtl.min.css" type="text/css" rel="stylesheet" id="user-style-rtl">
|
<link rel="preconnect" href="https://fonts.googleapis.com">
|
||||||
<link href="../../../assets/css/user.min.css" type="text/css" rel="stylesheet" id="user-style-default">
|
<link rel="preconnect" href="https://fonts.gstatic.com" crossorigin="">
|
||||||
<script>
|
<link href="https://fonts.googleapis.com/css2?family=Nunito+Sans:wght@300;400;600;700;800;900&display=swap"
|
||||||
|
rel="stylesheet">
|
||||||
|
<link href="../../../vendors/simplebar/simplebar.min.css" rel="stylesheet">
|
||||||
|
<link rel="stylesheet"
|
||||||
|
href="https://unicons.iconscout.com/release/v4.0.8/css/line.css">
|
||||||
|
<link href="../../../assets/css/theme-rtl.min.css"
|
||||||
|
type="text/css"
|
||||||
|
rel="stylesheet"
|
||||||
|
id="style-rtl">
|
||||||
|
<link href="../../../assets/css/theme.min.css"
|
||||||
|
type="text/css"
|
||||||
|
rel="stylesheet"
|
||||||
|
id="style-default">
|
||||||
|
<link href="../../../assets/css/user-rtl.min.css"
|
||||||
|
type="text/css"
|
||||||
|
rel="stylesheet"
|
||||||
|
id="user-style-rtl">
|
||||||
|
<link href="../../../assets/css/user.min.css"
|
||||||
|
type="text/css"
|
||||||
|
rel="stylesheet"
|
||||||
|
id="user-style-default">
|
||||||
|
<script>
|
||||||
var phoenixIsRTL = window.config.config.phoenixIsRTL;
|
var phoenixIsRTL = window.config.config.phoenixIsRTL;
|
||||||
if (phoenixIsRTL) {
|
if (phoenixIsRTL) {
|
||||||
var linkDefault = document.getElementById('style-default');
|
var linkDefault = document.getElementById('style-default');
|
||||||
@ -53,32 +74,41 @@
|
|||||||
linkRTL.setAttribute('disabled', true);
|
linkRTL.setAttribute('disabled', true);
|
||||||
userLinkRTL.setAttribute('disabled', true);
|
userLinkRTL.setAttribute('disabled', true);
|
||||||
}
|
}
|
||||||
</script>
|
</script>
|
||||||
</head>
|
</head>
|
||||||
|
<body>
|
||||||
|
<!-- ===============================================-->
|
||||||
<body>
|
<!-- Main Content-->
|
||||||
|
<!-- ===============================================-->
|
||||||
<!-- ===============================================-->
|
<main class="main" id="top">
|
||||||
<!-- Main Content-->
|
<div class="row">
|
||||||
<!-- ===============================================-->
|
<div class="row flex-center min-vh-100 py-5">
|
||||||
<main class="main" id="top">
|
<div class="col-sm-10 col-md-8 col-lg-5 col-xl-5 col-xxl-3">
|
||||||
<div class="row">
|
<div class="text-center mb-5">
|
||||||
<div class="row flex-center min-vh-100 py-5">
|
<div class="avatar avatar-4xl mb-4">
|
||||||
<div class="col-sm-10 col-md-8 col-lg-5 col-xl-5 col-xxl-3">
|
<img class="rounded-circle" src="../../../assets/img/team/30.webp" alt="" />
|
||||||
<div class="text-center mb-5">
|
</div>
|
||||||
<div class="avatar avatar-4xl mb-4"><img class="rounded-circle" src="../../../assets/img/team/30.webp" alt="" /></div>
|
<h2 class="text-body-highlight">
|
||||||
<h2 class="text-body-highlight"> <span class="fw-normal">Hello </span>John Smith</h2>
|
<span class="fw-normal">Hello</span>John Smith
|
||||||
<p class="text-body-tertiary">Enter your password to access the admin</p>
|
</h2>
|
||||||
|
<p class="text-body-tertiary">Enter your password to access the admin</p>
|
||||||
|
</div>
|
||||||
|
<div class="position-relative" data-password="data-password">
|
||||||
|
<input class="form-control mb-3"
|
||||||
|
id="password"
|
||||||
|
type="password"
|
||||||
|
placeholder="Enter Password"
|
||||||
|
data-password-input="data-password-input" />
|
||||||
|
<button class="btn px-3 py-0 h-100 position-absolute top-0 end-0 fs-7 text-body-tertiary"
|
||||||
|
data-password-toggle="data-password-toggle">
|
||||||
|
<span class="uil uil-eye show"></span><span class="uil uil-eye-slash hide"></span>
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
<a class="btn btn-phoenix-primary w-100" href="../../../index.html">Sign In</a>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div class="position-relative" data-password="data-password">
|
<script>
|
||||||
<input class="form-control mb-3" id="password" type="password" placeholder="Enter Password" data-password-input="data-password-input" />
|
|
||||||
<button class="btn px-3 py-0 h-100 position-absolute top-0 end-0 fs-7 text-body-tertiary" data-password-toggle="data-password-toggle"><span class="uil uil-eye show"></span><span class="uil uil-eye-slash hide"></span></button>
|
|
||||||
</div><a class="btn btn-phoenix-primary w-100" href="../../../index.html">Sign In</a>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
<script>
|
|
||||||
var navbarTopStyle = window.config.config.phoenixNavbarTopStyle;
|
var navbarTopStyle = window.config.config.phoenixNavbarTopStyle;
|
||||||
var navbarTop = document.querySelector('.navbar-top');
|
var navbarTop = document.querySelector('.navbar-top');
|
||||||
if (navbarTopStyle === 'darker') {
|
if (navbarTopStyle === 'darker') {
|
||||||
@ -90,194 +120,470 @@
|
|||||||
if (navbarVertical && navbarVerticalStyle === 'darker') {
|
if (navbarVertical && navbarVerticalStyle === 'darker') {
|
||||||
navbarVertical.setAttribute('data-navbar-appearance', 'darker');
|
navbarVertical.setAttribute('data-navbar-appearance', 'darker');
|
||||||
}
|
}
|
||||||
</script>
|
</script>
|
||||||
<div class="support-chat-row">
|
<div class="support-chat-row">
|
||||||
<div class="row-fluid support-chat">
|
<div class="row-fluid support-chat">
|
||||||
<div class="card bg-body-emphasis">
|
<div class="card bg-body-emphasis">
|
||||||
<div class="card-header d-flex flex-between-center px-4 py-3 border-bottom border-translucent">
|
<div class="card-header d-flex flex-between-center px-4 py-3 border-bottom border-translucent">
|
||||||
<h5 class="mb-0 d-flex align-items-center gap-2">Demo widget<span class="fa-solid fa-circle text-success fs-11"></span></h5>
|
<h5 class="mb-0 d-flex align-items-center gap-2">
|
||||||
<div class="btn-reveal-trigger">
|
Demo widget<span class="fa-solid fa-circle text-success fs-11"></span>
|
||||||
<button class="btn btn-link p-0 dropdown-toggle dropdown-caret-none transition-none d-flex" type="button" id="support-chat-dropdown" data-bs-toggle="dropdown" data-boundary="window" aria-haspopup="true" aria-expanded="false" data-bs-reference="parent"><span class="fas fa-ellipsis-h text-body"></span></button>
|
</h5>
|
||||||
<div class="dropdown-menu dropdown-menu-end py-2" aria-labelledby="support-chat-dropdown"><a class="dropdown-item" href="#!">Request a callback</a><a class="dropdown-item" href="#!">Search in chat</a><a class="dropdown-item" href="#!">Show history</a><a class="dropdown-item" href="#!">Report to Admin</a><a class="dropdown-item btn-support-chat" href="#!">Close Support</a></div>
|
<div class="btn-reveal-trigger">
|
||||||
</div>
|
<button class="btn btn-link p-0 dropdown-toggle dropdown-caret-none transition-none d-flex"
|
||||||
</div>
|
type="button"
|
||||||
<div class="card-body chat p-0">
|
id="support-chat-dropdown"
|
||||||
<div class="d-flex flex-column-reverse scrollbar h-100 p-3">
|
data-bs-toggle="dropdown"
|
||||||
<div class="text-end mt-6"><a class="mb-2 d-inline-flex align-items-center text-decoration-none text-body-emphasis bg-body-hover rounded-pill border border-primary py-2 ps-4 pe-3" href="#!">
|
data-boundary="window"
|
||||||
<p class="mb-0 fw-semibold fs-9">I need help with something</p><span class="fa-solid fa-paper-plane text-primary fs-9 ms-3"></span>
|
aria-haspopup="true"
|
||||||
</a><a class="mb-2 d-inline-flex align-items-center text-decoration-none text-body-emphasis bg-body-hover rounded-pill border border-primary py-2 ps-4 pe-3" href="#!">
|
aria-expanded="false"
|
||||||
<p class="mb-0 fw-semibold fs-9">I can’t reorder a product I previously ordered</p><span class="fa-solid fa-paper-plane text-primary fs-9 ms-3"></span>
|
data-bs-reference="parent">
|
||||||
</a><a class="mb-2 d-inline-flex align-items-center text-decoration-none text-body-emphasis bg-body-hover rounded-pill border border-primary py-2 ps-4 pe-3" href="#!">
|
<span class="fas fa-ellipsis-h text-body"></span>
|
||||||
<p class="mb-0 fw-semibold fs-9">How do I place an order?</p><span class="fa-solid fa-paper-plane text-primary fs-9 ms-3"></span>
|
</button>
|
||||||
</a><a class="false d-inline-flex align-items-center text-decoration-none text-body-emphasis bg-body-hover rounded-pill border border-primary py-2 ps-4 pe-3" href="#!">
|
<div class="dropdown-menu dropdown-menu-end py-2"
|
||||||
<p class="mb-0 fw-semibold fs-9">My payment method not working</p><span class="fa-solid fa-paper-plane text-primary fs-9 ms-3"></span>
|
aria-labelledby="support-chat-dropdown">
|
||||||
</a>
|
<a class="dropdown-item" href="#!">Request a callback</a><a class="dropdown-item" href="#!">Search in chat</a><a class="dropdown-item" href="#!">Show history</a><a class="dropdown-item" href="#!">Report to Admin</a><a class="dropdown-item btn-support-chat" href="#!">Close Support</a>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="card-body chat p-0">
|
||||||
|
<div class="d-flex flex-column-reverse scrollbar h-100 p-3">
|
||||||
|
<div class="text-end mt-6">
|
||||||
|
<a class="mb-2 d-inline-flex align-items-center text-decoration-none text-body-emphasis bg-body-hover rounded-pill border border-primary py-2 ps-4 pe-3"
|
||||||
|
href="#!">
|
||||||
|
<p class="mb-0 fw-semibold fs-9">I need help with something</p>
|
||||||
|
<span class="fa-solid fa-paper-plane text-primary fs-9 ms-3"></span>
|
||||||
|
</a><a class="mb-2 d-inline-flex align-items-center text-decoration-none text-body-emphasis bg-body-hover rounded-pill border border-primary py-2 ps-4 pe-3"
|
||||||
|
href="#!">
|
||||||
|
<p class="mb-0 fw-semibold fs-9">I can’t reorder a product I previously ordered</p>
|
||||||
|
<span class="fa-solid fa-paper-plane text-primary fs-9 ms-3"></span>
|
||||||
|
</a><a class="mb-2 d-inline-flex align-items-center text-decoration-none text-body-emphasis bg-body-hover rounded-pill border border-primary py-2 ps-4 pe-3"
|
||||||
|
href="#!">
|
||||||
|
<p class="mb-0 fw-semibold fs-9">How do I place an order?</p>
|
||||||
|
<span class="fa-solid fa-paper-plane text-primary fs-9 ms-3"></span>
|
||||||
|
</a><a class="false d-inline-flex align-items-center text-decoration-none text-body-emphasis bg-body-hover rounded-pill border border-primary py-2 ps-4 pe-3"
|
||||||
|
href="#!">
|
||||||
|
<p class="mb-0 fw-semibold fs-9">My payment method not working</p>
|
||||||
|
<span class="fa-solid fa-paper-plane text-primary fs-9 ms-3"></span>
|
||||||
|
</a>
|
||||||
|
</div>
|
||||||
|
<div class="text-center mt-auto">
|
||||||
|
<div class="avatar avatar-3xl status-online">
|
||||||
|
<img class="rounded-circle border border-3 border-light-subtle"
|
||||||
|
src="../../../assets/img/team/30.webp"
|
||||||
|
alt="" />
|
||||||
|
</div>
|
||||||
|
<h5 class="mt-2 mb-3">Eric</h5>
|
||||||
|
<p class="text-center text-body-emphasis mb-0">
|
||||||
|
Ask us anything – we’ll get back to you here or by email within 24 hours.
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div class="text-center mt-auto">
|
|
||||||
<div class="avatar avatar-3xl status-online"><img class="rounded-circle border border-3 border-light-subtle" src="../../../assets/img/team/30.webp" alt="" /></div>
|
|
||||||
<h5 class="mt-2 mb-3">Eric</h5>
|
|
||||||
<p class="text-center text-body-emphasis mb-0">Ask us anything – we’ll get back to you here or by email within 24 hours.</p>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
</div>
|
||||||
<div class="card-footer d-flex align-items-center gap-2 border-top border-translucent ps-3 pe-4 py-3">
|
<div class="card-footer d-flex align-items-center gap-2 border-top border-translucent ps-3 pe-4 py-3">
|
||||||
<div class="d-flex align-items-center flex-1 gap-3 border border-translucent rounded-pill px-4">
|
<div class="d-flex align-items-center flex-1 gap-3 border border-translucent rounded-pill px-4">
|
||||||
<input class="form-control outline-none border-0 flex-1 fs-9 px-0" type="text" placeholder="Write message" />
|
<input class="form-control outline-none border-0 flex-1 fs-9 px-0"
|
||||||
<label class="btn btn-link d-flex p-0 text-body-quaternary fs-9 border-0" for="supportChatPhotos"><span class="fa-solid fa-image"></span></label>
|
type="text"
|
||||||
<input class="d-none" type="file" accept="image/*" id="supportChatPhotos" />
|
placeholder="Write message" />
|
||||||
<label class="btn btn-link d-flex p-0 text-body-quaternary fs-9 border-0" for="supportChatAttachment"> <span class="fa-solid fa-paperclip"></span></label>
|
<label class="btn btn-link d-flex p-0 text-body-quaternary fs-9 border-0"
|
||||||
<input class="d-none" type="file" id="supportChatAttachment" />
|
for="supportChatPhotos">
|
||||||
</div>
|
<span class="fa-solid fa-image"></span>
|
||||||
<button class="btn p-0 border-0 send-btn"><span class="fa-solid fa-paper-plane fs-9"></span></button>
|
</label>
|
||||||
|
<input class="d-none" type="file" accept="image/*" id="supportChatPhotos" />
|
||||||
|
<label class="btn btn-link d-flex p-0 text-body-quaternary fs-9 border-0"
|
||||||
|
for="supportChatAttachment">
|
||||||
|
<span class="fa-solid fa-paperclip"></span>
|
||||||
|
</label>
|
||||||
|
<input class="d-none" type="file" id="supportChatAttachment" />
|
||||||
|
</div>
|
||||||
|
<button class="btn p-0 border-0 send-btn">
|
||||||
|
<span class="fa-solid fa-paper-plane fs-9"></span>
|
||||||
|
</button>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
|
||||||
</div>
|
</div>
|
||||||
<button class="btn btn-support-chat p-0 border border-translucent"><span class="fs-8 btn-text text-primary text-nowrap">Chat demo</span><span class="ping-icon-wrapper mt-n4 ms-n6 mt-sm-0 ms-sm-2 position-absolute position-sm-relative"><span class="ping-icon-bg"></span><span class="fa-solid fa-circle ping-icon"></span></span><span class="fa-solid fa-headset text-primary fs-8 d-sm-none"></span><span class="fa-solid fa-chevron-down text-primary fs-7"></span></button>
|
</div>
|
||||||
</div>
|
<button class="btn btn-support-chat p-0 border border-translucent">
|
||||||
</main>
|
<span class="fs-8 btn-text text-primary text-nowrap">Chat demo</span><span class="ping-icon-wrapper mt-n4 ms-n6 mt-sm-0 ms-sm-2 position-absolute position-sm-relative"><span class="ping-icon-bg"></span><span class="fa-solid fa-circle ping-icon"></span></span><span class="fa-solid fa-headset text-primary fs-8 d-sm-none"></span><span class="fa-solid fa-chevron-down text-primary fs-7"></span>
|
||||||
<!-- ===============================================-->
|
</button>
|
||||||
<!-- End of Main Content-->
|
</div>
|
||||||
<!-- ===============================================-->
|
</main>
|
||||||
|
<!-- ===============================================-->
|
||||||
|
<!-- End of Main Content-->
|
||||||
<div class="offcanvas offcanvas-end settings-panel border-0" id="settings-offcanvas" tabindex="-1" aria-labelledby="settings-offcanvas">
|
<!-- ===============================================-->
|
||||||
<div class="offcanvas-header align-items-start border-bottom flex-column border-translucent">
|
<div class="offcanvas offcanvas-end settings-panel border-0"
|
||||||
|
id="settings-offcanvas"
|
||||||
|
tabindex="-1"
|
||||||
|
aria-labelledby="settings-offcanvas">
|
||||||
|
<div class="offcanvas-header align-items-start border-bottom flex-column border-translucent">
|
||||||
<div class="pt-1 w-100 mb-6 d-flex justify-content-between align-items-start">
|
<div class="pt-1 w-100 mb-6 d-flex justify-content-between align-items-start">
|
||||||
<div>
|
<div>
|
||||||
<h5 class="mb-2 me-2 lh-sm"><span class="fas fa-palette me-2 fs-8"></span>Theme Customizer</h5>
|
<h5 class="mb-2 me-2 lh-sm">
|
||||||
<p class="mb-0 fs-9">Explore different styles according to your preferences</p>
|
<span class="fas fa-palette me-2 fs-8"></span>Theme Customizer
|
||||||
</div>
|
</h5>
|
||||||
<button class="btn p-1 fw-bolder" type="button" data-bs-dismiss="offcanvas" aria-label="Close"><span class="fas fa-times fs-8"> </span></button>
|
<p class="mb-0 fs-9">Explore different styles according to your preferences</p>
|
||||||
|
</div>
|
||||||
|
<button class="btn p-1 fw-bolder"
|
||||||
|
type="button"
|
||||||
|
data-bs-dismiss="offcanvas"
|
||||||
|
aria-label="Close">
|
||||||
|
<span class="fas fa-times fs-8"></span>
|
||||||
|
</button>
|
||||||
</div>
|
</div>
|
||||||
<button class="btn btn-phoenix-secondary w-100" data-theme-control="reset"><span class="fas fa-arrows-rotate me-2 fs-10"></span>Reset to default</button>
|
<button class="btn btn-phoenix-secondary w-100" data-theme-control="reset">
|
||||||
</div>
|
<span class="fas fa-arrows-rotate me-2 fs-10"></span>Reset to default
|
||||||
<div class="offcanvas-body scrollbar px-card" id="themeController">
|
</button>
|
||||||
|
</div>
|
||||||
|
<div class="offcanvas-body scrollbar px-card" id="themeController">
|
||||||
<div class="setting-panel-item mt-0">
|
<div class="setting-panel-item mt-0">
|
||||||
<h5 class="setting-panel-item-title">Color Scheme</h5>
|
<h5 class="setting-panel-item-title">Color Scheme</h5>
|
||||||
<div class="row gx-2">
|
<div class="row gx-2">
|
||||||
<div class="col-4">
|
<div class="col-4">
|
||||||
<input class="btn-check" id="themeSwitcherLight" name="theme-color" type="radio" value="light" data-theme-control="phoenixTheme" />
|
<input class="btn-check"
|
||||||
<label class="btn d-inline-block btn-navbar-style fs-9" for="themeSwitcherLight"> <span class="mb-2 rounded d-block"><img class="img-fluid img-prototype mb-0" src="../../../assets/img/generic/default-light.png" alt=""/></span><span class="label-text">Light</span></label>
|
id="themeSwitcherLight"
|
||||||
|
name="theme-color"
|
||||||
|
type="radio"
|
||||||
|
value="light"
|
||||||
|
data-theme-control="phoenixTheme" />
|
||||||
|
<label class="btn d-inline-block btn-navbar-style fs-9"
|
||||||
|
for="themeSwitcherLight">
|
||||||
|
<span class="mb-2 rounded d-block">
|
||||||
|
<img class="img-fluid img-prototype mb-0"
|
||||||
|
src="../../../assets/img/generic/default-light.png"
|
||||||
|
alt="" />
|
||||||
|
</span><span class="label-text">Light</span>
|
||||||
|
</label>
|
||||||
|
</div>
|
||||||
|
<div class="col-4">
|
||||||
|
<input class="btn-check"
|
||||||
|
id="themeSwitcherDark"
|
||||||
|
name="theme-color"
|
||||||
|
type="radio"
|
||||||
|
value="dark"
|
||||||
|
data-theme-control="phoenixTheme" />
|
||||||
|
<label class="btn d-inline-block btn-navbar-style fs-9"
|
||||||
|
for="themeSwitcherDark">
|
||||||
|
<span class="mb-2 rounded d-block">
|
||||||
|
<img class="img-fluid img-prototype mb-0"
|
||||||
|
src="../../../assets/img/generic/default-dark.png"
|
||||||
|
alt="" />
|
||||||
|
</span><span class="label-text">Dark</span>
|
||||||
|
</label>
|
||||||
|
</div>
|
||||||
|
<div class="col-4">
|
||||||
|
<input class="btn-check"
|
||||||
|
id="themeSwitcherAuto"
|
||||||
|
name="theme-color"
|
||||||
|
type="radio"
|
||||||
|
value="auto"
|
||||||
|
data-theme-control="phoenixTheme" />
|
||||||
|
<label class="btn d-inline-block btn-navbar-style fs-9"
|
||||||
|
for="themeSwitcherAuto">
|
||||||
|
<span class="mb-2 rounded d-block">
|
||||||
|
<img class="img-fluid img-prototype mb-0"
|
||||||
|
src="../../../assets/img/generic/auto.png"
|
||||||
|
alt="" />
|
||||||
|
</span><span class="label-text">Auto</span>
|
||||||
|
</label>
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div class="col-4">
|
|
||||||
<input class="btn-check" id="themeSwitcherDark" name="theme-color" type="radio" value="dark" data-theme-control="phoenixTheme" />
|
|
||||||
<label class="btn d-inline-block btn-navbar-style fs-9" for="themeSwitcherDark"> <span class="mb-2 rounded d-block"><img class="img-fluid img-prototype mb-0" src="../../../assets/img/generic/default-dark.png" alt=""/></span><span class="label-text"> Dark</span></label>
|
|
||||||
</div>
|
|
||||||
<div class="col-4">
|
|
||||||
<input class="btn-check" id="themeSwitcherAuto" name="theme-color" type="radio" value="auto" data-theme-control="phoenixTheme" />
|
|
||||||
<label class="btn d-inline-block btn-navbar-style fs-9" for="themeSwitcherAuto"> <span class="mb-2 rounded d-block"><img class="img-fluid img-prototype mb-0" src="../../../assets/img/generic/auto.png" alt=""/></span><span class="label-text"> Auto</span></label>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
</div>
|
||||||
<div class="border border-translucent rounded-3 p-4 setting-panel-item bg-body-emphasis">
|
<div class="border border-translucent rounded-3 p-4 setting-panel-item bg-body-emphasis">
|
||||||
<div class="d-flex justify-content-between align-items-center">
|
<div class="d-flex justify-content-between align-items-center">
|
||||||
<h5 class="setting-panel-item-title mb-1">RTL </h5>
|
<h5 class="setting-panel-item-title mb-1">RTL</h5>
|
||||||
<div class="form-check form-switch mb-0">
|
<div class="form-check form-switch mb-0">
|
||||||
<input class="form-check-input ms-auto" type="checkbox" data-theme-control="phoenixIsRTL" />
|
<input class="form-check-input ms-auto"
|
||||||
|
type="checkbox"
|
||||||
|
data-theme-control="phoenixIsRTL" />
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
<p class="mb-0 text-body-tertiary">Change text direction</p>
|
||||||
<p class="mb-0 text-body-tertiary">Change text direction</p>
|
|
||||||
</div>
|
</div>
|
||||||
<div class="border border-translucent rounded-3 p-4 setting-panel-item bg-body-emphasis">
|
<div class="border border-translucent rounded-3 p-4 setting-panel-item bg-body-emphasis">
|
||||||
<div class="d-flex justify-content-between align-items-center">
|
<div class="d-flex justify-content-between align-items-center">
|
||||||
<h5 class="setting-panel-item-title mb-1">Support Chat </h5>
|
<h5 class="setting-panel-item-title mb-1">Support Chat</h5>
|
||||||
<div class="form-check form-switch mb-0">
|
<div class="form-check form-switch mb-0">
|
||||||
<input class="form-check-input ms-auto" type="checkbox" data-theme-control="phoenixSupportChat" />
|
<input class="form-check-input ms-auto"
|
||||||
|
type="checkbox"
|
||||||
|
data-theme-control="phoenixSupportChat" />
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
<p class="mb-0 text-body-tertiary">Toggle support chat</p>
|
||||||
<p class="mb-0 text-body-tertiary">Toggle support chat</p>
|
|
||||||
</div>
|
</div>
|
||||||
<div class="setting-panel-item">
|
<div class="setting-panel-item">
|
||||||
<h5 class="setting-panel-item-title">Navigation Type</h5>
|
<h5 class="setting-panel-item-title">Navigation Type</h5>
|
||||||
<div class="row gx-2">
|
<div class="row gx-2">
|
||||||
<div class="col-6">
|
<div class="col-6">
|
||||||
<input class="btn-check" id="navbarPositionVertical" name="navigation-type" type="radio" value="vertical" data-theme-control="phoenixNavbarPosition" data-page-url="../../../documentation/layouts/vertical-navbar.html" disabled="disabled" />
|
<input class="btn-check"
|
||||||
<label class="btn d-inline-block btn-navbar-style fs-9" for="navbarPositionVertical"> <span class="rounded d-block"><img class="img-fluid img-prototype d-dark-none" src="../../../assets/img/generic/default-light.png" alt=""/><img class="img-fluid img-prototype d-light-none" src="../../../assets/img/generic/default-dark.png" alt=""/></span><span class="label-text">Vertical</span></label>
|
id="navbarPositionVertical"
|
||||||
|
name="navigation-type"
|
||||||
|
type="radio"
|
||||||
|
value="vertical"
|
||||||
|
data-theme-control="phoenixNavbarPosition"
|
||||||
|
data-page-url="../../../documentation/layouts/vertical-navbar.html"
|
||||||
|
disabled="disabled" />
|
||||||
|
<label class="btn d-inline-block btn-navbar-style fs-9"
|
||||||
|
for="navbarPositionVertical">
|
||||||
|
<span class="rounded d-block">
|
||||||
|
<img class="img-fluid img-prototype d-dark-none"
|
||||||
|
src="../../../assets/img/generic/default-light.png"
|
||||||
|
alt="" />
|
||||||
|
<img class="img-fluid img-prototype d-light-none"
|
||||||
|
src="../../../assets/img/generic/default-dark.png"
|
||||||
|
alt="" />
|
||||||
|
</span><span class="label-text">Vertical</span>
|
||||||
|
</label>
|
||||||
|
</div>
|
||||||
|
<div class="col-6">
|
||||||
|
<input class="btn-check"
|
||||||
|
id="navbarPositionHorizontal"
|
||||||
|
name="navigation-type"
|
||||||
|
type="radio"
|
||||||
|
value="horizontal"
|
||||||
|
data-theme-control="phoenixNavbarPosition"
|
||||||
|
data-page-url="../../../documentation/layouts/horizontal-navbar.html"
|
||||||
|
disabled="disabled" />
|
||||||
|
<label class="btn d-inline-block btn-navbar-style fs-9"
|
||||||
|
for="navbarPositionHorizontal">
|
||||||
|
<span class="rounded d-block">
|
||||||
|
<img class="img-fluid img-prototype d-dark-none"
|
||||||
|
src="../../../assets/img/generic/top-default.png"
|
||||||
|
alt="" />
|
||||||
|
<img class="img-fluid img-prototype d-light-none"
|
||||||
|
src="../../../assets/img/generic/top-default-dark.png"
|
||||||
|
alt="" />
|
||||||
|
</span><span class="label-text">Horizontal</span>
|
||||||
|
</label>
|
||||||
|
</div>
|
||||||
|
<div class="col-6">
|
||||||
|
<input class="btn-check"
|
||||||
|
id="navbarPositionCombo"
|
||||||
|
name="navigation-type"
|
||||||
|
type="radio"
|
||||||
|
value="combo"
|
||||||
|
data-theme-control="phoenixNavbarPosition"
|
||||||
|
disabled="disabled"
|
||||||
|
data-page-url="../../../documentation/layouts/combo-navbar.html" />
|
||||||
|
<label class="btn d-inline-block btn-navbar-style fs-9"
|
||||||
|
for="navbarPositionCombo">
|
||||||
|
<span class="rounded d-block">
|
||||||
|
<img class="img-fluid img-prototype d-dark-none"
|
||||||
|
src="../../../assets/img/generic/nav-combo-light.png"
|
||||||
|
alt="" />
|
||||||
|
<img class="img-fluid img-prototype d-light-none"
|
||||||
|
src="../../../assets/img/generic/nav-combo-dark.png"
|
||||||
|
alt="" />
|
||||||
|
</span><span class="label-text">Combo</span>
|
||||||
|
</label>
|
||||||
|
</div>
|
||||||
|
<div class="col-6">
|
||||||
|
<input class="btn-check"
|
||||||
|
id="navbarPositionTopDouble"
|
||||||
|
name="navigation-type"
|
||||||
|
type="radio"
|
||||||
|
value="dual-nav"
|
||||||
|
data-theme-control="phoenixNavbarPosition"
|
||||||
|
disabled="disabled"
|
||||||
|
data-page-url="../../../documentation/layouts/dual-nav.html" />
|
||||||
|
<label class="btn d-inline-block btn-navbar-style fs-9"
|
||||||
|
for="navbarPositionTopDouble">
|
||||||
|
<span class="rounded d-block">
|
||||||
|
<img class="img-fluid img-prototype d-dark-none"
|
||||||
|
src="../../../assets/img/generic/dual-light.png"
|
||||||
|
alt="" />
|
||||||
|
<img class="img-fluid img-prototype d-light-none"
|
||||||
|
src="../../../assets/img/generic/dual-dark.png"
|
||||||
|
alt="" />
|
||||||
|
</span><span class="label-text">Dual nav</span>
|
||||||
|
</label>
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div class="col-6">
|
<p class="text-warning-dark font-medium">
|
||||||
<input class="btn-check" id="navbarPositionHorizontal" name="navigation-type" type="radio" value="horizontal" data-theme-control="phoenixNavbarPosition" data-page-url="../../../documentation/layouts/horizontal-navbar.html" disabled="disabled" />
|
<span class="fa-solid fa-triangle-exclamation me-2 text-warning"></span>You can't update navigation type in this page
|
||||||
<label class="btn d-inline-block btn-navbar-style fs-9" for="navbarPositionHorizontal"> <span class="rounded d-block"><img class="img-fluid img-prototype d-dark-none" src="../../../assets/img/generic/top-default.png" alt=""/><img class="img-fluid img-prototype d-light-none" src="../../../assets/img/generic/top-default-dark.png" alt=""/></span><span class="label-text"> Horizontal</span></label>
|
</p>
|
||||||
</div>
|
|
||||||
<div class="col-6">
|
|
||||||
<input class="btn-check" id="navbarPositionCombo" name="navigation-type" type="radio" value="combo" data-theme-control="phoenixNavbarPosition" disabled="disabled" data-page-url="../../../documentation/layouts/combo-navbar.html" />
|
|
||||||
<label class="btn d-inline-block btn-navbar-style fs-9" for="navbarPositionCombo"> <span class="rounded d-block"><img class="img-fluid img-prototype d-dark-none" src="../../../assets/img/generic/nav-combo-light.png" alt=""/><img class="img-fluid img-prototype d-light-none" src="../../../assets/img/generic/nav-combo-dark.png" alt=""/></span><span class="label-text"> Combo</span></label>
|
|
||||||
</div>
|
|
||||||
<div class="col-6">
|
|
||||||
<input class="btn-check" id="navbarPositionTopDouble" name="navigation-type" type="radio" value="dual-nav" data-theme-control="phoenixNavbarPosition" disabled="disabled" data-page-url="../../../documentation/layouts/dual-nav.html" />
|
|
||||||
<label class="btn d-inline-block btn-navbar-style fs-9" for="navbarPositionTopDouble"> <span class="rounded d-block"><img class="img-fluid img-prototype d-dark-none" src="../../../assets/img/generic/dual-light.png" alt=""/><img class="img-fluid img-prototype d-light-none" src="../../../assets/img/generic/dual-dark.png" alt=""/></span><span class="label-text"> Dual nav</span></label>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
<p class="text-warning-dark font-medium"> <span class="fa-solid fa-triangle-exclamation me-2 text-warning"></span>You can't update navigation type in this page</p>
|
|
||||||
</div>
|
</div>
|
||||||
<div class="setting-panel-item">
|
<div class="setting-panel-item">
|
||||||
<h5 class="setting-panel-item-title">Vertical Navbar Appearance</h5>
|
<h5 class="setting-panel-item-title">Vertical Navbar Appearance</h5>
|
||||||
<div class="row gx-2">
|
<div class="row gx-2">
|
||||||
<div class="col-6">
|
<div class="col-6">
|
||||||
<input class="btn-check" id="navbar-style-default" type="radio" name="config.name" value="default" data-theme-control="phoenixNavbarVerticalStyle" disabled="disabled" />
|
<input class="btn-check"
|
||||||
<label class="btn d-block w-100 btn-navbar-style fs-9" for="navbar-style-default"> <img class="img-fluid img-prototype d-dark-none" src="../../../assets/img/generic/default-light.png" alt="" /><img class="img-fluid img-prototype d-light-none" src="../../../assets/img/generic/default-dark.png" alt="" /><span class="label-text d-dark-none"> Default</span><span class="label-text d-light-none">Default</span></label>
|
id="navbar-style-default"
|
||||||
|
type="radio"
|
||||||
|
name="config.name"
|
||||||
|
value="default"
|
||||||
|
data-theme-control="phoenixNavbarVerticalStyle"
|
||||||
|
disabled="disabled" />
|
||||||
|
<label class="btn d-block w-100 btn-navbar-style fs-9"
|
||||||
|
for="navbar-style-default">
|
||||||
|
<img class="img-fluid img-prototype d-dark-none"
|
||||||
|
src="../../../assets/img/generic/default-light.png"
|
||||||
|
alt="" />
|
||||||
|
<img class="img-fluid img-prototype d-light-none"
|
||||||
|
src="../../../assets/img/generic/default-dark.png"
|
||||||
|
alt="" />
|
||||||
|
<span class="label-text d-dark-none">Default</span><span class="label-text d-light-none">Default</span>
|
||||||
|
</label>
|
||||||
|
</div>
|
||||||
|
<div class="col-6">
|
||||||
|
<input class="btn-check"
|
||||||
|
id="navbar-style-dark"
|
||||||
|
type="radio"
|
||||||
|
name="config.name"
|
||||||
|
value="darker"
|
||||||
|
data-theme-control="phoenixNavbarVerticalStyle"
|
||||||
|
disabled="disabled" />
|
||||||
|
<label class="btn d-block w-100 btn-navbar-style fs-9"
|
||||||
|
for="navbar-style-dark">
|
||||||
|
<img class="img-fluid img-prototype d-dark-none"
|
||||||
|
src="../../../assets/img/generic/vertical-darker.png"
|
||||||
|
alt="" />
|
||||||
|
<img class="img-fluid img-prototype d-light-none"
|
||||||
|
src="../../../assets/img/generic/vertical-lighter.png"
|
||||||
|
alt="" />
|
||||||
|
<span class="label-text d-dark-none">Darker</span><span class="label-text d-light-none">Lighter</span>
|
||||||
|
</label>
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div class="col-6">
|
<p class="text-warning-dark font-medium">
|
||||||
<input class="btn-check" id="navbar-style-dark" type="radio" name="config.name" value="darker" data-theme-control="phoenixNavbarVerticalStyle" disabled="disabled" />
|
<span class="fa-solid fa-triangle-exclamation me-2 text-warning"></span>You can't update vertical navbar appearance in this page
|
||||||
<label class="btn d-block w-100 btn-navbar-style fs-9" for="navbar-style-dark"> <img class="img-fluid img-prototype d-dark-none" src="../../../assets/img/generic/vertical-darker.png" alt="" /><img class="img-fluid img-prototype d-light-none" src="../../../assets/img/generic/vertical-lighter.png" alt="" /><span class="label-text d-dark-none"> Darker</span><span class="label-text d-light-none">Lighter</span></label>
|
</p>
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
<p class="text-warning-dark font-medium"> <span class="fa-solid fa-triangle-exclamation me-2 text-warning"></span>You can't update vertical navbar appearance in this page</p>
|
|
||||||
</div>
|
</div>
|
||||||
<div class="setting-panel-item">
|
<div class="setting-panel-item">
|
||||||
<h5 class="setting-panel-item-title">Horizontal Navbar Shape</h5>
|
<h5 class="setting-panel-item-title">Horizontal Navbar Shape</h5>
|
||||||
<div class="row gx-2">
|
<div class="row gx-2">
|
||||||
<div class="col-6">
|
<div class="col-6">
|
||||||
<input class="btn-check" id="navbarShapeDefault" name="navbar-shape" type="radio" value="default" data-theme-control="phoenixNavbarTopShape" data-page-url="../../../documentation/layouts/horizontal-navbar.html" disabled="disabled" />
|
<input class="btn-check"
|
||||||
<label class="btn d-inline-block btn-navbar-style fs-9" for="navbarShapeDefault"> <span class="mb-2 rounded d-block"><img class="img-fluid img-prototype d-dark-none mb-0" src="../../../assets/img/generic/top-default.png" alt=""/><img class="img-fluid img-prototype d-light-none mb-0" src="../../../assets/img/generic/top-default-dark.png" alt=""/></span><span class="label-text">Default</span></label>
|
id="navbarShapeDefault"
|
||||||
|
name="navbar-shape"
|
||||||
|
type="radio"
|
||||||
|
value="default"
|
||||||
|
data-theme-control="phoenixNavbarTopShape"
|
||||||
|
data-page-url="../../../documentation/layouts/horizontal-navbar.html"
|
||||||
|
disabled="disabled" />
|
||||||
|
<label class="btn d-inline-block btn-navbar-style fs-9"
|
||||||
|
for="navbarShapeDefault">
|
||||||
|
<span class="mb-2 rounded d-block">
|
||||||
|
<img class="img-fluid img-prototype d-dark-none mb-0"
|
||||||
|
src="../../../assets/img/generic/top-default.png"
|
||||||
|
alt="" />
|
||||||
|
<img class="img-fluid img-prototype d-light-none mb-0"
|
||||||
|
src="../../../assets/img/generic/top-default-dark.png"
|
||||||
|
alt="" />
|
||||||
|
</span><span class="label-text">Default</span>
|
||||||
|
</label>
|
||||||
|
</div>
|
||||||
|
<div class="col-6">
|
||||||
|
<input class="btn-check"
|
||||||
|
id="navbarShapeSlim"
|
||||||
|
name="navbar-shape"
|
||||||
|
type="radio"
|
||||||
|
value="slim"
|
||||||
|
data-theme-control="phoenixNavbarTopShape"
|
||||||
|
data-page-url="../../../documentation/layouts/horizontal-navbar.html#horizontal-navbar-slim"
|
||||||
|
disabled="disabled" />
|
||||||
|
<label class="btn d-inline-block btn-navbar-style fs-9"
|
||||||
|
for="navbarShapeSlim">
|
||||||
|
<span class="mb-2 rounded d-block">
|
||||||
|
<img class="img-fluid img-prototype d-dark-none mb-0"
|
||||||
|
src="../../../assets/img/generic/top-slim.png"
|
||||||
|
alt="" />
|
||||||
|
<img class="img-fluid img-prototype d-light-none mb-0"
|
||||||
|
src="../../../assets/img/generic/top-slim-dark.png"
|
||||||
|
alt="" />
|
||||||
|
</span><span class="label-text">Slim</span>
|
||||||
|
</label>
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div class="col-6">
|
<p class="text-warning-dark font-medium">
|
||||||
<input class="btn-check" id="navbarShapeSlim" name="navbar-shape" type="radio" value="slim" data-theme-control="phoenixNavbarTopShape" data-page-url="../../../documentation/layouts/horizontal-navbar.html#horizontal-navbar-slim" disabled="disabled" />
|
<span class="fa-solid fa-triangle-exclamation me-2 text-warning"></span>You can't update horizontal navbar shape in this page
|
||||||
<label class="btn d-inline-block btn-navbar-style fs-9" for="navbarShapeSlim"> <span class="mb-2 rounded d-block"><img class="img-fluid img-prototype d-dark-none mb-0" src="../../../assets/img/generic/top-slim.png" alt=""/><img class="img-fluid img-prototype d-light-none mb-0" src="../../../assets/img/generic/top-slim-dark.png" alt=""/></span><span class="label-text"> Slim</span></label>
|
</p>
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
<p class="text-warning-dark font-medium"> <span class="fa-solid fa-triangle-exclamation me-2 text-warning"></span>You can't update horizontal navbar shape in this page</p>
|
|
||||||
</div>
|
</div>
|
||||||
<div class="setting-panel-item">
|
<div class="setting-panel-item">
|
||||||
<h5 class="setting-panel-item-title">Horizontal Navbar Appearance</h5>
|
<h5 class="setting-panel-item-title">Horizontal Navbar Appearance</h5>
|
||||||
<div class="row gx-2">
|
<div class="row gx-2">
|
||||||
<div class="col-6">
|
<div class="col-6">
|
||||||
<input class="btn-check" id="navbarTopDefault" name="navbar-top-style" type="radio" value="default" data-theme-control="phoenixNavbarTopStyle" disabled="disabled" />
|
<input class="btn-check"
|
||||||
<label class="btn d-inline-block btn-navbar-style fs-9" for="navbarTopDefault"> <span class="mb-2 rounded d-block"><img class="img-fluid img-prototype d-dark-none mb-0" src="../../../assets/img/generic/top-default.png" alt=""/><img class="img-fluid img-prototype d-light-none mb-0" src="../../../assets/img/generic/top-style-darker.png" alt=""/></span><span class="label-text">Default</span></label>
|
id="navbarTopDefault"
|
||||||
|
name="navbar-top-style"
|
||||||
|
type="radio"
|
||||||
|
value="default"
|
||||||
|
data-theme-control="phoenixNavbarTopStyle"
|
||||||
|
disabled="disabled" />
|
||||||
|
<label class="btn d-inline-block btn-navbar-style fs-9"
|
||||||
|
for="navbarTopDefault">
|
||||||
|
<span class="mb-2 rounded d-block">
|
||||||
|
<img class="img-fluid img-prototype d-dark-none mb-0"
|
||||||
|
src="../../../assets/img/generic/top-default.png"
|
||||||
|
alt="" />
|
||||||
|
<img class="img-fluid img-prototype d-light-none mb-0"
|
||||||
|
src="../../../assets/img/generic/top-style-darker.png"
|
||||||
|
alt="" />
|
||||||
|
</span><span class="label-text">Default</span>
|
||||||
|
</label>
|
||||||
|
</div>
|
||||||
|
<div class="col-6">
|
||||||
|
<input class="btn-check"
|
||||||
|
id="navbarTopDarker"
|
||||||
|
name="navbar-top-style"
|
||||||
|
type="radio"
|
||||||
|
value="darker"
|
||||||
|
data-theme-control="phoenixNavbarTopStyle"
|
||||||
|
disabled="disabled" />
|
||||||
|
<label class="btn d-inline-block btn-navbar-style fs-9"
|
||||||
|
for="navbarTopDarker">
|
||||||
|
<span class="mb-2 rounded d-block">
|
||||||
|
<img class="img-fluid img-prototype d-dark-none mb-0"
|
||||||
|
src="../../../assets/img/generic/navbar-top-style-light.png"
|
||||||
|
alt="" />
|
||||||
|
<img class="img-fluid img-prototype d-light-none mb-0"
|
||||||
|
src="../../../assets/img/generic/top-style-lighter.png"
|
||||||
|
alt="" />
|
||||||
|
</span><span class="label-text d-dark-none">Darker</span><span class="label-text d-light-none">Lighter</span>
|
||||||
|
</label>
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div class="col-6">
|
<p class="text-warning-dark font-medium">
|
||||||
<input class="btn-check" id="navbarTopDarker" name="navbar-top-style" type="radio" value="darker" data-theme-control="phoenixNavbarTopStyle" disabled="disabled" />
|
<span class="fa-solid fa-triangle-exclamation me-2 text-warning"></span>You can't update horizontal navbar appearance in this page
|
||||||
<label class="btn d-inline-block btn-navbar-style fs-9" for="navbarTopDarker"> <span class="mb-2 rounded d-block"><img class="img-fluid img-prototype d-dark-none mb-0" src="../../../assets/img/generic/navbar-top-style-light.png" alt=""/><img class="img-fluid img-prototype d-light-none mb-0" src="../../../assets/img/generic/top-style-lighter.png" alt=""/></span><span class="label-text d-dark-none">Darker</span><span class="label-text d-light-none">Lighter</span></label>
|
</p>
|
||||||
|
</div>
|
||||||
|
<a class="bun btn-primary d-grid mb-3 text-white mt-5 btn btn-primary"
|
||||||
|
href="https://themes.getbootstrap.com/product/phoenix-admin-dashboard-webapp-template/"
|
||||||
|
target="_blank">Purchase template</a>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<a class="card setting-toggle"
|
||||||
|
href="#settings-offcanvas"
|
||||||
|
data-bs-toggle="offcanvas">
|
||||||
|
<div class="card-body d-flex align-items-center px-2 py-1">
|
||||||
|
<div class="position-relative rounded-start"
|
||||||
|
style="height:34px;
|
||||||
|
width:28px">
|
||||||
|
<div class="settings-popover">
|
||||||
|
<span class="ripple"><span class="fa-spin position-absolute all-0 d-flex flex-center"><span class="icon-spin position-absolute all-0 d-flex flex-center">
|
||||||
|
<svg width="20"
|
||||||
|
height="20"
|
||||||
|
viewBox="0 0 20 20"
|
||||||
|
fill="#ffffff"
|
||||||
|
xmlns="http://www.w3.org/2000/svg">
|
||||||
|
<path d="M19.7369 12.3941L19.1989 12.1065C18.4459 11.7041 18.0843 10.8487 18.0843 9.99495C18.0843 9.14118 18.4459 8.28582 19.1989 7.88336L19.7369 7.59581C19.9474 7.47484 20.0316 7.23291 19.9474 7.03131C19.4842 5.57973 18.6843 4.28943 17.6738 3.20075C17.5053 3.03946 17.2527 2.99914 17.0422 3.12011L16.393 3.46714C15.6883 3.84379 14.8377 3.74529 14.1476 3.3427C14.0988 3.31422 14.0496 3.28621 14.0002 3.25868C13.2568 2.84453 12.7055 2.10629 12.7055 1.25525V0.70081C12.7055 0.499202 12.5371 0.297594 12.2845 0.257272C10.7266 -0.105622 9.16879 -0.0653007 7.69516 0.257272C7.44254 0.297594 7.31623 0.499202 7.31623 0.70081V1.23474C7.31623 2.09575 6.74999 2.8362 5.99824 3.25599C5.95774 3.27861 5.91747 3.30159 5.87744 3.32493C5.15643 3.74527 4.26453 3.85902 3.53534 3.45302L2.93743 3.12011C2.72691 2.99914 2.47429 3.03946 2.30587 3.20075C1.29538 4.28943 0.495411 5.57973 0.0322686 7.03131C-0.051939 7.23291 0.0322686 7.47484 0.242788 7.59581L0.784376 7.8853C1.54166 8.29007 1.92694 9.13627 1.92694 9.99495C1.92694 10.8536 1.54166 11.6998 0.784375 12.1046L0.242788 12.3941C0.0322686 12.515 -0.051939 12.757 0.0322686 12.9586C0.495411 14.4102 1.29538 15.7005 2.30587 16.7891C2.47429 16.9504 2.72691 16.9907 2.93743 16.8698L3.58669 16.5227C4.29133 16.1461 5.14131 16.2457 5.8331 16.6455C5.88713 16.6767 5.94159 16.7074 5.99648 16.7375C6.75162 17.1511 7.31623 17.8941 7.31623 18.7552V19.2891C7.31623 19.4425 7.41373 19.5959 7.55309 19.696C7.64066 19.7589 7.74815 19.7843 7.85406 19.8046C9.35884 20.0925 10.8609 20.0456 12.2845 19.7729C12.5371 19.6923 12.7055 19.4907 12.7055 19.2891V18.7346C12.7055 17.8836 13.2568 17.1454 14.0002 16.7312C14.0496 16.7037 14.0988 16.6757 14.1476 16.6472C14.8377 16.2446 15.6883 16.1461 16.393 16.5227L17.0422 16.8698C17.2527 16.9907 17.5053 16.9504 17.6738 16.7891C18.7264 15.7005 19.4842 14.4102 19.9895 12.9586C20.0316 12.757 19.9474 12.515 19.7369 12.3941ZM10.0109 13.2005C8.1162 13.2005 6.64257 11.7893 6.64257 9.97478C6.64257 8.20063 8.1162 6.74905 10.0109 6.74905C11.8634 6.74905 13.3792 8.20063 13.3792 9.97478C13.3792 11.7893 11.8634 13.2005 10.0109 13.2005Z" fill="#2A7BE4">
|
||||||
|
</path>
|
||||||
|
</svg>
|
||||||
|
</span></span></span>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<p class="text-warning-dark font-medium"> <span class="fa-solid fa-triangle-exclamation me-2 text-warning"></span>You can't update horizontal navbar appearance in this page</p>
|
<small class="text-uppercase text-body-tertiary fw-bold py-2 pe-2 ps-1 rounded-end">customize</small>
|
||||||
</div><a class="bun btn-primary d-grid mb-3 text-white mt-5 btn btn-primary" href="https://themes.getbootstrap.com/product/phoenix-admin-dashboard-webapp-template/" target="_blank">Purchase template</a>
|
</div>
|
||||||
</div>
|
</a>
|
||||||
</div><a class="card setting-toggle" href="#settings-offcanvas" data-bs-toggle="offcanvas">
|
<!-- ===============================================-->
|
||||||
<div class="card-body d-flex align-items-center px-2 py-1">
|
<!-- JavaScripts-->
|
||||||
<div class="position-relative rounded-start" style="height:34px;width:28px">
|
<!-- ===============================================-->
|
||||||
<div class="settings-popover"><span class="ripple"><span class="fa-spin position-absolute all-0 d-flex flex-center"><span class="icon-spin position-absolute all-0 d-flex flex-center">
|
<script src="../../../vendors/popper/popper.min.js"></script>
|
||||||
<svg width="20" height="20" viewBox="0 0 20 20" fill="#ffffff" xmlns="http://www.w3.org/2000/svg">
|
<script src="../../../vendors/bootstrap/bootstrap.min.js"></script>
|
||||||
<path d="M19.7369 12.3941L19.1989 12.1065C18.4459 11.7041 18.0843 10.8487 18.0843 9.99495C18.0843 9.14118 18.4459 8.28582 19.1989 7.88336L19.7369 7.59581C19.9474 7.47484 20.0316 7.23291 19.9474 7.03131C19.4842 5.57973 18.6843 4.28943 17.6738 3.20075C17.5053 3.03946 17.2527 2.99914 17.0422 3.12011L16.393 3.46714C15.6883 3.84379 14.8377 3.74529 14.1476 3.3427C14.0988 3.31422 14.0496 3.28621 14.0002 3.25868C13.2568 2.84453 12.7055 2.10629 12.7055 1.25525V0.70081C12.7055 0.499202 12.5371 0.297594 12.2845 0.257272C10.7266 -0.105622 9.16879 -0.0653007 7.69516 0.257272C7.44254 0.297594 7.31623 0.499202 7.31623 0.70081V1.23474C7.31623 2.09575 6.74999 2.8362 5.99824 3.25599C5.95774 3.27861 5.91747 3.30159 5.87744 3.32493C5.15643 3.74527 4.26453 3.85902 3.53534 3.45302L2.93743 3.12011C2.72691 2.99914 2.47429 3.03946 2.30587 3.20075C1.29538 4.28943 0.495411 5.57973 0.0322686 7.03131C-0.051939 7.23291 0.0322686 7.47484 0.242788 7.59581L0.784376 7.8853C1.54166 8.29007 1.92694 9.13627 1.92694 9.99495C1.92694 10.8536 1.54166 11.6998 0.784375 12.1046L0.242788 12.3941C0.0322686 12.515 -0.051939 12.757 0.0322686 12.9586C0.495411 14.4102 1.29538 15.7005 2.30587 16.7891C2.47429 16.9504 2.72691 16.9907 2.93743 16.8698L3.58669 16.5227C4.29133 16.1461 5.14131 16.2457 5.8331 16.6455C5.88713 16.6767 5.94159 16.7074 5.99648 16.7375C6.75162 17.1511 7.31623 17.8941 7.31623 18.7552V19.2891C7.31623 19.4425 7.41373 19.5959 7.55309 19.696C7.64066 19.7589 7.74815 19.7843 7.85406 19.8046C9.35884 20.0925 10.8609 20.0456 12.2845 19.7729C12.5371 19.6923 12.7055 19.4907 12.7055 19.2891V18.7346C12.7055 17.8836 13.2568 17.1454 14.0002 16.7312C14.0496 16.7037 14.0988 16.6757 14.1476 16.6472C14.8377 16.2446 15.6883 16.1461 16.393 16.5227L17.0422 16.8698C17.2527 16.9907 17.5053 16.9504 17.6738 16.7891C18.7264 15.7005 19.4842 14.4102 19.9895 12.9586C20.0316 12.757 19.9474 12.515 19.7369 12.3941ZM10.0109 13.2005C8.1162 13.2005 6.64257 11.7893 6.64257 9.97478C6.64257 8.20063 8.1162 6.74905 10.0109 6.74905C11.8634 6.74905 13.3792 8.20063 13.3792 9.97478C13.3792 11.7893 11.8634 13.2005 10.0109 13.2005Z" fill="#2A7BE4"></path>
|
<script src="../../../vendors/anchorjs/anchor.min.js"></script>
|
||||||
</svg></span></span></span></div>
|
<script src="../../../vendors/is/is.min.js"></script>
|
||||||
</div><small class="text-uppercase text-body-tertiary fw-bold py-2 pe-2 ps-1 rounded-end">customize</small>
|
<script src="../../../vendors/fontawesome/all.min.js"></script>
|
||||||
</div>
|
<script src="../../../vendors/lodash/lodash.min.js"></script>
|
||||||
</a>
|
<script src="../../../vendors/list.js/list.min.js"></script>
|
||||||
|
<script src="../../../vendors/feather-icons/feather.min.js"></script>
|
||||||
|
<script src="../../../vendors/dayjs/dayjs.min.js"></script>
|
||||||
<!-- ===============================================-->
|
<script src="../../../assets/js/phoenix.js"></script>
|
||||||
<!-- JavaScripts-->
|
</body>
|
||||||
<!-- ===============================================-->
|
</html>
|
||||||
<script src="../../../vendors/popper/popper.min.js"></script>
|
|
||||||
<script src="../../../vendors/bootstrap/bootstrap.min.js"></script>
|
|
||||||
<script src="../../../vendors/anchorjs/anchor.min.js"></script>
|
|
||||||
<script src="../../../vendors/is/is.min.js"></script>
|
|
||||||
<script src="../../../vendors/fontawesome/all.min.js"></script>
|
|
||||||
<script src="../../../vendors/lodash/lodash.min.js"></script>
|
|
||||||
<script src="../../../vendors/list.js/list.min.js"></script>
|
|
||||||
<script src="../../../vendors/feather-icons/feather.min.js"></script>
|
|
||||||
<script src="../../../vendors/dayjs/dayjs.min.js"></script>
|
|
||||||
<script src="../../../assets/js/phoenix.js"></script>
|
|
||||||
|
|
||||||
</body>
|
|
||||||
|
|
||||||
</html>
|
|
||||||
|
|||||||
@ -7,49 +7,67 @@
|
|||||||
{% endblock head_title %}
|
{% endblock head_title %}
|
||||||
{% block content %}
|
{% block content %}
|
||||||
<section class="main mt-2">
|
<section class="main mt-2">
|
||||||
|
|
||||||
<div class="row flex-center ">
|
<div class="row flex-center ">
|
||||||
<div class="col-sm-10 col-md-8 col-lg-5 col-xl-5 col-xxl-3">
|
<div class="col-sm-10 col-md-8 col-lg-5 col-xl-5 col-xxl-3">
|
||||||
<a class="d-flex flex-center text-decoration-none mb-4" href="{% url 'home' %}">
|
<a class="d-flex flex-center text-decoration-none mb-4"
|
||||||
|
href="{% url 'home' %}">
|
||||||
<div class="d-flex align-items-center fw-bolder fs-3 d-inline-block">
|
<div class="d-flex align-items-center fw-bolder fs-3 d-inline-block">
|
||||||
<div class="d-flex align-items-center fw-bolder fs-3 d-inline-block">
|
<div class="d-flex align-items-center fw-bolder fs-3 d-inline-block">
|
||||||
<img class="d-dark-none" src="{% static 'images/logos/logo-d.png' %}" alt="{% trans 'home' %}" width="58" />
|
<img class="d-dark-none"
|
||||||
<img class="d-light-none" src="{% static 'images/logos/logo.png' %}" alt="{% trans 'home' %}" width="58" />
|
src="{% static 'images/logos/logo-d.png' %}"
|
||||||
|
alt="{% trans 'home' %}"
|
||||||
|
width="58" />
|
||||||
|
<img class="d-light-none"
|
||||||
|
src="{% static 'images/logos/logo.png' %}"
|
||||||
|
alt="{% trans 'home' %}"
|
||||||
|
width="58" />
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</a>
|
</a>
|
||||||
<div class="text-center">
|
<div class="text-center">
|
||||||
<h3 class="mb-4">{% trans "Sign In" %}</h3>
|
<h3 class="mb-4">{% trans "Sign In" %}</h3>
|
||||||
{% if not SOCIALACCOUNT_ONLY %}
|
{% if not SOCIALACCOUNT_ONLY %}
|
||||||
|
<form method="post"
|
||||||
|
action="{% url 'account_login' %}"
|
||||||
<form method="post" action="{% url 'account_login' %}" class="form needs-validation" novalidate >
|
class="form needs-validation"
|
||||||
|
novalidate>
|
||||||
{% csrf_token %}
|
{% csrf_token %}
|
||||||
<div class="mb-3 ">
|
<div class="mb-3 ">
|
||||||
<label class="form-label" for="id_login" >{{ _("Email") }}</label>
|
<label class="form-label" for="id_login">{{ _("Email") }}</label>
|
||||||
<div class="form-icon-container">
|
<div class="form-icon-container">
|
||||||
<input type="email" name="login" id="id_login" class="form-control form-icon-input placeholder-center" placeholder="{{ _("Email") }}" required >
|
<input type="email"
|
||||||
|
name="login"
|
||||||
|
id="id_login"
|
||||||
|
class="form-control form-icon-input placeholder-center"
|
||||||
|
placeholder="{{ _("Email") }}"
|
||||||
|
required>
|
||||||
<span class="fas fa-user text-body fs-9 form-icon"></span>
|
<span class="fas fa-user text-body fs-9 form-icon"></span>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div class="mb-3 ">
|
<div class="mb-3 ">
|
||||||
<label class="form-label" for="id_password">{{ _("Password") }}</label>
|
<label class="form-label" for="id_password">{{ _("Password") }}</label>
|
||||||
<div class="form-icon-container">
|
<div class="form-icon-container">
|
||||||
<input type="password" name="password" id="id_password" class="form-control form-icon-input placeholder-center" placeholder="{{ _("Password") }}" required >
|
<input type="password"
|
||||||
|
name="password"
|
||||||
|
id="id_password"
|
||||||
|
class="form-control form-icon-input placeholder-center"
|
||||||
|
placeholder="{{ _("Password") }}"
|
||||||
|
required>
|
||||||
<span class="fas fa-key text-body fs-9 form-icon"></span>
|
<span class="fas fa-key text-body fs-9 form-icon"></span>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div class="form-group form-check">
|
<div class="form-group form-check">
|
||||||
<input type="checkbox" name="remember" id="id_remember" class="form-check-input">
|
<input type="checkbox"
|
||||||
<label class="form-check-label mb-0 fs-9" for="id_remember">{{ _("Remember Me")}}</label>
|
name="remember"
|
||||||
|
id="id_remember"
|
||||||
|
class="form-check-input">
|
||||||
|
<label class="form-check-label mb-0 fs-9" for="id_remember">{{ _("Remember Me") }}</label>
|
||||||
</div>
|
</div>
|
||||||
<button type="submit" class="btn btn-phoenix-primary btn-sm w-100">{% trans "Sign In" %}</button>
|
<button type="submit" class="btn btn-phoenix-primary btn-sm w-100">{% trans "Sign In" %}</button>
|
||||||
<div class="text-start mt-1">
|
<div class="text-start mt-1">
|
||||||
<a class="fs-9" href="{% url 'account_reset_password' %}">{{ _("Forgot Password?")}}</a>
|
<a class="fs-9" href="{% url 'account_reset_password' %}">{{ _("Forgot Password?") }}</a>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
{% include 'partials/form_errors.html' %}
|
{% include 'partials/form_errors.html' %}
|
||||||
|
|
||||||
</form>
|
</form>
|
||||||
<div class="text-center my-3 fs-9">
|
<div class="text-center my-3 fs-9">
|
||||||
{% trans 'If you have not created an account yet, then please' %}
|
{% trans 'If you have not created an account yet, then please' %}
|
||||||
@ -58,33 +76,29 @@
|
|||||||
{% endif %}
|
{% endif %}
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
</section>
|
</section>
|
||||||
<section class="pt-lg-0 pt-xl-8">
|
<section class="pt-lg-0 pt-xl-8">
|
||||||
{% include 'footer.html' %}
|
{% include 'footer.html' %}
|
||||||
</section>
|
</section>
|
||||||
|
|
||||||
{% if LOGIN_BY_CODE_ENABLED or PASSKEY_LOGIN_ENABLED %}
|
{% if LOGIN_BY_CODE_ENABLED or PASSKEY_LOGIN_ENABLED %}
|
||||||
<hr>
|
<hr>
|
||||||
{% element button_group vertical=True %}
|
{% element button_group vertical=True %}
|
||||||
{% if PASSKEY_LOGIN_ENABLED %}
|
{% if PASSKEY_LOGIN_ENABLED %}
|
||||||
{% element button type="submit" form="mfa_login" id="passkey_login" tags="prominent,login,outline,primary" %}
|
{% element button type="submit" form="mfa_login" id="passkey_login" tags="prominent,login,outline,primary" %}
|
||||||
{% trans "Sign in with a passkey" %}
|
{% trans "Sign in with a passkey" %}
|
||||||
{% endelement %}
|
|
||||||
{% endif %}
|
|
||||||
{% if LOGIN_BY_CODE_ENABLED %}
|
|
||||||
{% element button href=request_login_code_url tags="prominent,login,outline,primary" %}
|
|
||||||
{% trans "Mail me a sign-in code" %}
|
|
||||||
{% endelement %}
|
|
||||||
{% endif %}
|
|
||||||
{% endelement %}
|
{% endelement %}
|
||||||
{% endif %}
|
{% endif %}
|
||||||
{% if SOCIALACCOUNT_ENABLED %}
|
{% if LOGIN_BY_CODE_ENABLED %}
|
||||||
{% include "socialaccount/snippets/login.html" with page_layout="entrance" %}
|
{% element button href=request_login_code_url tags="prominent,login,outline,primary" %}
|
||||||
{% endif %}
|
{% trans "Mail me a sign-in code" %}
|
||||||
|
{% endelement %}
|
||||||
|
{% endif %}
|
||||||
|
{% endelement %}
|
||||||
|
{% endif %}
|
||||||
|
{% if SOCIALACCOUNT_ENABLED %}
|
||||||
|
{% include "socialaccount/snippets/login.html" with page_layout="entrance" %}
|
||||||
|
{% endif %}
|
||||||
{% endblock content %}
|
{% endblock content %}
|
||||||
|
|
||||||
{% block extra_body %}
|
{% block extra_body %}
|
||||||
{{ block.super }}
|
{{ block.super }}
|
||||||
{% if PASSKEY_LOGIN_ENABLED %}
|
{% if PASSKEY_LOGIN_ENABLED %}
|
||||||
|
|||||||
@ -1,30 +1,30 @@
|
|||||||
{% extends "base.html" %}
|
{% extends "base.html" %}
|
||||||
{% load i18n %}
|
{% load i18n %}
|
||||||
{% block title %}{{ _("Sign Out") }}{% endblock title %}
|
{% block title %}
|
||||||
|
{{ _("Sign Out") }}
|
||||||
|
{% endblock title %}
|
||||||
{% block content %}
|
{% block content %}
|
||||||
<div class="row">
|
<div class="row">
|
||||||
<div class="row flex-center min-vh-50">
|
<div class="row flex-center min-vh-50">
|
||||||
<div class="col-sm-10 col-md-8 col-lg-5 col-xl-4 col-xxl-3">
|
<div class="col-sm-10 col-md-8 col-lg-5 col-xl-4 col-xxl-3">
|
||||||
<div class="text-center mb-6 mx-auto">
|
<div class="text-center mb-6 mx-auto">
|
||||||
<div class="mb-6">
|
<div class="mb-6">
|
||||||
<h1 class="fw-bold">{{ _("Sign Out") }}</h1>
|
<h1 class="fw-bold">{{ _("Sign Out") }}</h1>
|
||||||
<p class="text-body-tertiary">{{ _("Are you sure you want to sign out?") }}</p>
|
<p class="text-body-tertiary">{{ _("Are you sure you want to sign out?") }}</p>
|
||||||
</div>
|
</div>
|
||||||
<div class="d-grid">
|
<div class="d-grid">
|
||||||
<form method="post" action="{% url 'account_logout' %}">
|
<form method="post" action="{% url 'account_logout' %}">
|
||||||
{% csrf_token %}
|
{% csrf_token %}
|
||||||
{{ redirect_field }}
|
{{ redirect_field }}
|
||||||
<div class="d-grid gap-2 mt-3">
|
<div class="d-grid gap-2 mt-3">
|
||||||
<button type="submit" class="btn btn-phoenix-danger">
|
<button type="submit" class="btn btn-phoenix-danger">
|
||||||
<span data-feather="log-out"></span> {{ _("Sign Out") }}
|
<span data-feather="log-out"></span> {{ _("Sign Out") }}
|
||||||
</button>
|
</button>
|
||||||
</div>
|
</div>
|
||||||
</form>
|
</form>
|
||||||
</div>
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
|
||||||
</div>
|
</div>
|
||||||
</div>
|
{% endblock content %}
|
||||||
|
|
||||||
{% endblock content %}
|
|
||||||
|
|||||||
@ -1,14 +1,20 @@
|
|||||||
{% extends 'base.html' %}
|
{% extends 'base.html' %}
|
||||||
{% load i18n static %}
|
{% load i18n static %}
|
||||||
|
|
||||||
{% block content %}
|
{% block content %}
|
||||||
<div class="row">
|
<div class="row">
|
||||||
<div class="row flex-center min-vh-50 py-5">
|
<div class="row flex-center min-vh-50 py-5">
|
||||||
<div class="col-sm-10 col-md-8 col-lg-5 col-xxl-4">
|
<div class="col-sm-10 col-md-8 col-lg-5 col-xxl-4">
|
||||||
<a class="d-flex flex-center text-decoration-none mb-4" href="{% url 'home' %}">
|
<a class="d-flex flex-center text-decoration-none mb-4"
|
||||||
|
href="{% url 'home' %}">
|
||||||
<div class="d-flex align-items-center fw-bolder fs-3 d-inline-block">
|
<div class="d-flex align-items-center fw-bolder fs-3 d-inline-block">
|
||||||
<img class="d-dark-none" src="{% static 'images/logos/logo-d.png' %}" alt="{% trans 'home' %}" width="58" />
|
<img class="d-dark-none"
|
||||||
<img class="d-light-none" src="{% static 'images/logos/logo.png' %}" alt="{% trans 'home' %}" width="58" />
|
src="{% static 'images/logos/logo-d.png' %}"
|
||||||
|
alt="{% trans 'home' %}"
|
||||||
|
width="58" />
|
||||||
|
<img class="d-light-none"
|
||||||
|
src="{% static 'images/logos/logo.png' %}"
|
||||||
|
alt="{% trans 'home' %}"
|
||||||
|
width="58" />
|
||||||
</div>
|
</div>
|
||||||
</a>
|
</a>
|
||||||
<div class="px-xxl-5">
|
<div class="px-xxl-5">
|
||||||
@ -17,17 +23,16 @@
|
|||||||
<p class="text-body-tertiary mb-0">
|
<p class="text-body-tertiary mb-0">
|
||||||
{{ _("An email containing a 6-digit verification code has been sent to your email.") }}
|
{{ _("An email containing a 6-digit verification code has been sent to your email.") }}
|
||||||
</p>
|
</p>
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
<form class="verification-form" method="POST">
|
<form class="verification-form" method="POST">
|
||||||
{% csrf_token %}
|
{% csrf_token %}
|
||||||
<div class="d-flex align-items-center gap-2 mb-3">
|
<div class="d-flex align-items-center gap-2 mb-3">
|
||||||
<input class="form-control px-2 text-center" type="number" name="otp_code" required maxlength="6" />
|
<input class="form-control px-2 text-center"
|
||||||
|
type="number"
|
||||||
|
name="otp_code"
|
||||||
|
required
|
||||||
|
maxlength="6" />
|
||||||
</div>
|
</div>
|
||||||
<button class="btn btn-phoenix-primary w-100 mb-5" type="submit">
|
<button class="btn btn-phoenix-primary w-100 mb-5" type="submit">{{ _("Verify") }}</button>
|
||||||
{{ _("Verify") }}
|
|
||||||
</button>
|
|
||||||
<a class="fs-9" href="">{{ _("Didn’t receive the code") }}</a>
|
<a class="fs-9" href="">{{ _("Didn’t receive the code") }}</a>
|
||||||
</form>
|
</form>
|
||||||
</div>
|
</div>
|
||||||
@ -35,4 +40,4 @@
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
{% endblock %}
|
{% endblock %}
|
||||||
|
|||||||
@ -1,6 +1,6 @@
|
|||||||
{% extends "base.html" %}
|
{% extends "base.html" %}
|
||||||
{% load crispy_forms_filters %}
|
{% load crispy_forms_filters %}
|
||||||
{% load allauth i18n static%}
|
{% load allauth i18n static %}
|
||||||
{% block head_title %}
|
{% block head_title %}
|
||||||
{% trans "Change Password" %}
|
{% trans "Change Password" %}
|
||||||
{% endblock head_title %}
|
{% endblock head_title %}
|
||||||
@ -8,24 +8,34 @@
|
|||||||
<div class="row ">
|
<div class="row ">
|
||||||
<div class="row flex-center min-vh-50">
|
<div class="row flex-center min-vh-50">
|
||||||
<div class="col-sm-10 col-md-8 col-lg-5 col-xl-5 col-xxl-3">
|
<div class="col-sm-10 col-md-8 col-lg-5 col-xl-5 col-xxl-3">
|
||||||
<a class="d-flex flex-center text-decoration-none mb-4" href="{% url 'home' %}">
|
<a class="d-flex flex-center text-decoration-none mb-4"
|
||||||
|
href="{% url 'home' %}">
|
||||||
<div class="d-flex align-items-center fw-bolder fs-3 d-inline-block">
|
<div class="d-flex align-items-center fw-bolder fs-3 d-inline-block">
|
||||||
<div class="d-flex align-items-center fw-bolder fs-3 d-inline-block">
|
<div class="d-flex align-items-center fw-bolder fs-3 d-inline-block">
|
||||||
<img class="d-dark-none" src="{% static 'images/logos/logo-d.png' %}" alt="{% trans 'home' %}" width="58" />
|
<img class="d-dark-none"
|
||||||
<img class="d-light-none" src="{% static 'images/logos/logo.png' %}" alt="{% trans 'home' %}" width="58" />
|
src="{% static 'images/logos/logo-d.png' %}"
|
||||||
|
alt="{% trans 'home' %}"
|
||||||
|
width="58" />
|
||||||
|
<img class="d-light-none"
|
||||||
|
src="{% static 'images/logos/logo.png' %}"
|
||||||
|
alt="{% trans 'home' %}"
|
||||||
|
width="58" />
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</a>
|
</a>
|
||||||
<div class="text-center">
|
<div class="text-center">
|
||||||
<h3 class="mb-4">{% trans "Change Password" %}</h3>
|
<h3 class="mb-4">{% trans "Change Password" %}</h3>
|
||||||
</div>
|
</div>
|
||||||
<form method="post" action="{% url 'account_change_password' %}" class="form needs-validation" novalidate>
|
<form method="post"
|
||||||
|
action="{% url 'account_change_password' %}"
|
||||||
|
class="form needs-validation"
|
||||||
|
novalidate>
|
||||||
{% csrf_token %}
|
{% csrf_token %}
|
||||||
{{ redirect_field }}
|
{{ redirect_field }}
|
||||||
{{ form|crispy }}
|
{{ form|crispy }}
|
||||||
<button type="submit" class="btn btn-phoenix-primary btn-sm w-100">{% trans "Change Password" %}</button>
|
<button type="submit" class="btn btn-phoenix-primary btn-sm w-100">{% trans "Change Password" %}</button>
|
||||||
<div class="text-start mt-1">
|
<div class="text-start mt-1">
|
||||||
<a class="fs-9" href="{% url 'account_reset_password' %}">{{ _("Forgot Password?")}}</a>
|
<a class="fs-9" href="{% url 'account_reset_password' %}">{{ _("Forgot Password?") }}</a>
|
||||||
</div>
|
</div>
|
||||||
</form>
|
</form>
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@ -8,26 +8,34 @@
|
|||||||
<div class="row ">
|
<div class="row ">
|
||||||
<div class="row flex-center min-vh-50">
|
<div class="row flex-center min-vh-50">
|
||||||
<div class="col-sm-10 col-md-8 col-lg-5 col-xl-5 col-xxl-3">
|
<div class="col-sm-10 col-md-8 col-lg-5 col-xl-5 col-xxl-3">
|
||||||
<a class="d-flex flex-center text-decoration-none mb-4" href="{% url 'home' %}">
|
<a class="d-flex flex-center text-decoration-none mb-4"
|
||||||
|
href="{% url 'home' %}">
|
||||||
<div class="d-flex align-items-center fw-bolder fs-3 d-inline-block">
|
<div class="d-flex align-items-center fw-bolder fs-3 d-inline-block">
|
||||||
<div class="d-flex align-items-center fw-bolder fs-3 d-inline-block">
|
<div class="d-flex align-items-center fw-bolder fs-3 d-inline-block">
|
||||||
<img class="d-dark-none" src="{% static 'images/logos/logo-d.png' %}" alt="{% trans 'home' %}" width="58" />
|
<img class="d-dark-none"
|
||||||
<img class="d-light-none" src="{% static 'images/logos/logo.png' %}" alt="{% trans 'home' %}" width="58" />
|
src="{% static 'images/logos/logo-d.png' %}"
|
||||||
|
alt="{% trans 'home' %}"
|
||||||
|
width="58" />
|
||||||
|
<img class="d-light-none"
|
||||||
|
src="{% static 'images/logos/logo.png' %}"
|
||||||
|
alt="{% trans 'home' %}"
|
||||||
|
width="58" />
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</a>
|
</a>
|
||||||
<div class="text-center">
|
<div class="text-center">
|
||||||
<h3 class="mb-4">{% trans "Password Reset" %}</h3>
|
<h3 class="mb-4">{% trans "Password Reset" %}</h3>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
{% if user.is_authenticated %}
|
{% if user.is_authenticated %}
|
||||||
{% include "account/snippets/already_logged_in.html" %}
|
{% include "account/snippets/already_logged_in.html" %}
|
||||||
{% endif %}
|
{% endif %}
|
||||||
<p>
|
<p>
|
||||||
{% trans "Forgotten your password? Enter your email address below, and we'll send you an email allowing you to reset it." %}
|
{% trans "Forgotten your password? Enter your email address below, and we'll send you an email allowing you to reset it." %}
|
||||||
</p>
|
</p>
|
||||||
|
<form method="post"
|
||||||
<form method="post" action="{% url 'account_reset_password' %}" class="form needs-validation" novalidate>
|
action="{% url 'account_reset_password' %}"
|
||||||
|
class="form needs-validation"
|
||||||
|
novalidate>
|
||||||
{% csrf_token %}
|
{% csrf_token %}
|
||||||
{{ form|crispy }}
|
{{ form|crispy }}
|
||||||
<button type="submit" class="btn btn-phoenix-primary btn-sm w-100">{% trans 'Reset My Password' %}</button>
|
<button type="submit" class="btn btn-phoenix-primary btn-sm w-100">{% trans 'Reset My Password' %}</button>
|
||||||
|
|||||||
@ -1,5 +1,5 @@
|
|||||||
{% extends "base.html" %}
|
{% extends "base.html" %}
|
||||||
{% load i18n static%}
|
{% load i18n static %}
|
||||||
{% load allauth %}
|
{% load allauth %}
|
||||||
{% load account %}
|
{% load account %}
|
||||||
{% block head_title %}
|
{% block head_title %}
|
||||||
@ -9,18 +9,24 @@
|
|||||||
<div class="row ">
|
<div class="row ">
|
||||||
<div class="row flex-center min-vh-50">
|
<div class="row flex-center min-vh-50">
|
||||||
<div class="col-sm-10 col-md-8 col-lg-5 col-xl-5 col-xxl-3">
|
<div class="col-sm-10 col-md-8 col-lg-5 col-xl-5 col-xxl-3">
|
||||||
<a class="d-flex flex-center text-decoration-none mb-4" href="{% url 'home' %}">
|
<a class="d-flex flex-center text-decoration-none mb-4"
|
||||||
|
href="{% url 'home' %}">
|
||||||
<div class="d-flex align-items-center fw-bolder fs-3 d-inline-block">
|
<div class="d-flex align-items-center fw-bolder fs-3 d-inline-block">
|
||||||
<div class="d-flex align-items-center fw-bolder fs-3 d-inline-block">
|
<div class="d-flex align-items-center fw-bolder fs-3 d-inline-block">
|
||||||
<img class="d-dark-none" src="{% static 'images/logos/logo-d.png' %}" alt="{% trans 'home' %}" width="58" />
|
<img class="d-dark-none"
|
||||||
<img class="d-light-none" src="{% static 'images/logos/logo.png' %}" alt="{% trans 'home' %}" width="58" />
|
src="{% static 'images/logos/logo-d.png' %}"
|
||||||
|
alt="{% trans 'home' %}"
|
||||||
|
width="58" />
|
||||||
|
<img class="d-light-none"
|
||||||
|
src="{% static 'images/logos/logo.png' %}"
|
||||||
|
alt="{% trans 'home' %}"
|
||||||
|
width="58" />
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</a>
|
</a>
|
||||||
<div class="text-center">
|
<div class="text-center">
|
||||||
<h3 class="mb-4">{% trans "Password Reset" %}</h3>
|
<h3 class="mb-4">{% trans "Password Reset" %}</h3>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
{% if user.is_authenticated %}
|
{% if user.is_authenticated %}
|
||||||
{% include "account/snippets/already_logged_in.html" %}
|
{% include "account/snippets/already_logged_in.html" %}
|
||||||
{% endif %}
|
{% endif %}
|
||||||
|
|||||||
@ -9,11 +9,18 @@
|
|||||||
<div class="row ">
|
<div class="row ">
|
||||||
<div class="row flex-center min-vh-50">
|
<div class="row flex-center min-vh-50">
|
||||||
<div class="col-sm-10 col-md-8 col-lg-5 col-xl-5 col-xxl-3">
|
<div class="col-sm-10 col-md-8 col-lg-5 col-xl-5 col-xxl-3">
|
||||||
<a class="d-flex flex-center text-decoration-none mb-4" href="{% url 'home' %}">
|
<a class="d-flex flex-center text-decoration-none mb-4"
|
||||||
|
href="{% url 'home' %}">
|
||||||
<div class="d-flex align-items-center fw-bolder fs-3 d-inline-block">
|
<div class="d-flex align-items-center fw-bolder fs-3 d-inline-block">
|
||||||
<div class="d-flex align-items-center fw-bolder fs-3 d-inline-block">
|
<div class="d-flex align-items-center fw-bolder fs-3 d-inline-block">
|
||||||
<img class="d-dark-none" src="{% static 'images/logos/logo-d.png' %}" alt="{% trans 'home' %}" width="58" />
|
<img class="d-dark-none"
|
||||||
<img class="d-light-none" src="{% static 'images/logos/logo.png' %}" alt="{% trans 'home' %}" width="58" />
|
src="{% static 'images/logos/logo-d.png' %}"
|
||||||
|
alt="{% trans 'home' %}"
|
||||||
|
width="58" />
|
||||||
|
<img class="d-light-none"
|
||||||
|
src="{% static 'images/logos/logo.png' %}"
|
||||||
|
alt="{% trans 'home' %}"
|
||||||
|
width="58" />
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</a>
|
</a>
|
||||||
@ -26,7 +33,6 @@
|
|||||||
{% endif %}
|
{% endif %}
|
||||||
</h3>
|
</h3>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
{% if token_fail %}
|
{% if token_fail %}
|
||||||
{% url 'account_reset_password' as passwd_reset_url %}
|
{% url 'account_reset_password' as passwd_reset_url %}
|
||||||
<p>
|
<p>
|
||||||
|
|||||||
@ -1,5 +1,5 @@
|
|||||||
{% extends "base.html" %}
|
{% extends "base.html" %}
|
||||||
{% load i18n static%}
|
{% load i18n static %}
|
||||||
{% load allauth %}
|
{% load allauth %}
|
||||||
{% block head_title %}
|
{% block head_title %}
|
||||||
{% trans "Change Password" %}
|
{% trans "Change Password" %}
|
||||||
@ -8,20 +8,27 @@
|
|||||||
<div class="row ">
|
<div class="row ">
|
||||||
<div class="row flex-center min-vh-50">
|
<div class="row flex-center min-vh-50">
|
||||||
<div class="col-sm-10 col-md-8 col-lg-5 col-xl-5 col-xxl-3">
|
<div class="col-sm-10 col-md-8 col-lg-5 col-xl-5 col-xxl-3">
|
||||||
<a class="d-flex flex-center text-decoration-none mb-4" href="{% url 'home' %}">
|
<a class="d-flex flex-center text-decoration-none mb-4"
|
||||||
|
href="{% url 'home' %}">
|
||||||
<div class="d-flex align-items-center fw-bolder fs-3 d-inline-block">
|
<div class="d-flex align-items-center fw-bolder fs-3 d-inline-block">
|
||||||
<div class="d-flex align-items-center fw-bolder fs-3 d-inline-block">
|
<div class="d-flex align-items-center fw-bolder fs-3 d-inline-block">
|
||||||
<img class="d-dark-none" src="{% static 'images/logos/logo-d.png' %}" alt="{% trans 'home' %}" width="58" />
|
<img class="d-dark-none"
|
||||||
<img class="d-light-none" src="{% static 'images/logos/logo.png' %}" alt="{% trans 'home' %}" width="58" />
|
src="{% static 'images/logos/logo-d.png' %}"
|
||||||
|
alt="{% trans 'home' %}"
|
||||||
|
width="58" />
|
||||||
|
<img class="d-light-none"
|
||||||
|
src="{% static 'images/logos/logo.png' %}"
|
||||||
|
alt="{% trans 'home' %}"
|
||||||
|
width="58" />
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</a>
|
</a>
|
||||||
<div class="text-center">
|
<div class="text-center">
|
||||||
<h3 class="mb-4">{% trans "Change Password" %}</h3>
|
<h3 class="mb-4">{% trans "Change Password" %}</h3>
|
||||||
<p class="fs-9 fw-bold text-success">{% trans 'Your password is now changed.' %} <span class="far fa-check-circle ms-1"></span></p>
|
<p class="fs-9 fw-bold text-success">
|
||||||
|
{% trans 'Your password is now changed.' %} <span class="far fa-check-circle ms-1"></span>
|
||||||
|
</p>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@ -6,20 +6,20 @@
|
|||||||
{% endblock head_title %}
|
{% endblock head_title %}
|
||||||
{% block content %}
|
{% block content %}
|
||||||
{% element h1 %}
|
{% element h1 %}
|
||||||
{% trans "Set Password" %}
|
{% trans "Set Password" %}
|
||||||
{% endelement %}
|
{% endelement %}
|
||||||
{% url 'account_set_password' as action_url %}
|
{% url 'account_set_password' as action_url %}
|
||||||
{% element form method="post" action=action_url %}
|
{% element form method="post" action=action_url %}
|
||||||
{% slot body %}
|
{% slot body %}
|
||||||
{% csrf_token %}
|
{% csrf_token %}
|
||||||
{{ redirect_field }}
|
{{ redirect_field }}
|
||||||
{% element fields form=form %}
|
{% element fields form=form %}
|
||||||
{% endelement %}
|
{% endelement %}
|
||||||
{% endslot %}
|
{% endslot %}
|
||||||
{% slot actions %}
|
{% slot actions %}
|
||||||
{% element button type="submit" name="action" %}
|
{% element button type="submit" name="action" %}
|
||||||
{% trans 'Set Password' %}
|
{% trans 'Set Password' %}
|
||||||
{% endelement %}
|
{% endelement %}
|
||||||
{% endslot %}
|
{% endslot %}
|
||||||
{% endelement %}
|
{% endelement %}
|
||||||
{% endblock content %}
|
{% endblock content %}
|
||||||
|
|||||||
@ -3,20 +3,20 @@
|
|||||||
{% load i18n %}
|
{% load i18n %}
|
||||||
{% block reauthenticate_content %}
|
{% block reauthenticate_content %}
|
||||||
{% element p %}
|
{% element p %}
|
||||||
{% blocktranslate %}Enter your password:{% endblocktranslate %}
|
{% blocktranslate %}Enter your password:{% endblocktranslate %}
|
||||||
{% endelement %}
|
{% endelement %}
|
||||||
{% url 'account_reauthenticate' as action_url %}
|
{% url 'account_reauthenticate' as action_url %}
|
||||||
{% element form form=form method="post" action=action_url %}
|
{% element form form=form method="post" action=action_url %}
|
||||||
{% slot body %}
|
{% slot body %}
|
||||||
{% csrf_token %}
|
{% csrf_token %}
|
||||||
{% element fields form=form unlabeled=True %}
|
{% element fields form=form unlabeled=True %}
|
||||||
{% endelement %}
|
{% endelement %}
|
||||||
{{ redirect_field }}
|
{{ redirect_field }}
|
||||||
{% endslot %}
|
{% endslot %}
|
||||||
{% slot actions %}
|
{% slot actions %}
|
||||||
{% element button type="submit" tags="primary,reauthenticate" %}
|
{% element button type="submit" tags="primary,reauthenticate" %}
|
||||||
{% trans "Confirm" %}
|
{% trans "Confirm" %}
|
||||||
{% endelement %}
|
{% endelement %}
|
||||||
{% endslot %}
|
{% endslot %}
|
||||||
{% endelement %}
|
{% endelement %}
|
||||||
{% endblock %}
|
{% endblock %}
|
||||||
|
|||||||
@ -6,27 +6,27 @@
|
|||||||
{% endblock head_title %}
|
{% endblock head_title %}
|
||||||
{% block content %}
|
{% block content %}
|
||||||
{% element h1 %}
|
{% element h1 %}
|
||||||
{% translate "Mail me a sign-in code" %}
|
{% translate "Mail me a sign-in code" %}
|
||||||
{% endelement %}
|
{% endelement %}
|
||||||
{% element p %}
|
{% element p %}
|
||||||
{% blocktranslate %}You will receive an email containing a special code for a password-free sign-in.{% endblocktranslate %}
|
{% blocktranslate %}You will receive an email containing a special code for a password-free sign-in.{% endblocktranslate %}
|
||||||
{% endelement %}
|
{% endelement %}
|
||||||
{% url 'account_request_login_code' as login_url %}
|
{% url 'account_request_login_code' as login_url %}
|
||||||
{% element form form=form method="post" action=login_url tags="entrance,login" %}
|
{% element form form=form method="post" action=login_url tags="entrance,login" %}
|
||||||
{% slot body %}
|
{% slot body %}
|
||||||
{% csrf_token %}
|
{% csrf_token %}
|
||||||
{% element fields form=form unlabeled=True %}
|
{% element fields form=form unlabeled=True %}
|
||||||
{% endelement %}
|
{% endelement %}
|
||||||
{{ redirect_field }}
|
{{ redirect_field }}
|
||||||
{% endslot %}
|
{% endslot %}
|
||||||
{% slot actions %}
|
{% slot actions %}
|
||||||
{% element button type="submit" tags="prominent,login" %}
|
{% element button type="submit" tags="prominent,login" %}
|
||||||
{% translate "Request Code" %}
|
{% translate "Request Code" %}
|
||||||
{% endelement %}
|
{% endelement %}
|
||||||
{% endslot %}
|
{% endslot %}
|
||||||
{% endelement %}
|
{% endelement %}
|
||||||
{% url 'account_login' as login_url %}
|
{% url 'account_login' as login_url %}
|
||||||
{% element button href=login_url tags="link" %}
|
{% element button href=login_url tags="link" %}
|
||||||
{% translate "Other sign-in options" %}
|
{% translate "Other sign-in options" %}
|
||||||
{% endelement %}
|
{% endelement %}
|
||||||
{% endblock content %}
|
{% endblock content %}
|
||||||
|
|||||||
@ -1,96 +1,184 @@
|
|||||||
{% extends "welcome_base.html" %}
|
{% extends "welcome_base.html" %}
|
||||||
{% load crispy_forms_filters %}
|
{% load crispy_forms_filters %}
|
||||||
{% load i18n static %}
|
{% load i18n static %}
|
||||||
|
|
||||||
|
|
||||||
{% block content %}
|
{% block content %}
|
||||||
<section class="main my-2">
|
<section class="main my-2">
|
||||||
<div class="container-fluid">
|
<div class="container-fluid">
|
||||||
<div class="row form-container" id="form-container">
|
<div class="row form-container" id="form-container">
|
||||||
|
<div class="col-12 ">
|
||||||
<div class="col-12 "><a class="d-flex flex-center text-decoration-none mb-4" href="{% url 'home' %}">
|
<a class="d-flex flex-center text-decoration-none mb-4"
|
||||||
<div class="d-flex align-items-center fw-bolder fs-3 d-inline-block">
|
href="{% url 'home' %}">
|
||||||
<img class="d-dark-none" src="{% static 'images/logos/logo-d.png' %}" alt="{% trans 'home' %}" width="58" />
|
<div class="d-flex align-items-center fw-bolder fs-3 d-inline-block">
|
||||||
<img class="d-light-none" src="{% static 'images/logos/logo.png' %}" alt="{% trans 'home' %}" width="58" />
|
<img class="d-dark-none"
|
||||||
</div>
|
src="{% static 'images/logos/logo-d.png' %}"
|
||||||
</a>
|
alt="{% trans 'home' %}"
|
||||||
<div class="text-center">
|
width="58" />
|
||||||
<h3 class="text-body-highlight">{% trans 'Sign Up' %}</h3>
|
<img class="d-light-none"
|
||||||
<p class="text-body-tertiary fs-9">{% trans 'Create your account today' %}</p>
|
src="{% static 'images/logos/logo.png' %}"
|
||||||
</div>
|
alt="{% trans 'home' %}"
|
||||||
|
width="58" />
|
||||||
<div class="card theme-wizard" data-theme-wizard="data-theme-wizard">
|
</div>
|
||||||
<div class="card-header pt-3 pb-2 ">
|
</a>
|
||||||
<ul class="nav justify-content-between nav-wizard nav-wizard-success" role="tablist">
|
<div class="text-center">
|
||||||
<li class="nav-item" role="presentation"><a class="nav-link active fw-semibold" href="#bootstrap-wizard-validation-tab1" data-bs-toggle="tab" data-wizard-step="1" aria-selected="true" role="tab">
|
<h3 class="text-body-highlight">{% trans 'Sign Up' %}</h3>
|
||||||
<div class="text-center d-inline-block"><span class="nav-item-circle-parent"><span class="nav-item-circle"><span class="fa fa-lock"></span></span></span><span class="d-none d-md-block mt-1 fs-9">{% trans 'Access' %}</span></div>
|
<p class="text-body-tertiary fs-9">{% trans 'Create your account today' %}</p>
|
||||||
</a></li>
|
|
||||||
<li class="nav-item" role="presentation"><a class="nav-link fw-semibold" href="#bootstrap-wizard-validation-tab2" data-bs-toggle="tab" data-wizard-step="2" aria-selected="false" tabindex="-1" role="tab">
|
|
||||||
<div class="text-center d-inline-block"><span class="nav-item-circle-parent"><span class="nav-item-circle"><span class="fa fa-user"></span></span></span><span class="d-none d-md-block mt-1 fs-9">{% trans 'Account' %}</span></div>
|
|
||||||
</a></li>
|
|
||||||
<li class="nav-item" role="presentation"><a class="nav-link fw-semibold" href="#bootstrap-wizard-validation-tab3" data-bs-toggle="tab" data-wizard-step="3" aria-selected="false" tabindex="-1" role="tab">
|
|
||||||
<div class="text-center d-inline-block"><span class="nav-item-circle-parent"><span class="nav-item-circle"><svg class="fa fa-file-lines"></svg></span></span><span class="d-none d-md-block mt-1 fs-9">{% trans 'Extra' %}</span></div>
|
|
||||||
</a></li>
|
|
||||||
<li class="nav-item" role="presentation"><a class="nav-link fw-semibold" href="#bootstrap-wizard-validation-tab4" data-bs-toggle="tab" data-wizard-step="4" aria-selected="false" tabindex="-1" role="tab">
|
|
||||||
<div class="text-center d-inline-block"><span class="nav-item-circle-parent"><span class="nav-item-circle"><span class="fa fa-check"></span></span></span><span class="d-none d-md-block mt-1 fs-9">{% trans 'Done' %}</span></div>
|
|
||||||
</a></li>
|
|
||||||
</ul>
|
|
||||||
</div>
|
|
||||||
<div class="card-body pt-4 pb-0">
|
|
||||||
<div class="tab-content">
|
|
||||||
<div class="tab-pane active" role="tabpanel" aria-labelledby="bootstrap-wizard-validation-tab1" id="bootstrap-wizard-validation-tab1">
|
|
||||||
<form class="needs-validation" id="wizardValidationForm1" novalidate="novalidate" data-wizard-form="1">
|
|
||||||
{{form1|crispy}}
|
|
||||||
<a class="fs-10 text-decoration-none" href="{% url 'terms_and_privacy' %}" target="_blank">{{ _("Read Terms of Service and Privacy Policy")}}</a>
|
|
||||||
</form>
|
|
||||||
</div>
|
|
||||||
<div class="tab-pane" role="tabpanel" aria-labelledby="bootstrap-wizard-validation-tab2" id="bootstrap-wizard-validation-tab2">
|
|
||||||
<form class="needs-validation" id="wizardValidationForm2" novalidate="novalidate" data-wizard-form="2">
|
|
||||||
{{form2|crispy}}
|
|
||||||
</form>
|
|
||||||
</div>
|
|
||||||
<div class="tab-pane" role="tabpanel" aria-labelledby="bootstrap-wizard-validation-tab3" id="bootstrap-wizard-validation-tab3">
|
|
||||||
<form class="needs-validation" id="wizardValidationForm3" novalidate="novalidate" data-wizard-form="3">
|
|
||||||
{{form3|crispy}}
|
|
||||||
</form>
|
|
||||||
</div>
|
|
||||||
<div class="tab-pane" role="tabpanel" aria-labelledby="bootstrap-wizard-validation-tab4" id="bootstrap-wizard-validation-tab4">
|
|
||||||
<div class="row flex-center pb-8 pt-4 gx-3 gy-4">
|
|
||||||
<div class="col-12 col-sm-auto">
|
|
||||||
<div class="text-center text-sm-start"><img class="d-dark-none" src="{% static 'images/spot-illustrations/38.webp' %}" alt="" width="220"><img class="d-light-none" src="{% static 'images/spot-illustrations/dark_38.webp' %}" alt="" width="220"></div>
|
|
||||||
</div>
|
</div>
|
||||||
<div class="col-12 col-sm-auto">
|
<div class="card theme-wizard" data-theme-wizard="data-theme-wizard">
|
||||||
<div class="text-center text-sm-start">
|
<div class="card-header pt-3 pb-2 ">
|
||||||
<h5 class="mb-3">{% trans 'You are all set!' %}</h5>
|
<ul class="nav justify-content-between nav-wizard nav-wizard-success"
|
||||||
<p class="text-body-emphasis fs-9">{% trans 'Now you can access your account' %}<br>{% trans 'anytime' %} {% trans 'anywhere' %}</p><button class="btn btn-primary px-6" id='submit_btn'>{% trans 'Submit' %}</button>
|
role="tablist">
|
||||||
</div>
|
<li class="nav-item" role="presentation">
|
||||||
|
<a class="nav-link active fw-semibold"
|
||||||
|
href="#bootstrap-wizard-validation-tab1"
|
||||||
|
data-bs-toggle="tab"
|
||||||
|
data-wizard-step="1"
|
||||||
|
aria-selected="true"
|
||||||
|
role="tab">
|
||||||
|
<div class="text-center d-inline-block">
|
||||||
|
<span class="nav-item-circle-parent"><span class="nav-item-circle"><span class="fa fa-lock"></span></span></span><span class="d-none d-md-block mt-1 fs-9">{% trans 'Access' %}</span>
|
||||||
|
</div>
|
||||||
|
</a>
|
||||||
|
</li>
|
||||||
|
<li class="nav-item" role="presentation">
|
||||||
|
<a class="nav-link fw-semibold"
|
||||||
|
href="#bootstrap-wizard-validation-tab2"
|
||||||
|
data-bs-toggle="tab"
|
||||||
|
data-wizard-step="2"
|
||||||
|
aria-selected="false"
|
||||||
|
tabindex="-1"
|
||||||
|
role="tab">
|
||||||
|
<div class="text-center d-inline-block">
|
||||||
|
<span class="nav-item-circle-parent"><span class="nav-item-circle"><span class="fa fa-user"></span></span></span><span class="d-none d-md-block mt-1 fs-9">{% trans 'Account' %}</span>
|
||||||
|
</div>
|
||||||
|
</a>
|
||||||
|
</li>
|
||||||
|
<li class="nav-item" role="presentation">
|
||||||
|
<a class="nav-link fw-semibold"
|
||||||
|
href="#bootstrap-wizard-validation-tab3"
|
||||||
|
data-bs-toggle="tab"
|
||||||
|
data-wizard-step="3"
|
||||||
|
aria-selected="false"
|
||||||
|
tabindex="-1"
|
||||||
|
role="tab">
|
||||||
|
<div class="text-center d-inline-block">
|
||||||
|
<span class="nav-item-circle-parent"><span class="nav-item-circle">
|
||||||
|
<svg class="fa fa-file-lines">
|
||||||
|
</svg>
|
||||||
|
</span></span><span class="d-none d-md-block mt-1 fs-9">{% trans 'Extra' %}</span>
|
||||||
|
</div>
|
||||||
|
</a>
|
||||||
|
</li>
|
||||||
|
<li class="nav-item" role="presentation">
|
||||||
|
<a class="nav-link fw-semibold"
|
||||||
|
href="#bootstrap-wizard-validation-tab4"
|
||||||
|
data-bs-toggle="tab"
|
||||||
|
data-wizard-step="4"
|
||||||
|
aria-selected="false"
|
||||||
|
tabindex="-1"
|
||||||
|
role="tab">
|
||||||
|
<div class="text-center d-inline-block">
|
||||||
|
<span class="nav-item-circle-parent"><span class="nav-item-circle"><span class="fa fa-check"></span></span></span><span class="d-none d-md-block mt-1 fs-9">{% trans 'Done' %}</span>
|
||||||
|
</div>
|
||||||
|
</a>
|
||||||
|
</li>
|
||||||
|
</ul>
|
||||||
|
</div>
|
||||||
|
<div class="card-body pt-4 pb-0">
|
||||||
|
<div class="tab-content">
|
||||||
|
<div class="tab-pane active"
|
||||||
|
role="tabpanel"
|
||||||
|
aria-labelledby="bootstrap-wizard-validation-tab1"
|
||||||
|
id="bootstrap-wizard-validation-tab1">
|
||||||
|
<form class="needs-validation"
|
||||||
|
id="wizardValidationForm1"
|
||||||
|
novalidate="novalidate"
|
||||||
|
data-wizard-form="1">
|
||||||
|
{{ form1|crispy }}
|
||||||
|
<a class="fs-10 text-decoration-none"
|
||||||
|
href="{% url 'terms_and_privacy' %}"
|
||||||
|
target="_blank">{{ _("Read Terms of Service and Privacy Policy") }}</a>
|
||||||
|
</form>
|
||||||
|
</div>
|
||||||
|
<div class="tab-pane"
|
||||||
|
role="tabpanel"
|
||||||
|
aria-labelledby="bootstrap-wizard-validation-tab2"
|
||||||
|
id="bootstrap-wizard-validation-tab2">
|
||||||
|
<form class="needs-validation"
|
||||||
|
id="wizardValidationForm2"
|
||||||
|
novalidate="novalidate"
|
||||||
|
data-wizard-form="2">
|
||||||
|
{{ form2|crispy }}
|
||||||
|
</form>
|
||||||
|
</div>
|
||||||
|
<div class="tab-pane"
|
||||||
|
role="tabpanel"
|
||||||
|
aria-labelledby="bootstrap-wizard-validation-tab3"
|
||||||
|
id="bootstrap-wizard-validation-tab3">
|
||||||
|
<form class="needs-validation"
|
||||||
|
id="wizardValidationForm3"
|
||||||
|
novalidate="novalidate"
|
||||||
|
data-wizard-form="3">
|
||||||
|
{{ form3|crispy }}
|
||||||
|
</form>
|
||||||
|
</div>
|
||||||
|
<div class="tab-pane"
|
||||||
|
role="tabpanel"
|
||||||
|
aria-labelledby="bootstrap-wizard-validation-tab4"
|
||||||
|
id="bootstrap-wizard-validation-tab4">
|
||||||
|
<div class="row flex-center pb-8 pt-4 gx-3 gy-4">
|
||||||
|
<div class="col-12 col-sm-auto">
|
||||||
|
<div class="text-center text-sm-start">
|
||||||
|
<img class="d-dark-none"
|
||||||
|
src="{% static 'images/spot-illustrations/38.webp' %}"
|
||||||
|
alt=""
|
||||||
|
width="220">
|
||||||
|
<img class="d-light-none"
|
||||||
|
src="{% static 'images/spot-illustrations/dark_38.webp' %}"
|
||||||
|
alt=""
|
||||||
|
width="220">
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="col-12 col-sm-auto">
|
||||||
|
<div class="text-center text-sm-start">
|
||||||
|
<h5 class="mb-3">{% trans 'You are all set!' %}</h5>
|
||||||
|
<p class="text-body-emphasis fs-9">
|
||||||
|
{% trans 'Now you can access your account' %}
|
||||||
|
<br>
|
||||||
|
{% trans 'anytime' %} {% trans 'anywhere' %}
|
||||||
|
</p>
|
||||||
|
<button class="btn btn-primary px-6" id='submit_btn'>{% trans 'Submit' %}</button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="card-footer border-top-0"
|
||||||
|
data-wizard-footer="data-wizard-footer">
|
||||||
|
<div class="d-flex pager wizard list-inline mb-0">
|
||||||
|
<button class="d-none btn btn-link ps-0"
|
||||||
|
type="button"
|
||||||
|
data-wizard-prev-btn="data-wizard-prev-btn">{% trans 'Previous' %}</button>
|
||||||
|
<div class="flex-1 text-end">
|
||||||
|
<button class="btn btn-phoenix-primary px-6 px-sm-6 next"
|
||||||
|
type="submit"
|
||||||
|
data-wizard-next-btn="data-wizard-next-btn">{% trans 'Next' %}</button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
|
||||||
</div>
|
</div>
|
||||||
</div>
|
|
||||||
</div>
|
</div>
|
||||||
<div class="card-footer border-top-0" data-wizard-footer="data-wizard-footer">
|
|
||||||
<div class="d-flex pager wizard list-inline mb-0">
|
|
||||||
<button class="d-none btn btn-link ps-0" type="button" data-wizard-prev-btn="data-wizard-prev-btn">{% trans 'Previous' %}</button>
|
|
||||||
<div class="flex-1 text-end">
|
|
||||||
<button class="btn btn-phoenix-primary px-6 px-sm-6 next" type="submit" data-wizard-next-btn="data-wizard-next-btn">{% trans 'Next' %}</button>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</section>
|
||||||
</div>
|
<section class="pt-lg-0 pt-xl-8">
|
||||||
</section>
|
{% include 'footer.html' %}
|
||||||
<section class="pt-lg-0 pt-xl-8">
|
</section>
|
||||||
{% include 'footer.html' %}
|
<script src="{% static 'js/phoenix.js' %}"></script>
|
||||||
</section>
|
|
||||||
<script src="{% static 'js/phoenix.js' %}"></script>
|
|
||||||
{% endblock content %}
|
{% endblock content %}
|
||||||
|
|
||||||
{% block customJS %}
|
{% block customJS %}
|
||||||
<script src="https://unpkg.com/just-validate@latest/dist/just-validate.production.min.js"></script>
|
<script src="https://unpkg.com/just-validate@latest/dist/just-validate.production.min.js"></script>
|
||||||
<script>
|
<script>
|
||||||
const validator = new JustValidate('#wizardValidationForm1', {
|
const validator = new JustValidate('#wizardValidationForm1', {
|
||||||
validateBeforeSubmitting: true,
|
validateBeforeSubmitting: true,
|
||||||
});
|
});
|
||||||
@ -282,6 +370,5 @@
|
|||||||
}
|
}
|
||||||
return cookieValue;
|
return cookieValue;
|
||||||
}
|
}
|
||||||
</script>
|
</script>
|
||||||
|
{% endblock customJS %}
|
||||||
{% endblock customJS %}
|
|
||||||
|
|||||||
@ -1,219 +1,325 @@
|
|||||||
{% extends "welcome_base.html" %}
|
{% extends "welcome_base.html" %}
|
||||||
{% load crispy_forms_filters %}
|
{% load crispy_forms_filters %}
|
||||||
{% load i18n static %}
|
{% load i18n static %}
|
||||||
|
|
||||||
|
|
||||||
{% block content %}
|
{% block content %}
|
||||||
|
<section class="main my-2">
|
||||||
<section class="main my-2">
|
<div class="container" style="max-width:60rem;">
|
||||||
<div class="container" style="max-width:60rem;">
|
<div class="row form-container" id="form-container">
|
||||||
<div class="row form-container" id="form-container">
|
<div class="col-12 ">
|
||||||
|
<a class="d-flex flex-center text-decoration-none mb-4"
|
||||||
<div class="col-12 "><a class="d-flex flex-center text-decoration-none mb-4" href="{% url 'home' %}">
|
href="{% url 'home' %}">
|
||||||
<div class="d-flex align-items-center fw-bolder fs-3 d-inline-block">
|
<div class="d-flex align-items-center fw-bolder fs-3 d-inline-block">
|
||||||
<img class="d-dark-none" src="{% static 'images/logos/logo-d.png' %}" alt="{% trans 'home' %}" width="58" />
|
<img class="d-dark-none"
|
||||||
<img class="d-light-none" src="{% static 'images/logos/logo.png' %}" alt="{% trans 'home' %}" width="58" />
|
src="{% static 'images/logos/logo-d.png' %}"
|
||||||
</div>
|
alt="{% trans 'home' %}"
|
||||||
</a>
|
width="58" />
|
||||||
<div class="text-center">
|
<img class="d-light-none"
|
||||||
<h3 class="text-body-highlight">{% trans 'Sign Up' %}</h3>
|
src="{% static 'images/logos/logo.png' %}"
|
||||||
<p class="text-body-tertiary fs-9">{% trans 'Create your account today' %}</p>
|
alt="{% trans 'home' %}"
|
||||||
</div>
|
width="58" />
|
||||||
|
</div>
|
||||||
<div
|
</a>
|
||||||
data-signals="{
|
<div class="text-center">
|
||||||
form1:{email:'',password:'',confirm_password:''},
|
<h3 class="text-body-highlight">{% trans 'Sign Up' %}</h3>
|
||||||
form2:{name:'',arabic_name:'',phone_number:''},
|
<p class="text-body-tertiary fs-9">{% trans 'Create your account today' %}</p>
|
||||||
form3:{crn:'',vrn:'',address:''},
|
</div>
|
||||||
form1_valid:true,
|
<div data-signals="{ form1:{email:'',password:'',confirm_password:''}, form2:{name:'',arabic_name:'',phone_number:''}, form3:{crn:'',vrn:'',address:''}, form1_valid:true, form2_valid:true, form3_valid:true, email_valid:true, password_valid:true, phone_number_valid:true }"
|
||||||
form2_valid:true,
|
class="card theme-wizard"
|
||||||
form3_valid:true,
|
data-theme-wizard="data-theme-wizard">
|
||||||
email_valid:true,
|
<div class="card-header pt-3 pb-2 ">
|
||||||
password_valid:true,
|
<ul class="nav justify-content-between nav-wizard nav-wizard-success"
|
||||||
phone_number_valid:true
|
role="tablist">
|
||||||
}"
|
<li class="nav-item" role="presentation">
|
||||||
class="card theme-wizard" data-theme-wizard="data-theme-wizard">
|
<a class="nav-link active fw-semibold"
|
||||||
<div class="card-header pt-3 pb-2 ">
|
href="#bootstrap-wizard-validation-tab1"
|
||||||
<ul class="nav justify-content-between nav-wizard nav-wizard-success" role="tablist">
|
data-bs-toggle="tab"
|
||||||
<li class="nav-item" role="presentation"><a class="nav-link active fw-semibold" href="#bootstrap-wizard-validation-tab1" data-bs-toggle="tab" data-wizard-step="1" aria-selected="true" role="tab">
|
data-wizard-step="1"
|
||||||
<div class="text-center d-inline-block"><span class="nav-item-circle-parent"><span class="nav-item-circle"><span class="fa fa-lock"></span></span></span><span class="d-none d-md-block mt-1 fs-9">{% trans 'Access' %}</span></div>
|
aria-selected="true"
|
||||||
</a></li>
|
role="tab">
|
||||||
<li class="nav-item" role="presentation"><a class="nav-link fw-semibold" href="#bootstrap-wizard-validation-tab2" data-bs-toggle="tab" data-wizard-step="2" aria-selected="false" tabindex="-1" role="tab">
|
<div class="text-center d-inline-block">
|
||||||
<div class="text-center d-inline-block"><span class="nav-item-circle-parent"><span class="nav-item-circle"><span class="fa fa-user"></span></span></span><span class="d-none d-md-block mt-1 fs-9">{% trans 'Account' %}</span></div>
|
<span class="nav-item-circle-parent"><span class="nav-item-circle"><span class="fa fa-lock"></span></span></span><span class="d-none d-md-block mt-1 fs-9">{% trans 'Access' %}</span>
|
||||||
</a></li>
|
</div>
|
||||||
<li class="nav-item" role="presentation"><a class="nav-link fw-semibold" href="#bootstrap-wizard-validation-tab3" data-bs-toggle="tab" data-wizard-step="3" aria-selected="false" tabindex="-1" role="tab">
|
</a>
|
||||||
<div class="text-center d-inline-block"><span class="nav-item-circle-parent"><span class="nav-item-circle"><svg class="fa fa-file-lines"></svg></span></span><span class="d-none d-md-block mt-1 fs-9">{% trans 'Extra' %}</span></div>
|
</li>
|
||||||
</a></li>
|
<li class="nav-item" role="presentation">
|
||||||
<li class="nav-item" role="presentation"><a class="nav-link fw-semibold" href="#bootstrap-wizard-validation-tab4" data-bs-toggle="tab" data-wizard-step="4" aria-selected="false" tabindex="-1" role="tab">
|
<a class="nav-link fw-semibold"
|
||||||
<div class="text-center d-inline-block"><span class="nav-item-circle-parent"><span class="nav-item-circle"><span class="fa fa-check"></span></span></span><span class="d-none d-md-block mt-1 fs-9">{% trans 'Done' %}</span></div>
|
href="#bootstrap-wizard-validation-tab2"
|
||||||
</a></li>
|
data-bs-toggle="tab"
|
||||||
</ul>
|
data-wizard-step="2"
|
||||||
|
aria-selected="false"
|
||||||
|
tabindex="-1"
|
||||||
|
role="tab">
|
||||||
|
<div class="text-center d-inline-block">
|
||||||
|
<span class="nav-item-circle-parent"><span class="nav-item-circle"><span class="fa fa-user"></span></span></span><span class="d-none d-md-block mt-1 fs-9">{% trans 'Account' %}</span>
|
||||||
|
</div>
|
||||||
|
</a>
|
||||||
|
</li>
|
||||||
|
<li class="nav-item" role="presentation">
|
||||||
|
<a class="nav-link fw-semibold"
|
||||||
|
href="#bootstrap-wizard-validation-tab3"
|
||||||
|
data-bs-toggle="tab"
|
||||||
|
data-wizard-step="3"
|
||||||
|
aria-selected="false"
|
||||||
|
tabindex="-1"
|
||||||
|
role="tab">
|
||||||
|
<div class="text-center d-inline-block">
|
||||||
|
<span class="nav-item-circle-parent"><span class="nav-item-circle">
|
||||||
|
<svg class="fa fa-file-lines">
|
||||||
|
</svg>
|
||||||
|
</span></span><span class="d-none d-md-block mt-1 fs-9">{% trans 'Extra' %}</span>
|
||||||
|
</div>
|
||||||
|
</a>
|
||||||
|
</li>
|
||||||
|
<li class="nav-item" role="presentation">
|
||||||
|
<a class="nav-link fw-semibold"
|
||||||
|
href="#bootstrap-wizard-validation-tab4"
|
||||||
|
data-bs-toggle="tab"
|
||||||
|
data-wizard-step="4"
|
||||||
|
aria-selected="false"
|
||||||
|
tabindex="-1"
|
||||||
|
role="tab">
|
||||||
|
<div class="text-center d-inline-block">
|
||||||
|
<span class="nav-item-circle-parent"><span class="nav-item-circle"><span class="fa fa-check"></span></span></span><span class="d-none d-md-block mt-1 fs-9">{% trans 'Done' %}</span>
|
||||||
|
</div>
|
||||||
|
</a>
|
||||||
|
</li>
|
||||||
|
</ul>
|
||||||
|
</div>
|
||||||
|
<div class="card-body pt-4 pb-0">
|
||||||
|
<div class="tab-content" data-signals-current_form="1">
|
||||||
|
<div class="tab-pane active"
|
||||||
|
role="tabpanel"
|
||||||
|
aria-labelledby="bootstrap-wizard-validation-tab1"
|
||||||
|
id="bootstrap-wizard-validation-tab1">
|
||||||
|
<form class="needs-validation"
|
||||||
|
id="wizardValidationForm1"
|
||||||
|
novalidate="novalidate"
|
||||||
|
data-wizard-form="1"
|
||||||
|
data-ref-f1>
|
||||||
|
<div class="mb-3">
|
||||||
|
<label for="email"
|
||||||
|
data-class="{'text-danger':!$email_valid}"
|
||||||
|
class="form-label">
|
||||||
|
{% trans "Email" %}
|
||||||
|
<span data-show="!$email_valid" class="text-danger">*</span>
|
||||||
|
</label>
|
||||||
|
<input data-on-input="$email_valid = validateEmail($form1.email)"
|
||||||
|
data-on-blur="$email_valid = validateEmail($form1.email)"
|
||||||
|
data-bind-form1.email
|
||||||
|
data-class="{'is-invalid': !$email_valid , 'is-valid': ($email_valid && $form1.email)}"
|
||||||
|
type="email"
|
||||||
|
class="form-control"
|
||||||
|
id="email"
|
||||||
|
name="email"
|
||||||
|
required>
|
||||||
|
<div class="invalid-feedback" data-show="!$email_valid">{% trans "Please enter a valid email address" %}</div>
|
||||||
|
</div>
|
||||||
|
<div class="mb-3">
|
||||||
|
<label for="password" class="form-label">{% trans "Password" %}</label>
|
||||||
|
<input data-bind-form1.password
|
||||||
|
type="password"
|
||||||
|
data-on-input="$password_valid = validatePassword($form1.password,$form1.confirm_password)"
|
||||||
|
data-on-blur="$password_valid = validatePassword($form1.password,$form1.confirm_password)"
|
||||||
|
class="form-control"
|
||||||
|
data-class="{'is-invalid':($form1.password.length && $form1.password.length < 8),'is-valid':$form1.password.length > 8 }"
|
||||||
|
id="password"
|
||||||
|
name="password"
|
||||||
|
required>
|
||||||
|
<div class="invalid-feedback" data-show="!$password_valid">
|
||||||
|
{% trans "Password does not match. or length is less than 8 characters." %}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="mb-3">
|
||||||
|
<label for="confirm_password" class="form-label">{% trans "Confirm Password" %}</label>
|
||||||
|
<span class="text-danger" data-show="!$password_valid">*</span>
|
||||||
|
<input data-bind-form1.confirm_password
|
||||||
|
data-on-input="$password_valid = validatePassword($form1.password,$form1.confirm_password)"
|
||||||
|
data-on-blur="$password_valid = validatePassword($form1.password,$form1.confirm_password)"
|
||||||
|
type="password"
|
||||||
|
class="form-control"
|
||||||
|
data-class="{'is-invalid':!$password_valid,'is-valid':($password_valid&& $form1.confirm_password)}"
|
||||||
|
id="confirm_password"
|
||||||
|
name="confirm_password"
|
||||||
|
required>
|
||||||
|
<div class="invalid-feedback" data-show="!$password_valid">
|
||||||
|
{% trans "Password does not match. or length is less than 8 characters." %}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</form>
|
||||||
|
</div>
|
||||||
|
<div class="tab-pane"
|
||||||
|
role="tabpanel"
|
||||||
|
aria-labelledby="bootstrap-wizard-validation-tab2"
|
||||||
|
id="bootstrap-wizard-validation-tab2">
|
||||||
|
<form class="needs-validation"
|
||||||
|
id="wizardValidationForm2"
|
||||||
|
novalidate="novalidate"
|
||||||
|
data-wizard-form="2"
|
||||||
|
data-ref-f2>
|
||||||
|
<div class="mb-3">
|
||||||
|
<label for="name" class="form-label">{% trans "Name" %}</label>
|
||||||
|
<input data-bind-form2.name
|
||||||
|
type="text"
|
||||||
|
class="form-control"
|
||||||
|
id="name"
|
||||||
|
name="name"
|
||||||
|
required>
|
||||||
|
</div>
|
||||||
|
<div class="mb-3">
|
||||||
|
<label for="arabic_name" class="form-label">{% trans "Arabic Name" %}</label>
|
||||||
|
<input data-bind-form2.arabic_name
|
||||||
|
type="text"
|
||||||
|
class="form-control"
|
||||||
|
id="arabic_name"
|
||||||
|
name="arabic_name"
|
||||||
|
required>
|
||||||
|
</div>
|
||||||
|
<div class="mb-3">
|
||||||
|
<label for="phone_number" class="form-label">{% trans "Phone Number" %}</label>
|
||||||
|
<span data-show="!$phone_number_valid" class="text-danger">*</span>
|
||||||
|
<input data-bind-form2.phone_number
|
||||||
|
type="tel"
|
||||||
|
data-class="{'is-invalid':!$phone_number_valid}"
|
||||||
|
class="form-control"
|
||||||
|
id="phone_number"
|
||||||
|
name="phone_number"
|
||||||
|
required
|
||||||
|
data-on-input="$phone_number_valid = validate_sa_phone_number($form2.phone_number)">
|
||||||
|
<div class="invalid-feedback" data-show="!$phone_number_valid">{% trans "Please enter a valid phone number" %}</div>
|
||||||
|
</div>
|
||||||
|
</form>
|
||||||
|
</div>
|
||||||
|
<div class="tab-pane"
|
||||||
|
role="tabpanel"
|
||||||
|
aria-labelledby="bootstrap-wizard-validation-tab3"
|
||||||
|
id="bootstrap-wizard-validation-tab3">
|
||||||
|
<form class="needs-validation"
|
||||||
|
id="wizardValidationForm3"
|
||||||
|
novalidate="novalidate"
|
||||||
|
data-wizard-form="3"
|
||||||
|
data-ref-f3>
|
||||||
|
<div class="mb-3">
|
||||||
|
<label for="crn" class="form-label">{% trans "CRN" %}</label>
|
||||||
|
<input data-bind-form3.crn
|
||||||
|
type="text"
|
||||||
|
class="form-control"
|
||||||
|
id="crn"
|
||||||
|
name="crn"
|
||||||
|
required>
|
||||||
|
</div>
|
||||||
|
<div class="mb-3">
|
||||||
|
<label for="vrn" class="form-label">{% trans "VRN" %}</label>
|
||||||
|
<input data-bind-form3.vrn
|
||||||
|
type="text"
|
||||||
|
class="form-control"
|
||||||
|
id="vrn"
|
||||||
|
name="vrn"
|
||||||
|
required>
|
||||||
|
</div>
|
||||||
|
<div class="mb-3">
|
||||||
|
<label for="address" class="form-label">{% trans "Address" %}</label>
|
||||||
|
<textarea data-bind-form3.address
|
||||||
|
class="form-control"
|
||||||
|
id="address"
|
||||||
|
name="address"
|
||||||
|
required></textarea>
|
||||||
|
</div>
|
||||||
|
</form>
|
||||||
|
</div>
|
||||||
|
<div class="tab-pane"
|
||||||
|
role="tabpanel"
|
||||||
|
aria-labelledby="bootstrap-wizard-validation-tab4"
|
||||||
|
id="bootstrap-wizard-validation-tab4">
|
||||||
|
<div class="row flex-center pb-8 pt-4 gx-3 gy-4">
|
||||||
|
<div class="col-12 col-sm-auto">
|
||||||
|
<div class="text-center text-sm-start">
|
||||||
|
<img class="d-dark-none"
|
||||||
|
src="{% static 'images/spot-illustrations/38.webp' %}"
|
||||||
|
alt=""
|
||||||
|
width="220">
|
||||||
|
<img class="d-light-none"
|
||||||
|
src="{% static 'images/spot-illustrations/dark_38.webp' %}"
|
||||||
|
alt=""
|
||||||
|
width="220">
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="col-12 col-sm-auto">
|
||||||
|
<div class="text-center text-sm-start">
|
||||||
|
<h5 class="mb-3">{% trans 'You are all set!' %}</h5>
|
||||||
|
<p class="text-body-emphasis fs-9">
|
||||||
|
{% trans 'Now you can access your account' %}
|
||||||
|
<br>
|
||||||
|
{% trans 'anytime' %} {% trans 'anywhere' %}
|
||||||
|
</p>
|
||||||
|
<button data-on-click="sendFormData()"
|
||||||
|
class="btn btn-primary px-6"
|
||||||
|
id='submit_btn'>{% trans 'Submit' %}</button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div data-computed-form1_valid="validatePassword($form1.password,$form1.confirm_password) && validateEmail($form1.email)"
|
||||||
|
class="card-footer border-top-0"
|
||||||
|
data-wizard-footer="data-wizard-footer">
|
||||||
|
<div class="d-flex pager wizard list-inline mb-0">
|
||||||
|
<button class="d-none btn btn-link ps-0"
|
||||||
|
type="button"
|
||||||
|
data-wizard-prev-btn="data-wizard-prev-btn">{% trans 'Previous' %}</button>
|
||||||
|
<div class="flex-1 text-end">
|
||||||
|
<button data-attr-disabled="!$form1_valid"
|
||||||
|
data-attr-disabled="!$phone_number_valid"
|
||||||
|
class="btn btn-phoenix-primary px-6 px-sm-6 next"
|
||||||
|
type="button"
|
||||||
|
id="next_btn"
|
||||||
|
data-wizard-next-btn="data-wizard-next-btn">{% trans 'Next' %}</button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div class="card-body pt-4 pb-0">
|
|
||||||
<div class="tab-content" data-signals-current_form="1">
|
|
||||||
<div class="tab-pane active" role="tabpanel" aria-labelledby="bootstrap-wizard-validation-tab1" id="bootstrap-wizard-validation-tab1">
|
|
||||||
<form class="needs-validation" id="wizardValidationForm1" novalidate="novalidate" data-wizard-form="1" data-ref-f1>
|
|
||||||
<div class="mb-3">
|
|
||||||
<label
|
|
||||||
for="email"
|
|
||||||
data-class="{'text-danger':!$email_valid}"
|
|
||||||
class="form-label">{% trans "Email"%}
|
|
||||||
<span
|
|
||||||
data-show="!$email_valid"
|
|
||||||
class="text-danger">*</span>
|
|
||||||
</label>
|
|
||||||
<input
|
|
||||||
data-on-input="$email_valid = validateEmail($form1.email)"
|
|
||||||
data-on-blur="$email_valid = validateEmail($form1.email)"
|
|
||||||
data-bind-form1.email
|
|
||||||
data-class="{'is-invalid': !$email_valid , 'is-valid': ($email_valid && $form1.email)}"
|
|
||||||
type="email"
|
|
||||||
class="form-control"
|
|
||||||
id="email"
|
|
||||||
name="email"
|
|
||||||
required>
|
|
||||||
<div class="invalid-feedback" data-show="!$email_valid">
|
|
||||||
{% trans "Please enter a valid email address" %}
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
<div class="mb-3">
|
|
||||||
<label for="password" class="form-label">{% trans "Password" %}</label>
|
|
||||||
<input
|
|
||||||
data-bind-form1.password
|
|
||||||
type="password"
|
|
||||||
data-on-input="$password_valid = validatePassword($form1.password,$form1.confirm_password)"
|
|
||||||
data-on-blur="$password_valid = validatePassword($form1.password,$form1.confirm_password)"
|
|
||||||
class="form-control"
|
|
||||||
data-class="{'is-invalid':($form1.password.length && $form1.password.length < 8),'is-valid':$form1.password.length > 8 }"
|
|
||||||
id="password"
|
|
||||||
name="password"
|
|
||||||
required>
|
|
||||||
<div class="invalid-feedback" data-show="!$password_valid">
|
|
||||||
{% trans "Password does not match. or length is less than 8 characters." %}
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
<div class="mb-3">
|
|
||||||
<label for="confirm_password" class="form-label">{% trans "Confirm Password" %}</label><span class="text-danger" data-show="!$password_valid">*</span>
|
|
||||||
<input
|
|
||||||
data-bind-form1.confirm_password
|
|
||||||
data-on-input="$password_valid = validatePassword($form1.password,$form1.confirm_password)"
|
|
||||||
data-on-blur="$password_valid = validatePassword($form1.password,$form1.confirm_password)"
|
|
||||||
type="password"
|
|
||||||
class="form-control"
|
|
||||||
data-class="{'is-invalid':!$password_valid,'is-valid':($password_valid&& $form1.confirm_password)}"
|
|
||||||
id="confirm_password"
|
|
||||||
name="confirm_password"
|
|
||||||
required>
|
|
||||||
<div class="invalid-feedback" data-show="!$password_valid">
|
|
||||||
{% trans "Password does not match. or length is less than 8 characters." %}
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</form>
|
|
||||||
</div>
|
|
||||||
<div class="tab-pane" role="tabpanel" aria-labelledby="bootstrap-wizard-validation-tab2" id="bootstrap-wizard-validation-tab2">
|
|
||||||
<form class="needs-validation" id="wizardValidationForm2" novalidate="novalidate" data-wizard-form="2" data-ref-f2>
|
|
||||||
<div class="mb-3">
|
|
||||||
<label for="name" class="form-label">{% trans "Name" %}</label>
|
|
||||||
<input data-bind-form2.name type="text" class="form-control" id="name" name="name" required>
|
|
||||||
</div>
|
|
||||||
<div class="mb-3">
|
|
||||||
<label for="arabic_name" class="form-label">{% trans "Arabic Name" %}</label>
|
|
||||||
<input data-bind-form2.arabic_name type="text" class="form-control" id="arabic_name" name="arabic_name" required>
|
|
||||||
</div>
|
|
||||||
<div class="mb-3">
|
|
||||||
<label for="phone_number" class="form-label">{% trans "Phone Number" %}</label><span data-show="!$phone_number_valid" class="text-danger">*</span>
|
|
||||||
<input data-bind-form2.phone_number type="tel" data-class="{'is-invalid':!$phone_number_valid}" class="form-control" id="phone_number" name="phone_number" required
|
|
||||||
data-on-input="$phone_number_valid = validate_sa_phone_number($form2.phone_number)"
|
|
||||||
>
|
|
||||||
<div class="invalid-feedback" data-show="!$phone_number_valid">
|
|
||||||
{% trans "Please enter a valid phone number" %}
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</form>
|
|
||||||
</div>
|
|
||||||
<div class="tab-pane" role="tabpanel" aria-labelledby="bootstrap-wizard-validation-tab3" id="bootstrap-wizard-validation-tab3">
|
|
||||||
<form class="needs-validation" id="wizardValidationForm3" novalidate="novalidate" data-wizard-form="3" data-ref-f3>
|
|
||||||
<div class="mb-3">
|
|
||||||
<label for="crn" class="form-label">{% trans "CRN" %}</label>
|
|
||||||
<input data-bind-form3.crn type="text" class="form-control" id="crn" name="crn" required>
|
|
||||||
</div>
|
|
||||||
<div class="mb-3">
|
|
||||||
<label for="vrn" class="form-label">{% trans "VRN" %}</label>
|
|
||||||
<input data-bind-form3.vrn type="text" class="form-control" id="vrn" name="vrn" required>
|
|
||||||
</div>
|
|
||||||
<div class="mb-3">
|
|
||||||
<label for="address" class="form-label">{% trans "Address" %}</label>
|
|
||||||
<textarea data-bind-form3.address class="form-control" id="address" name="address" required></textarea>
|
|
||||||
</div>
|
|
||||||
</form>
|
|
||||||
</div>
|
|
||||||
<div class="tab-pane" role="tabpanel" aria-labelledby="bootstrap-wizard-validation-tab4" id="bootstrap-wizard-validation-tab4">
|
|
||||||
<div class="row flex-center pb-8 pt-4 gx-3 gy-4">
|
|
||||||
<div class="col-12 col-sm-auto">
|
|
||||||
<div class="text-center text-sm-start"><img class="d-dark-none" src="{% static 'images/spot-illustrations/38.webp' %}" alt="" width="220"><img class="d-light-none" src="{% static 'images/spot-illustrations/dark_38.webp' %}" alt="" width="220"></div>
|
|
||||||
</div>
|
|
||||||
<div class="col-12 col-sm-auto">
|
|
||||||
<div class="text-center text-sm-start">
|
|
||||||
<h5 class="mb-3">{% trans 'You are all set!' %}</h5>
|
|
||||||
<p class="text-body-emphasis fs-9">{% trans 'Now you can access your account' %}<br>{% trans 'anytime' %} {% trans 'anywhere' %}</p><button data-on-click="sendFormData()" class="btn btn-primary px-6" id='submit_btn'>{% trans 'Submit' %}</button>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
<div data-computed-form1_valid="validatePassword($form1.password,$form1.confirm_password) && validateEmail($form1.email)" class="card-footer border-top-0" data-wizard-footer="data-wizard-footer">
|
|
||||||
<div class="d-flex pager wizard list-inline mb-0">
|
|
||||||
<button class="d-none btn btn-link ps-0" type="button" data-wizard-prev-btn="data-wizard-prev-btn">{% trans 'Previous' %}</button>
|
|
||||||
<div class="flex-1 text-end">
|
|
||||||
<button data-attr-disabled="!$form1_valid" data-attr-disabled="!$phone_number_valid" class="btn btn-phoenix-primary px-6 px-sm-6 next" type="button" id="next_btn" data-wizard-next-btn="data-wizard-next-btn">{% trans 'Next' %}</button>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</section>
|
||||||
</div>
|
<section class="pt-lg-0 pt-xl-8">
|
||||||
</section>
|
{% include 'footer.html' %}
|
||||||
<section class="pt-lg-0 pt-xl-8">
|
</section>
|
||||||
{% include 'footer.html' %}
|
<script src="{% static 'js/phoenix.js' %}"></script>
|
||||||
</section>
|
{% endblock content %}
|
||||||
<script src="{% static 'js/phoenix.js' %}"></script>
|
{% block customJS %}
|
||||||
{% endblock content %}
|
|
||||||
|
|
||||||
{% block customJS %}
|
|
||||||
<script src="{% static 'js/main.js' %}"></script>
|
<script src="{% static 'js/main.js' %}"></script>
|
||||||
<script src="{% static 'js/sweetalert2.all.min.js' %}"></script>
|
<script src="{% static 'js/sweetalert2.all.min.js' %}"></script>
|
||||||
|
<script type="module"
|
||||||
<script type="module" src="https://cdn.jsdelivr.net/gh/starfederation/datastar@v1.0.0-beta.11/bundles/datastar.js"></script>
|
src="https://cdn.jsdelivr.net/gh/starfederation/datastar@v1.0.0-beta.11/bundles/datastar.js"></script>
|
||||||
<script>
|
<script>
|
||||||
function validatePassword(password, confirmPassword) {
|
function validatePassword(password, confirmPassword) {
|
||||||
return password === confirmPassword && password.length > 7 && password !== '';
|
return password === confirmPassword && password.length > 7 && password !== '';
|
||||||
}
|
}
|
||||||
function validateEmail(email) {
|
function validateEmail(email) {
|
||||||
const emailRegex = /^[a-zA-Z0-9._%+-]+@[a-zA-Z0-9.-]+\.[a-zA-Z]{2,}$/;
|
const emailRegex = /^[a-zA-Z0-9._%+-]+@[a-zA-Z0-9.-]+\.[a-zA-Z]{2,}$/;
|
||||||
return emailRegex.test(email) && email !== '';
|
return emailRegex.test(email) && email !== '';
|
||||||
}
|
}
|
||||||
function validateform2(name,arabic_name,phone_number) {
|
function validateform2(name,arabic_name,phone_number) {
|
||||||
if (name === '' || arabic_name === '' || phone_number === '' || phone_number.length < 10 || !phone_number.startsWith('056')) {
|
if (name === '' || arabic_name === '' || phone_number === '' || phone_number.length < 10 || !phone_number.startsWith('056')) {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
return true
|
return true
|
||||||
}
|
}
|
||||||
function validate_sa_phone_number(phone_number) {
|
function validate_sa_phone_number(phone_number) {
|
||||||
const phone_numberRegex = /^056[0-9]{7}$/;
|
const phone_numberRegex = /^056[0-9]{7}$/;
|
||||||
return phone_numberRegex.test(phone_number) && phone_numberRegex !== '';
|
return phone_numberRegex.test(phone_number) && phone_numberRegex !== '';
|
||||||
}
|
}
|
||||||
function getAllFormData() {
|
function getAllFormData() {
|
||||||
const forms = document.querySelectorAll('.needs-validation');
|
const forms = document.querySelectorAll('.needs-validation');
|
||||||
const formData = {};
|
const formData = {};
|
||||||
forms.forEach(form => {
|
forms.forEach(form => {
|
||||||
const fields = form.querySelectorAll('input,textarea,select');
|
const fields = form.querySelectorAll('input,textarea,select');
|
||||||
fields.forEach(field => {
|
fields.forEach(field => {
|
||||||
formData[field.name] = field.value;
|
formData[field.name] = field.value;
|
||||||
});
|
|
||||||
});
|
});
|
||||||
return formData;
|
});
|
||||||
|
return formData;
|
||||||
}
|
}
|
||||||
|
|
||||||
function showLoading() {
|
function showLoading() {
|
||||||
@ -237,7 +343,7 @@
|
|||||||
titleText: msg
|
titleText: msg
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
function getCookie(name) {
|
function getCookie(name) {
|
||||||
let cookieValue = null;
|
let cookieValue = null;
|
||||||
if (document.cookie && document.cookie !== "") {
|
if (document.cookie && document.cookie !== "") {
|
||||||
const cookies = document.cookie.split(";");
|
const cookies = document.cookie.split(";");
|
||||||
@ -252,32 +358,32 @@
|
|||||||
return cookieValue;
|
return cookieValue;
|
||||||
}
|
}
|
||||||
async function sendFormData() {
|
async function sendFormData() {
|
||||||
const formData = getAllFormData();
|
const formData = getAllFormData();
|
||||||
const url = "{% url 'account_signup' %}";
|
const url = "{% url 'account_signup' %}";
|
||||||
const csrftoken = getCookie('csrftoken');
|
const csrftoken = getCookie('csrftoken');
|
||||||
try {
|
try {
|
||||||
showLoading();
|
showLoading();
|
||||||
const response = await fetch(url, {
|
const response = await fetch(url, {
|
||||||
method: 'POST',
|
method: 'POST',
|
||||||
headers: {
|
headers: {
|
||||||
'X-CSRFToken': '{{csrf_token}}',
|
'X-CSRFToken': '{{csrf_token}}',
|
||||||
'Content-Type': 'application/json',
|
'Content-Type': 'application/json',
|
||||||
},
|
},
|
||||||
body: JSON.stringify(formData),
|
body: JSON.stringify(formData),
|
||||||
});
|
});
|
||||||
hideLoading();
|
hideLoading();
|
||||||
const data = await response.json();
|
const data = await response.json();
|
||||||
if (response.ok) {
|
if (response.ok) {
|
||||||
notify("success","Account created successfully");
|
notify("success","Account created successfully");
|
||||||
setTimeout(() => {
|
setTimeout(() => {
|
||||||
window.location.href = "{% url 'account_login' %}";
|
window.location.href = "{% url 'account_login' %}";
|
||||||
}, 1000);
|
}, 1000);
|
||||||
} else {
|
} else {
|
||||||
notify("error",data.error);
|
notify("error",data.error);
|
||||||
}
|
|
||||||
} catch (error) {
|
|
||||||
notify("error",error);
|
|
||||||
}
|
}
|
||||||
|
} catch (error) {
|
||||||
|
notify("error",error);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
</script>
|
</script>
|
||||||
{% endblock customJS %}
|
{% endblock customJS %}
|
||||||
|
|||||||
@ -1,18 +1,25 @@
|
|||||||
{% extends "base.html" %}
|
{% extends "base.html" %}
|
||||||
{% load crispy_forms_filters %}
|
{% load crispy_forms_filters %}
|
||||||
{% load allauth i18n static%}
|
{% load allauth i18n static %}
|
||||||
|
{% block title %}
|
||||||
{% block title %}{{ _("Sign Up") }}{% endblock title %}
|
{{ _("Sign Up") }}
|
||||||
|
{% endblock title %}
|
||||||
{% block content %}
|
{% block content %}
|
||||||
<div class="row">
|
<div class="row">
|
||||||
<div class="row min-vh-100 text-center">
|
<div class="row min-vh-100 text-center">
|
||||||
<div class="col-sm-10 col-md-8 col-lg-5 col-xl-5 col-xxl-3">
|
<div class="col-sm-10 col-md-8 col-lg-5 col-xl-5 col-xxl-3">
|
||||||
<a class="d-flex flex-center text-decoration-none mb-4" href="{% url 'home' %}">
|
<a class="d-flex flex-center text-decoration-none mb-4"
|
||||||
|
href="{% url 'home' %}">
|
||||||
<div class="d-flex align-items-center fw-bolder fs-3 d-inline-block">
|
<div class="d-flex align-items-center fw-bolder fs-3 d-inline-block">
|
||||||
<div class="d-flex align-items-center fw-bolder fs-3 d-inline-block">
|
<div class="d-flex align-items-center fw-bolder fs-3 d-inline-block">
|
||||||
<img class="d-dark-none" src="{% static 'images/logos/logo-d.png' %}" alt="{% trans 'home' %}" width="58" />
|
<img class="d-dark-none"
|
||||||
<img class="d-light-none" src="{% static 'images/logos/logo.png' %}" alt="{% trans 'home' %}" width="58" />
|
src="{% static 'images/logos/logo-d.png' %}"
|
||||||
|
alt="{% trans 'home' %}"
|
||||||
|
width="58" />
|
||||||
|
<img class="d-light-none"
|
||||||
|
src="{% static 'images/logos/logo.png' %}"
|
||||||
|
alt="{% trans 'home' %}"
|
||||||
|
width="58" />
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</a>
|
</a>
|
||||||
@ -20,74 +27,76 @@
|
|||||||
<h3 class="text-body-highlight">Sign Up</h3>
|
<h3 class="text-body-highlight">Sign Up</h3>
|
||||||
<p class="text-body-tertiary">Create your account today</p>
|
<p class="text-body-tertiary">Create your account today</p>
|
||||||
</div>
|
</div>
|
||||||
<!-- Passkey Signup -->
|
<!-- Passkey Signup -->
|
||||||
{% if PASSKEY_SIGNUP_ENABLED %}
|
{% if PASSKEY_SIGNUP_ENABLED %}
|
||||||
<hr class="my-4">
|
<hr class="my-4">
|
||||||
<div class="d-grid gap-2">
|
<div class="d-grid gap-2">
|
||||||
<a href="{{ signup_by_passkey_url }}" class="btn btn-outline-primary btn-lg">
|
<a href="{{ signup_by_passkey_url }}"
|
||||||
{{ _("Sign up using a passkey") }}
|
class="btn btn-outline-primary btn-lg">{{ _("Sign up using a passkey") }}</a>
|
||||||
</a>
|
|
||||||
</div>
|
</div>
|
||||||
{% endif %}
|
{% endif %}
|
||||||
|
<!-- Social Signup -->
|
||||||
<!-- Social Signup -->
|
|
||||||
{% if SOCIALACCOUNT_ENABLED %}
|
{% if SOCIALACCOUNT_ENABLED %}
|
||||||
|
|
||||||
{% include "socialaccount/snippets/login.html" with page_layout="entrance" %}
|
{% include "socialaccount/snippets/login.html" with page_layout="entrance" %}
|
||||||
{% endif %}
|
{% endif %}
|
||||||
|
|
||||||
<!-- Sign Up Form -->
|
<!-- Sign Up Form -->
|
||||||
{% if not SOCIALACCOUNT_ONLY %}
|
{% if not SOCIALACCOUNT_ONLY %}
|
||||||
<form method="post" action="{% url 'account_signup' %}" class="needs-validation" novalidate>
|
<form method="post"
|
||||||
|
action="{% url 'account_signup' %}"
|
||||||
|
class="needs-validation"
|
||||||
|
novalidate>
|
||||||
{% csrf_token %}
|
{% csrf_token %}
|
||||||
{{ redirect_field }}
|
{{ redirect_field }}
|
||||||
<div class="mb-3">
|
<div class="mb-3">
|
||||||
<label for="id_email" class="form-label">{{ form.email.label }}</label>
|
<label for="id_email" class="form-label">{{ form.email.label }}</label>
|
||||||
<input type="email" class="form-control" id="id_email" name="email" placeholder="name@example.com">
|
<input type="email"
|
||||||
{% if form.email.errors %}
|
class="form-control"
|
||||||
<div class="text-danger">{{ form.email.errors|striptags }}</div>
|
id="id_email"
|
||||||
{% endif %}
|
name="email"
|
||||||
|
placeholder="name@example.com">
|
||||||
|
{% if form.email.errors %}<div class="text-danger">{{ form.email.errors|striptags }}</div>{% endif %}
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class="mb-3">
|
<div class="mb-3">
|
||||||
<label for="id_password1" class="form-label">{{ form.password1.label }}</label>
|
<label for="id_password1" class="form-label">{{ form.password1.label }}</label>
|
||||||
<div class="position-relative" data-password="data-password">
|
<div class="position-relative" data-password="data-password">
|
||||||
<input type="password" class="form-control form-icon-input pe-6" id="id_password1" name="password1" data-password-input="data-password-input" placeholder="Password">
|
<input type="password"
|
||||||
<a class="btn px-3 py-0 h-100 position-absolute top-0 end-0 fs-7 text-body-tertiary" data-password-toggle="data-password-toggle"><span class="uil uil-eye show"></span><span class="uil uil-eye-slash hide"></span></a>
|
class="form-control form-icon-input pe-6"
|
||||||
|
id="id_password1"
|
||||||
|
name="password1"
|
||||||
|
data-password-input="data-password-input"
|
||||||
|
placeholder="Password">
|
||||||
|
<a class="btn px-3 py-0 h-100 position-absolute top-0 end-0 fs-7 text-body-tertiary"
|
||||||
|
data-password-toggle="data-password-toggle"><span class="uil uil-eye show"></span><span class="uil uil-eye-slash hide"></span></a>
|
||||||
</div>
|
</div>
|
||||||
{% if form.password1.errors %}
|
{% if form.password1.errors %}<div class="text-danger">{{ form.password1.errors|striptags }}</div>{% endif %}
|
||||||
<div class="text-danger">{{ form.password1.errors|striptags }}</div>
|
|
||||||
{% endif %}
|
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class="mb-3">
|
<div class="mb-3">
|
||||||
<label for="id_password2" class="form-label">{{ form.password2.label }}</label>
|
<label for="id_password2" class="form-label">{{ form.password2.label }}</label>
|
||||||
<div class="position-relative" data-password="data-password">
|
<div class="position-relative" data-password="data-password">
|
||||||
<input type="password" class="form-control form-icon-input pe-6" id="id_password2" name="password2" data-password-input="data-password-input" placeholder="Confirm Password">
|
<input type="password"
|
||||||
<a class="btn px-3 py-0 h-100 position-absolute top-0 end-0 fs-7 text-body-tertiary" data-password-toggle="data-password-toggle"><span class="uil uil-eye show"></span><span class="uil uil-eye-slash hide"></span></a>
|
class="form-control form-icon-input pe-6"
|
||||||
|
id="id_password2"
|
||||||
|
name="password2"
|
||||||
|
data-password-input="data-password-input"
|
||||||
|
placeholder="Confirm Password">
|
||||||
|
<a class="btn px-3 py-0 h-100 position-absolute top-0 end-0 fs-7 text-body-tertiary"
|
||||||
|
data-password-toggle="data-password-toggle"><span class="uil uil-eye show"></span><span class="uil uil-eye-slash hide"></span></a>
|
||||||
</div>
|
</div>
|
||||||
{% if form.password2.errors %}
|
{% if form.password2.errors %}<div class="text-danger">{{ form.password2.errors|striptags }}</div>{% endif %}
|
||||||
<div class="text-danger">{{ form.password2.errors|striptags }}</div>
|
|
||||||
{% endif %}
|
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class="form-check mb-3">
|
<div class="form-check mb-3">
|
||||||
|
|
||||||
<input class="form-check-input" id="termsService" type="checkbox" />
|
<input class="form-check-input" id="termsService" type="checkbox" />
|
||||||
<label class="form-label fs-9 text-transform-none" for="termsService">I accept the <a href="">terms </a>and <a href="">privacy policy</a></label>
|
<label class="form-label fs-9 text-transform-none" for="termsService">
|
||||||
|
I accept the <a href="">terms</a>and <a href="">privacy policy</a>
|
||||||
|
</label>
|
||||||
</div>
|
</div>
|
||||||
<button type="submit" class="btn btn-phoenix-primary w-100 mb-3">{{ _("Sign Up") }}</button>
|
<button type="submit" class="btn btn-phoenix-primary w-100 mb-3">{{ _("Sign Up") }}</button>
|
||||||
<div class="text-center">{% trans 'Already have an account?' %}<a class="fw-bold" href="{% url 'account_login' %}"> {{ _("Sign In") }}</a></div>
|
<div class="text-center">
|
||||||
|
{% trans 'Already have an account?' %}<a class="fw-bold" href="{% url 'account_login' %}">{{ _("Sign In") }}</a>
|
||||||
|
</div>
|
||||||
</form>
|
</form>
|
||||||
{% endif %}
|
{% endif %}
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
{% endblock content %}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
{% endblock content %}
|
|
||||||
|
|||||||
@ -2,10 +2,8 @@
|
|||||||
{% load account %}
|
{% load account %}
|
||||||
{% load allauth %}
|
{% load allauth %}
|
||||||
{% user_display user as user_display %}
|
{% user_display user as user_display %}
|
||||||
|
|
||||||
<div class="alert alert-phoenix-danger d-flex fs-9" role="alert">
|
<div class="alert alert-phoenix-danger d-flex fs-9" role="alert">
|
||||||
<span class="fas fa-info-circle fs-8 me-3"></span>
|
<span class="fas fa-info-circle fs-8 me-3"></span>
|
||||||
<strong>{% trans 'Note' %}:</strong>
|
<strong>{% trans 'Note' %}:</strong>
|
||||||
{% blocktranslate %}You are already logged in as {{ user_display }}.{% endblocktranslate %}
|
{% blocktranslate %}You are already logged in as {{ user_display }}.{% endblocktranslate %}
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
|||||||
@ -1,4 +1,4 @@
|
|||||||
{% load i18n allauth %}
|
{% load i18n allauth %}
|
||||||
{% element p %}
|
{% element p %}
|
||||||
<strong>{% trans 'Warning:' %}</strong> {% trans "You currently do not have any email address set up. You should really add an email address so you can receive notifications, reset your password, etc." %}
|
<strong>{% trans 'Warning:' %}</strong> {% trans "You currently do not have any email address set up. You should really add an email address so you can receive notifications, reset your password, etc." %}
|
||||||
{% endelement %}
|
{% endelement %}
|
||||||
|
|||||||
@ -3,49 +3,35 @@
|
|||||||
{% load i18n static %}
|
{% load i18n static %}
|
||||||
{% load allauth account %}
|
{% load allauth account %}
|
||||||
{% block title %}
|
{% block title %}
|
||||||
{% trans 'User Settings' %}
|
{% trans 'User Settings' %}
|
||||||
{% endblock %}
|
{% endblock %}
|
||||||
{% block content %}
|
{% block content %}
|
||||||
<form action="" method="post">
|
<form action="" method="post">
|
||||||
{% csrf_token %}
|
{% csrf_token %}
|
||||||
<div class="col-12 col-xl-8">
|
<div class="col-12 col-xl-8">
|
||||||
<div class=" mb-4">
|
<div class=" mb-4">
|
||||||
|
<div class="row gx-3 mb-4 gy-6 gy-sm-3">
|
||||||
<div class="row gx-3 mb-4 gy-6 gy-sm-3">
|
<div class="col-12 col-sm-8">
|
||||||
<div class="col-12 col-sm-8">
|
<h4 class="mb-4">Default Invoice Accounts</h4>
|
||||||
<h4 class="mb-4">Default Invoice Accounts</h4>
|
<div class="form-icon-container mb-3">{{ form.invoice_cash_account|as_crispy_field }}</div>
|
||||||
<div class="form-icon-container mb-3">
|
<div class="form-icon-container mb-3">{{ form.invoice_prepaid_account|as_crispy_field }}</div>
|
||||||
{{ form.invoice_cash_account|as_crispy_field }}
|
<div class="form-icon-container mb-3">{{ form.invoice_unearned_account|as_crispy_field }}</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="row gx-3 mb-4 gy-6 gy-sm-3">
|
||||||
|
<div class="col-12 col-sm-8">
|
||||||
|
<h4 class="mb-4">Default Bill Accounts</h4>
|
||||||
|
<div class="form-icon-container mb-3">{{ form.bill_cash_account|as_crispy_field }}</div>
|
||||||
|
<div class="form-icon-container mb-3">{{ form.bill_prepaid_account|as_crispy_field }}</div>
|
||||||
|
<div class="form-icon-container mb-3">{{ form.bill_unearned_account|as_crispy_field }}</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="text-start mb-6">
|
||||||
|
<div>
|
||||||
|
<button type="submit" class="btn btn-phoenix-primary">Update</button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div class="form-icon-container mb-3">
|
|
||||||
{{ form.invoice_prepaid_account|as_crispy_field }}
|
|
||||||
</div>
|
|
||||||
<div class="form-icon-container mb-3">
|
|
||||||
{{ form.invoice_unearned_account|as_crispy_field }}
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
</div>
|
||||||
<div class="row gx-3 mb-4 gy-6 gy-sm-3">
|
</form>
|
||||||
<div class="col-12 col-sm-8">
|
{% endblock %}
|
||||||
<h4 class="mb-4">Default Bill Accounts</h4>
|
|
||||||
<div class="form-icon-container mb-3">
|
|
||||||
{{ form.bill_cash_account|as_crispy_field }}
|
|
||||||
</div>
|
|
||||||
<div class="form-icon-container mb-3">
|
|
||||||
{{ form.bill_prepaid_account|as_crispy_field }}
|
|
||||||
</div>
|
|
||||||
<div class="form-icon-container mb-3">
|
|
||||||
{{ form.bill_unearned_account|as_crispy_field }}
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div class="text-start mb-6">
|
|
||||||
<div>
|
|
||||||
<button type="submit" class="btn btn-phoenix-primary">Update</button>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</form>
|
|
||||||
{% endblock %}
|
|
||||||
|
|||||||
@ -1,25 +1,27 @@
|
|||||||
{% extends "base.html" %}
|
{% extends "base.html" %}
|
||||||
{% load i18n static%}
|
{% load i18n static %}
|
||||||
{% load allauth %}
|
{% load allauth %}
|
||||||
{% block head_title %}
|
{% block head_title %}
|
||||||
{% trans "Verify Your Email Address" %}
|
{% trans "Verify Your Email Address" %}
|
||||||
{% endblock head_title %}
|
{% endblock head_title %}
|
||||||
|
|
||||||
{% block content %}
|
{% block content %}
|
||||||
<div class="row">
|
<div class="row">
|
||||||
<div class="row flex-center min-vh-50 py-5">
|
<div class="row flex-center min-vh-50 py-5">
|
||||||
<div class="col-sm-10 col-md-8 col-lg-5 col-xxl-4">
|
<div class="col-sm-10 col-md-8 col-lg-5 col-xxl-4">
|
||||||
<a class="d-flex flex-center text-decoration-none mb-4" href="{% url 'home' %}">
|
<a class="d-flex flex-center text-decoration-none mb-4"
|
||||||
|
href="{% url 'home' %}">
|
||||||
<div class="d-flex align-items-center fw-bolder fs-3 d-inline-block">
|
<div class="d-flex align-items-center fw-bolder fs-3 d-inline-block">
|
||||||
<img class="d-dark-none" src="{% static 'images/logos/logo-d.png' %}" alt="{% trans 'home' %}" width="58" />
|
<img class="d-dark-none"
|
||||||
<img class="d-light-none" src="{% static 'images/logos/logo.png' %}" alt="{% trans 'home' %}" width="58" />
|
src="{% static 'images/logos/logo-d.png' %}"
|
||||||
|
alt="{% trans 'home' %}"
|
||||||
|
width="58" />
|
||||||
|
<img class="d-light-none"
|
||||||
|
src="{% static 'images/logos/logo.png' %}"
|
||||||
|
alt="{% trans 'home' %}"
|
||||||
|
width="58" />
|
||||||
</div>
|
</div>
|
||||||
</a>
|
</a>
|
||||||
<h3>
|
<h3>{% trans "Verify Your Email Address" %}</h3>
|
||||||
{% trans "Verify Your Email Address" %}
|
|
||||||
</h3>
|
|
||||||
|
|
||||||
|
|
||||||
<p>
|
<p>
|
||||||
{% blocktrans %}We have sent an email to you for verification. Follow the link provided to finalize the signup process. If you do not see the verification email in your main inbox, check your spam folder. Please contact us if you do not receive the verification email within a few minutes.{% endblocktrans %}
|
{% blocktrans %}We have sent an email to you for verification. Follow the link provided to finalize the signup process. If you do not see the verification email in your main inbox, check your spam folder. Please contact us if you do not receive the verification email within a few minutes.{% endblocktrans %}
|
||||||
</p>
|
</p>
|
||||||
|
|||||||
@ -8,16 +8,20 @@
|
|||||||
<div class="row">
|
<div class="row">
|
||||||
<div class="row flex-center min-vh-50 py-5">
|
<div class="row flex-center min-vh-50 py-5">
|
||||||
<div class="col-sm-10 col-md-8 col-lg-5 col-xxl-4">
|
<div class="col-sm-10 col-md-8 col-lg-5 col-xxl-4">
|
||||||
<a class="d-flex flex-center text-decoration-none mb-4" href="{% url 'home' %}">
|
<a class="d-flex flex-center text-decoration-none mb-4"
|
||||||
|
href="{% url 'home' %}">
|
||||||
<div class="d-flex align-items-center fw-bolder fs-3 d-inline-block">
|
<div class="d-flex align-items-center fw-bolder fs-3 d-inline-block">
|
||||||
<img class="d-dark-none" src="{% static 'images/logos/logo-d.png' %}" alt="{% trans 'home' %}" width="58" />
|
<img class="d-dark-none"
|
||||||
<img class="d-light-none" src="{% static 'images/logos/logo.png' %}" alt="{% trans 'home' %}" width="58" />
|
src="{% static 'images/logos/logo-d.png' %}"
|
||||||
|
alt="{% trans 'home' %}"
|
||||||
|
width="58" />
|
||||||
|
<img class="d-light-none"
|
||||||
|
src="{% static 'images/logos/logo.png' %}"
|
||||||
|
alt="{% trans 'home' %}"
|
||||||
|
width="58" />
|
||||||
</div>
|
</div>
|
||||||
</a>
|
</a>
|
||||||
<h3>
|
<h3>{% trans "Verify Your Email Address" %}</h3>
|
||||||
{% trans "Verify Your Email Address" %}
|
|
||||||
</h3>
|
|
||||||
|
|
||||||
<p>
|
<p>
|
||||||
{% blocktrans %}We have sent an email to you for verification. Follow the link provided to finalize the signup process. If you do not see the verification email in your main inbox, check your spam folder. Please contact us if you do not receive the verification email within a few minutes.{% endblocktrans %}
|
{% blocktrans %}We have sent an email to you for verification. Follow the link provided to finalize the signup process. If you do not see the verification email in your main inbox, check your spam folder. Please contact us if you do not receive the verification email within a few minutes.{% endblocktrans %}
|
||||||
</p>
|
</p>
|
||||||
|
|||||||
@ -1,5 +1,5 @@
|
|||||||
{% extends "base.html" %}
|
{% extends "base.html" %}
|
||||||
{% load i18n static%}
|
{% load i18n static %}
|
||||||
{% load allauth %}
|
{% load allauth %}
|
||||||
{% block head_title %}
|
{% block head_title %}
|
||||||
{% trans "Verify Your Email Address" %}
|
{% trans "Verify Your Email Address" %}
|
||||||
@ -8,16 +8,20 @@
|
|||||||
<div class="row">
|
<div class="row">
|
||||||
<div class="row flex-center min-vh-50 py-5">
|
<div class="row flex-center min-vh-50 py-5">
|
||||||
<div class="col-sm-10 col-md-8 col-lg-5 col-xxl-4">
|
<div class="col-sm-10 col-md-8 col-lg-5 col-xxl-4">
|
||||||
<a class="d-flex flex-center text-decoration-none mb-4" href="{% url 'home' %}">
|
<a class="d-flex flex-center text-decoration-none mb-4"
|
||||||
|
href="{% url 'home' %}">
|
||||||
<div class="d-flex align-items-center fw-bolder fs-3 d-inline-block">
|
<div class="d-flex align-items-center fw-bolder fs-3 d-inline-block">
|
||||||
<img class="d-dark-none" src="{% static 'images/logos/logo-d.png' %}" alt="{% trans 'home' %}" width="58" />
|
<img class="d-dark-none"
|
||||||
<img class="d-light-none" src="{% static 'images/logos/logo.png' %}" alt="{% trans 'home' %}" width="58" />
|
src="{% static 'images/logos/logo-d.png' %}"
|
||||||
|
alt="{% trans 'home' %}"
|
||||||
|
width="58" />
|
||||||
|
<img class="d-light-none"
|
||||||
|
src="{% static 'images/logos/logo.png' %}"
|
||||||
|
alt="{% trans 'home' %}"
|
||||||
|
width="58" />
|
||||||
</div>
|
</div>
|
||||||
</a>
|
</a>
|
||||||
<h3>
|
<h3>{% trans "Verify Your Email Address" %}</h3>
|
||||||
{% trans "Verify Your Email Address" %}
|
|
||||||
</h3>
|
|
||||||
|
|
||||||
{% url 'account_email' as email_url %}
|
{% url 'account_email' as email_url %}
|
||||||
<p>
|
<p>
|
||||||
{% blocktrans %}This part of the site requires us to verify that
|
{% blocktrans %}This part of the site requires us to verify that
|
||||||
|
|||||||
@ -1,7 +1,8 @@
|
|||||||
|
|
||||||
{% extends "base.html" %}
|
{% extends "base.html" %}
|
||||||
{% load i18n custom_filters %}
|
{% load i18n custom_filters %}
|
||||||
{% block title %}{% trans "Accounts" %}{% endblock title %}
|
{% block title %}
|
||||||
|
{% trans "Accounts" %}
|
||||||
|
{% endblock title %}
|
||||||
{% block accounts %}
|
{% block accounts %}
|
||||||
<a class="nav-link active fw-bold">
|
<a class="nav-link active fw-bold">
|
||||||
{% trans "Accounts"|capfirst %}
|
{% trans "Accounts"|capfirst %}
|
||||||
@ -10,18 +11,17 @@
|
|||||||
{% endblock %}
|
{% endblock %}
|
||||||
{% block content %}
|
{% block content %}
|
||||||
<div class="row mt-4">
|
<div class="row mt-4">
|
||||||
|
|
||||||
<div class="d-flex justify-content-between mb-2">
|
<div class="d-flex justify-content-between mb-2">
|
||||||
<h3 class=""><i class="fas fa-right-to-bracket me-2"></i> {% trans "Audit Log Dashboard" %}</h3>
|
<h3 class="">
|
||||||
|
<i class="fas fa-right-to-bracket me-2"></i> {% trans "Audit Log Dashboard" %}
|
||||||
|
</h3>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<!-- Log Type Tabs -->
|
<!-- Log Type Tabs -->
|
||||||
<div class="mb-4">
|
<div class="mb-4">
|
||||||
{% include 'admin_management/nav.html' %}
|
{% include 'admin_management/nav.html' %}
|
||||||
|
<div class="tab-content p-3 border border-top-0 rounded-bottom"
|
||||||
<div class="tab-content p-3 border border-top-0 rounded-bottom" id="accountTypeTabsContent">
|
id="accountTypeTabsContent">
|
||||||
<!-- modellogs Tab -->
|
<!-- modellogs Tab -->
|
||||||
|
|
||||||
{% if page_obj %}
|
{% if page_obj %}
|
||||||
<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">
|
||||||
@ -37,38 +37,23 @@
|
|||||||
<tbody class="list">
|
<tbody class="list">
|
||||||
{% for event in page_obj.object_list %}
|
{% for event in page_obj.object_list %}
|
||||||
<tr class="hover-actions-trigger btn-reveal-trigger position-static">
|
<tr class="hover-actions-trigger btn-reveal-trigger position-static">
|
||||||
|
<td class="align-middle product white-space-nowrap">{{ event.datetime }}</td>
|
||||||
<td class="align-middle product white-space-nowrap">{{event.datetime}}</td>
|
|
||||||
<td class="align-middle product white-space-nowrap">{{ event.user.username|default:"N/A" }}</td>
|
<td class="align-middle product white-space-nowrap">{{ event.user.username|default:"N/A" }}</td>
|
||||||
<td class="align-middle product white-space-nowrap">{{ event.get_login_type_display}}</td>
|
<td class="align-middle product white-space-nowrap">{{ event.get_login_type_display }}</td>
|
||||||
<td class="align-middle product white-space-nowrap">{{ event.username}}</td>
|
<td class="align-middle product white-space-nowrap">{{ event.username }}</td>
|
||||||
<td class="align-middle product white-space-nowrap">{{ event.remote_ip}}</td>
|
<td class="align-middle product white-space-nowrap">{{ event.remote_ip }}</td>
|
||||||
|
|
||||||
</tr>
|
</tr>
|
||||||
{% endfor %}
|
{% endfor %}
|
||||||
</tbody>
|
</tbody>
|
||||||
</table>
|
</table>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
|
||||||
<div class="d-flex justify-content-end mt-3">
|
<div class="d-flex justify-content-end mt-3">
|
||||||
<div class="d-flex">
|
<div class="d-flex">{% include 'partials/pagination.html' with q='loginEvents' %}</div>
|
||||||
{% include 'partials/pagination.html' with q='loginEvents' %}
|
|
||||||
</div>
|
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
{% else %}
|
{% else %}
|
||||||
<p>No authentication audit events found.</p>
|
<p>No authentication audit events found.</p>
|
||||||
{% endif %}
|
{% endif %}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
{% endblock %}
|
{% endblock %}
|
||||||
|
|
||||||
|
|||||||
@ -1,4 +1,3 @@
|
|||||||
|
|
||||||
{% extends 'base.html' %}
|
{% extends 'base.html' %}
|
||||||
{% load i18n %}
|
{% load i18n %}
|
||||||
{% load crispy_forms_filters %}
|
{% load crispy_forms_filters %}
|
||||||
@ -16,37 +15,27 @@
|
|||||||
</form>
|
</form>
|
||||||
</div>
|
</div>
|
||||||
</div> {% endcomment %}
|
</div> {% endcomment %}
|
||||||
|
|
||||||
|
|
||||||
<!---->
|
<!---->
|
||||||
<div class="row justify-content-center mt-5 mb-3">
|
<div class="row justify-content-center mt-5 mb-3">
|
||||||
|
<div class="col-lg-8 col-md-10">
|
||||||
<div class="col-lg-8 col-md-10">
|
<div class="card shadow-sm border-0 rounded-3">
|
||||||
<div class="card shadow-sm border-0 rounded-3">
|
<div class="card-header bg-gray-200 py-3 border-0 rounded-top-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 'Activate Account' %}</h3>
|
||||||
<h3 class="mb-0 fs-4 text-center text-white">
|
</div>
|
||||||
{% trans 'Activate Account'%}
|
<div class="card-body bg-light-subtle">
|
||||||
</h3>
|
<p class="text-center">Are you sure you want to activate this account "{{ obj.email }}"?</p>
|
||||||
</div>
|
<form method="post">
|
||||||
<div class="card-body bg-light-subtle">
|
{% csrf_token %}
|
||||||
<p class="text-center">Are you sure you want to activate this account "{{ obj.email }}"?</p>
|
<hr class="my-2">
|
||||||
|
<div class="d-grid gap-2 d-md-flex justify-content-md-center mt-3">
|
||||||
<form method="post">
|
<button class="btn btn-lg btn-phoenix-primary md-me-2" type="submit">{{ _("Activate") }}</button>
|
||||||
{% csrf_token %}
|
<a class="btn btn-lg btn-phoenix-danger mx-2"
|
||||||
<hr class="my-2">
|
href="{% url 'user_management' request.dealer.slug %}">Cancel</a>
|
||||||
<div class="d-grid gap-2 d-md-flex justify-content-md-center mt-3">
|
</div>
|
||||||
<button class="btn btn-lg btn-phoenix-primary md-me-2" type="submit">{{ _("Activate") }}</button>
|
</form>
|
||||||
<a class="btn btn-lg btn-phoenix-danger mx-2" href="{% url 'user_management' request.dealer.slug %}">Cancel</a>
|
</div>
|
||||||
</div>
|
|
||||||
</form>
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
</div>
|
||||||
</div>
|
|
||||||
|
|
||||||
</div>
|
|
||||||
<!---->
|
<!---->
|
||||||
{% endblock %}
|
{% endblock %}
|
||||||
|
|||||||
@ -1,29 +1,28 @@
|
|||||||
{% extends "base.html" %}
|
{% extends "base.html" %}
|
||||||
{% load i18n %}
|
{% load i18n %}
|
||||||
{%block title%} {%trans 'Admin Management' %} {%endblock%}
|
{% block title %}
|
||||||
{% block content %}
|
{% trans 'Admin Management' %} {% endblock %}
|
||||||
<div class="row row-cols-1 row-cols-sm-2 row-cols-md-4 g-4 mt-10">
|
{% block content %}
|
||||||
<div class="col">
|
<div class="row row-cols-1 row-cols-sm-2 row-cols-md-4 g-4 mt-10">
|
||||||
<a href="{% url 'user_management' request.dealer.slug %}">
|
<div class="col">
|
||||||
<div class="card h-100">
|
<a href="{% url 'user_management' request.dealer.slug %}">
|
||||||
<div class="card-header text-center">
|
<div class="card h-100">
|
||||||
<h5 class="card-title">{{ _("User Management")}}</h5>
|
<div class="card-header text-center">
|
||||||
<span class="me-2"><i class="fas fa-user fa-2x"></i></span>
|
<h5 class="card-title">{{ _("User Management") }}</h5>
|
||||||
|
<span class="me-2"><i class="fas fa-user fa-2x"></i></span>
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</a>
|
||||||
</a>
|
</div>
|
||||||
</div>
|
<div class="col">
|
||||||
<div class="col">
|
<a href="{% url 'audit_log_dashboard' request.dealer.slug %}">
|
||||||
<a href="{% url 'audit_log_dashboard' request.dealer.slug %}">
|
<div class="card h-100">
|
||||||
<div class="card h-100">
|
<div class="card-header text-center">
|
||||||
<div class="card-header text-center">
|
<h5 class="card-title">{{ _("Audit Log Dashboard") }}</h5>
|
||||||
<h5 class="card-title">{{ _("Audit Log Dashboard")}}</h5>
|
<span class="me-2"><i class="fas fa-user fa-2x"></i></span>
|
||||||
<span class="me-2"><i class="fas fa-user fa-2x"></i></span>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</a>
|
||||||
</a>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
{% endblock content %}
|
||||||
|
|
||||||
|
|
||||||
{% endblock content %}
|
|
||||||
|
|||||||
@ -1,7 +1,8 @@
|
|||||||
|
|
||||||
{% extends "base.html" %}
|
{% extends "base.html" %}
|
||||||
{% load i18n custom_filters %}
|
{% load i18n custom_filters %}
|
||||||
{% block title %}{% trans "Accounts" %}{% endblock title %}
|
{% block title %}
|
||||||
|
{% trans "Accounts" %}
|
||||||
|
{% endblock title %}
|
||||||
{% block accounts %}
|
{% block accounts %}
|
||||||
<a class="nav-link active fw-bold">
|
<a class="nav-link active fw-bold">
|
||||||
{% trans "Accounts"|capfirst %}
|
{% trans "Accounts"|capfirst %}
|
||||||
@ -10,18 +11,17 @@
|
|||||||
{% endblock %}
|
{% endblock %}
|
||||||
{% block content %}
|
{% block content %}
|
||||||
<div class="row mt-4">
|
<div class="row mt-4">
|
||||||
|
|
||||||
<div class="d-flex justify-content-between mb-2">
|
<div class="d-flex justify-content-between mb-2">
|
||||||
<h3 class=""><i class="fas fa-history me-2"></i>{% trans "Audit Log Dashboard" %}</h3>
|
<h3 class="">
|
||||||
|
<i class="fas fa-history me-2"></i>{% trans "Audit Log Dashboard" %}
|
||||||
|
</h3>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<!-- Log Type Tabs -->
|
<!-- Log Type Tabs -->
|
||||||
<div class="mb-4">
|
<div class="mb-4">
|
||||||
{% include 'admin_management/nav.html' %}
|
{% include 'admin_management/nav.html' %}
|
||||||
|
<div class="tab-content p-3 border border-top-0 rounded-bottom"
|
||||||
<div class="tab-content p-3 border border-top-0 rounded-bottom" id="accountTypeTabsContent">
|
id="accountTypeTabsContent">
|
||||||
<!-- modellogs Tab -->
|
<!-- modellogs Tab -->
|
||||||
|
|
||||||
{% if page_obj %}
|
{% if page_obj %}
|
||||||
<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 mt-3">
|
<table class="table align-items-center table-flush table-hover mt-3">
|
||||||
@ -33,51 +33,53 @@
|
|||||||
<th>{% trans "Model" %}</th>
|
<th>{% trans "Model" %}</th>
|
||||||
<th>{% trans "Object ID" %}</th>
|
<th>{% trans "Object ID" %}</th>
|
||||||
<th>{% trans "Object Representation" %}</th>
|
<th>{% trans "Object Representation" %}</th>
|
||||||
<th>{% trans "Field" %}</th> {# Dedicated column for field name #}
|
<th>{% trans "Field" %}</th>
|
||||||
<th>{% trans "Old Value" %}</th> {# Dedicated column for old value #}
|
{# Dedicated column for field name #}
|
||||||
<th>{% trans "New Value" %}</th> {# Dedicated column for new value #}
|
<th>{% trans "Old Value" %}</th>
|
||||||
|
{# Dedicated column for old value #}
|
||||||
|
<th>{% trans "New Value" %}</th>
|
||||||
|
{# Dedicated column for new value #}
|
||||||
</tr>
|
</tr>
|
||||||
</thead>
|
</thead>
|
||||||
<tbody>
|
<tbody>
|
||||||
{% for event in page_obj.object_list %}
|
{% for event in page_obj.object_list %}
|
||||||
{% if event.field_changes %}
|
{% if event.field_changes %}
|
||||||
{# Loop through each individual field change for this event #}
|
{# Loop through each individual field change for this event #}
|
||||||
{% for change in event.field_changes %}
|
{% for change in event.field_changes %}
|
||||||
<tr>
|
<tr>
|
||||||
{# Display common event details using rowspan for the first change #}
|
{# Display common event details using rowspan for the first change #}
|
||||||
{% if forloop.first %}
|
{% if forloop.first %}
|
||||||
<td rowspan="{{ event.field_changes|length }}">
|
<td rowspan="{{ event.field_changes|length }}">{{ event.datetime|date:"Y-m-d H:i:s" }}</td>
|
||||||
{{ event.datetime|date:"Y-m-d H:i:s" }}
|
<td rowspan="{{ event.field_changes|length }}">{{ event.user.username|default:"Anonymous" }}</td>
|
||||||
</td>
|
<td rowspan="{{ event.field_changes|length }}">{{ event.event_type_display }}</td>
|
||||||
<td rowspan="{{ event.field_changes|length }}">
|
<td rowspan="{{ event.field_changes|length }}">{{ event.model_name|title }}</td>
|
||||||
{{ event.user.username|default:"Anonymous" }}
|
<td rowspan="{{ event.field_changes|length }}">{{ event.object_id }}</td>
|
||||||
</td>
|
<td rowspan="{{ event.field_changes|length }}">{{ event.object_repr }}</td>
|
||||||
<td rowspan="{{ event.field_changes|length }}">
|
|
||||||
{{ event.event_type_display }}
|
|
||||||
</td>
|
|
||||||
<td rowspan="{{ event.field_changes|length }}">
|
|
||||||
{{ event.model_name|title }}
|
|
||||||
</td>
|
|
||||||
<td rowspan="{{ event.field_changes|length }}">
|
|
||||||
{{ event.object_id }}
|
|
||||||
</td>
|
|
||||||
<td rowspan="{{ event.field_changes|length }}">
|
|
||||||
{{ event.object_repr }}
|
|
||||||
</td>
|
|
||||||
{% endif %}
|
{% endif %}
|
||||||
|
{# Display the specific field change details in their own columns #}
|
||||||
{# Display the specific field change details in their own columns #}
|
<td>
|
||||||
<td><strong>{{ change.field }}</strong></td>
|
<strong>{{ change.field }}</strong>
|
||||||
|
</td>
|
||||||
<td>
|
<td>
|
||||||
{% if change.old is not None %}
|
{% if change.old is not None %}
|
||||||
<pre style="white-space: pre-wrap; word-break: break-all; font-size: 0.85em; background-color: #f8f9fa; padding: 5px; border-radius: 3px;">{{ change.old }}</pre>
|
<pre style="white-space: pre-wrap;
|
||||||
|
word-break: break-all;
|
||||||
|
font-size: 0.85em;
|
||||||
|
background-color: #f8f9fa;
|
||||||
|
padding: 5px;
|
||||||
|
border-radius: 3px">{{ change.old }}</pre>
|
||||||
{% else %}
|
{% else %}
|
||||||
(None)
|
(None)
|
||||||
{% endif %}
|
{% endif %}
|
||||||
</td>
|
</td>
|
||||||
<td>
|
<td>
|
||||||
{% if change.new is not None %}
|
{% if change.new is not None %}
|
||||||
<pre style="white-space: pre-wrap; word-break: break-all; font-size: 0.85em; background-color: #f8f9fa; padding: 5px; border-radius: 3px;">{{ change.new }}</pre>
|
<pre style="white-space: pre-wrap;
|
||||||
|
word-break: break-all;
|
||||||
|
font-size: 0.85em;
|
||||||
|
background-color: #f8f9fa;
|
||||||
|
padding: 5px;
|
||||||
|
border-radius: 3px">{{ change.new }}</pre>
|
||||||
{% else %}
|
{% else %}
|
||||||
(None)
|
(None)
|
||||||
{% endif %}
|
{% endif %}
|
||||||
@ -85,7 +87,7 @@
|
|||||||
</tr>
|
</tr>
|
||||||
{% endfor %}
|
{% endfor %}
|
||||||
{% else %}
|
{% else %}
|
||||||
{# Fallback for events with no specific field changes (e.g., CREATE, DELETE) #}
|
{# Fallback for events with no specific field changes (e.g., CREATE, DELETE) #}
|
||||||
<tr>
|
<tr>
|
||||||
<td>{{ event.datetime|date:"Y-m-d H:i:s" }}</td>
|
<td>{{ event.datetime|date:"Y-m-d H:i:s" }}</td>
|
||||||
<td>{{ event.user.username|default:"Anonymous" }}</td>
|
<td>{{ event.user.username|default:"Anonymous" }}</td>
|
||||||
@ -93,7 +95,7 @@
|
|||||||
<td>{{ event.model_name|title }}</td>
|
<td>{{ event.model_name|title }}</td>
|
||||||
<td>{{ event.object_id }}</td>
|
<td>{{ event.object_id }}</td>
|
||||||
<td>{{ event.object_repr }}</td>
|
<td>{{ event.object_repr }}</td>
|
||||||
{# Span the 'Field', 'Old Value', 'New Value' columns #}
|
{# Span the 'Field', 'Old Value', 'New Value' columns #}
|
||||||
<td>
|
<td>
|
||||||
{% if event.event_type_display == "Create" %}
|
{% if event.event_type_display == "Create" %}
|
||||||
{% trans "Object created." %}
|
{% trans "Object created." %}
|
||||||
@ -108,26 +110,14 @@
|
|||||||
{% endfor %}
|
{% endfor %}
|
||||||
</tbody>
|
</tbody>
|
||||||
</table>
|
</table>
|
||||||
|
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class="d-flex justify-content-end mt-3">
|
<div class="d-flex justify-content-end mt-3">
|
||||||
<div class="d-flex">
|
<div class="d-flex">{% include 'partials/pagination.html' with q='userActions' %}</div>
|
||||||
{% include 'partials/pagination.html' with q='userActions' %}
|
|
||||||
</div>
|
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
{% else %}
|
{% else %}
|
||||||
<p>{% trans "No model change audit events found." %}</p>
|
<p>{% trans "No model change audit events found." %}</p>
|
||||||
{% endif %}
|
{% endif %}
|
||||||
|
|
||||||
|
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
{% endblock %}
|
{% endblock %}
|
||||||
|
|
||||||
|
|||||||
@ -1,6 +1,5 @@
|
|||||||
{% load i18n %}
|
{% load i18n %}
|
||||||
<ul class="nav nav-tabs" id="accountTypeTabs" role="tablist">
|
<ul class="nav nav-tabs" id="accountTypeTabs" role="tablist">
|
||||||
|
|
||||||
<li class="nav-item me-3" role="presentation">
|
<li class="nav-item me-3" role="presentation">
|
||||||
<a href="{% url 'audit_log_dashboard' request.dealer.slug %}?q=userActions">
|
<a href="{% url 'audit_log_dashboard' request.dealer.slug %}?q=userActions">
|
||||||
<i class="fas fa-history me-2"></i>{% trans "User Actions" %}
|
<i class="fas fa-history me-2"></i>{% trans "User Actions" %}
|
||||||
|
|||||||
@ -14,38 +14,31 @@
|
|||||||
</form>
|
</form>
|
||||||
</div>
|
</div>
|
||||||
</div> {% endcomment %}
|
</div> {% endcomment %}
|
||||||
|
|
||||||
<!---->
|
<!---->
|
||||||
|
|
||||||
<div class="row justify-content-center mt-5 mb-3">
|
<div class="row justify-content-center mt-5 mb-3">
|
||||||
|
<div class="col-lg-8 col-md-10">
|
||||||
<div class="col-lg-8 col-md-10">
|
<div class="card shadow-sm border-0 rounded-3">
|
||||||
<div class="card shadow-sm border-0 rounded-3">
|
<div class="card-header bg-gray-200 py-3 border-0 rounded-top-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 'Delete Account' %}</h3>
|
||||||
<h3 class="mb-0 fs-4 text-center text-white">
|
</div>
|
||||||
{% trans 'Delete Account'%}
|
<div class="card-body bg-light-subtle">
|
||||||
</h3>
|
<p class="lead text-center">
|
||||||
</div>
|
Are you sure you want to delete this account "{{ obj.email }}"? This will delete all associated information for this user.
|
||||||
<div class="card-body bg-light-subtle">
|
</p>
|
||||||
<p class="lead text-center">Are you sure you want to delete this account "{{ obj.email }}"? This will delete all associated information for this user.</p>
|
<form method="post">
|
||||||
|
{% csrf_token %}
|
||||||
<form method="post">
|
<hr class="my-2">
|
||||||
{% csrf_token %}
|
<div class="d-grid gap-2 d-md-flex justify-content-md-center mt-3">
|
||||||
<hr class="my-2">
|
<button class="btn btn-lg btn-phoenix-danger md-me-2" type="submit">
|
||||||
<div class="d-grid gap-2 d-md-flex justify-content-md-center mt-3">
|
<i class="fas fa-trash me-2"></i>{{ _("Delete Permenantly") }}
|
||||||
<button class="btn btn-lg btn-phoenix-danger md-me-2" type="submit"><i class="fas fa-trash me-2"></i>{{ _("Delete Permenantly") }}</button>
|
</button>
|
||||||
<a class="btn btn-lg btn-phoenix-secondary mx-2" href="{% url 'user_management' request.dealer.slug %}"><i class="fas fa-ban me-2"></i>Cancel</a>
|
<a class="btn btn-lg btn-phoenix-secondary mx-2"
|
||||||
</div>
|
href="{% url 'user_management' request.dealer.slug %}"><i class="fas fa-ban me-2"></i>Cancel</a>
|
||||||
</form>
|
</div>
|
||||||
|
</form>
|
||||||
|
</div>
|
||||||
|
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
</div>
|
||||||
</div>
|
|
||||||
|
|
||||||
</div>
|
|
||||||
<!---->
|
<!---->
|
||||||
{% endblock %}
|
{% endblock %}
|
||||||
|
|
||||||
|
|||||||
@ -1,7 +1,8 @@
|
|||||||
|
|
||||||
{% extends "base.html" %}
|
{% extends "base.html" %}
|
||||||
{% load i18n custom_filters %}
|
{% load i18n custom_filters %}
|
||||||
{% block title %}{% trans "Accounts" %}{% endblock title %}
|
{% block title %}
|
||||||
|
{% trans "Accounts" %}
|
||||||
|
{% endblock title %}
|
||||||
{% block accounts %}
|
{% block accounts %}
|
||||||
<a class="nav-link active fw-bold">
|
<a class="nav-link active fw-bold">
|
||||||
{% trans "Accounts"|capfirst %}
|
{% trans "Accounts"|capfirst %}
|
||||||
@ -10,16 +11,16 @@
|
|||||||
{% endblock %}
|
{% endblock %}
|
||||||
{% block content %}
|
{% block content %}
|
||||||
<div class="row mt-4">
|
<div class="row mt-4">
|
||||||
|
|
||||||
<div class="d-flex justify-content-between mb-2">
|
<div class="d-flex justify-content-between mb-2">
|
||||||
<h3 class=""><i class="fas fa-file-alt me-2"></i> {% trans "Audit Log Dashboard" %}</h3>
|
<h3 class="">
|
||||||
|
<i class="fas fa-file-alt me-2"></i> {% trans "Audit Log Dashboard" %}
|
||||||
|
</h3>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<!-- Log Type Tabs -->
|
<!-- Log Type Tabs -->
|
||||||
<div class="mb-4">
|
<div class="mb-4">
|
||||||
{% include 'admin_management/nav.html' %}
|
{% include 'admin_management/nav.html' %}
|
||||||
|
<div class="tab-content p-3 border border-top-0 rounded-bottom"
|
||||||
<div class="tab-content p-3 border border-top-0 rounded-bottom" id="accountTypeTabsContent">
|
id="accountTypeTabsContent">
|
||||||
<!-- modellogs Tab -->
|
<!-- modellogs Tab -->
|
||||||
{% if page_obj %}
|
{% if page_obj %}
|
||||||
<div class="table-responsive px-1 scrollbar mt-3">
|
<div class="table-responsive px-1 scrollbar mt-3">
|
||||||
@ -36,34 +37,23 @@
|
|||||||
<tbody class="list">
|
<tbody class="list">
|
||||||
{% for event in page_obj.object_list %}
|
{% for event in page_obj.object_list %}
|
||||||
<tr class="hover-actions-trigger btn-reveal-trigger position-static">
|
<tr class="hover-actions-trigger btn-reveal-trigger position-static">
|
||||||
|
<td class="align-middle product white-space-nowrap">{{ event.datetime }}</td>
|
||||||
<td class="align-middle product white-space-nowrap">{{event.datetime}}</td>
|
|
||||||
<td class="align-middle product white-space-nowrap">{{ event.user.username|default:"Anonymous" }}</td>
|
<td class="align-middle product white-space-nowrap">{{ event.user.username|default:"Anonymous" }}</td>
|
||||||
<td class="align-middle product white-space-nowrap">{{ event.url }}</td>
|
<td class="align-middle product white-space-nowrap">{{ event.url }}</td>
|
||||||
<td class="align-middle product white-space-nowrap">{{ event.method}}</td>
|
<td class="align-middle product white-space-nowrap">{{ event.method }}</td>
|
||||||
<td class="align-middle product white-space-nowrap">{{ event.remote_ip}}</td>
|
<td class="align-middle product white-space-nowrap">{{ event.remote_ip }}</td>
|
||||||
|
|
||||||
</tr>
|
</tr>
|
||||||
{% endfor %}
|
{% endfor %}
|
||||||
</tbody>
|
</tbody>
|
||||||
</table>
|
</table>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class="d-flex justify-content-end mt-3">
|
<div class="d-flex justify-content-end mt-3">
|
||||||
<div class="d-flex">
|
<div class="d-flex">{% include 'partials/pagination.html' with q='userRequests' %}</div>
|
||||||
{% include 'partials/pagination.html' with q='userRequests' %}
|
|
||||||
</div>
|
|
||||||
</div>
|
</div>
|
||||||
{% else %}
|
{% else %}
|
||||||
<p>No request audit events found.</p>
|
<p>No request audit events found.</p>
|
||||||
{% endif %}
|
{% endif %}
|
||||||
|
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
{% endblock %}
|
{% endblock %}
|
||||||
|
|
||||||
|
|||||||
@ -1,271 +1,322 @@
|
|||||||
{% extends 'base.html' %}
|
{% extends 'base.html' %}
|
||||||
{% load i18n static humanize %}
|
{% load i18n static humanize %}
|
||||||
|
|
||||||
{% block title %}
|
{% block title %}
|
||||||
{% trans 'User Management' %}
|
{% trans 'User Management' %}
|
||||||
{% endblock title %}
|
{% endblock title %}
|
||||||
|
|
||||||
{% block content %}
|
{% block content %}
|
||||||
<section class="pt-5 pb-9">
|
<section class="pt-5 pb-9">
|
||||||
<div class="row">
|
<div class="row">
|
||||||
<h2 class="mb-4"><i class="fa-solid fa-people-roof me-1"></i> {% trans 'User Management' %}</h2>
|
<h2 class="mb-4">
|
||||||
<div class="row g-3 justify-content-between mb-4">
|
<i class="fa-solid fa-people-roof me-1"></i> {% trans 'User Management' %}
|
||||||
<div class="col-12">
|
</h2>
|
||||||
<h3 class="mb-3">{% trans 'Customers' %}</h3>
|
<div class="row g-3 justify-content-between mb-4">
|
||||||
<div class="table-responsive scrollbar mx-n1 px-1">
|
<div class="col-12">
|
||||||
<table class="table align-items-center table-flush table-hover">
|
<h3 class="mb-3">{% trans 'Customers' %}</h3>
|
||||||
<thead>
|
<div class="table-responsive scrollbar mx-n1 px-1">
|
||||||
<tr class="bg-body-highlight">
|
<table class="table align-items-center table-flush table-hover">
|
||||||
<th class="sort white-space-nowrap align-middle text-uppercase ps-0" scope="col" style="width:20%;">{{ _('First Name') }}</th>
|
<thead>
|
||||||
<th class="sort white-space-nowrap align-middle text-uppercase ps-0" scope="col" style="width:20%;">{{ _('Last Name') }}</th>
|
<tr class="bg-body-highlight">
|
||||||
<th class="sort white-space-nowrap align-middle text-uppercase ps-0" scope="col" style="width:20%;">{{ _('Email') }}</th>
|
<th class="sort white-space-nowrap align-middle text-uppercase ps-0"
|
||||||
<th class="sort align-middle ps-4 pe-5 text-uppercase " scope="col" style="width:15%;">{{ _('Status') }}</th>
|
scope="col"
|
||||||
<th class="sort align-middle ps-4 pe-5 text-uppercase" scope="col" style="width:15%;">{{ _('Created date') }}</th>
|
style="width:20%">{{ _("First Name") }}</th>
|
||||||
<th class="sort text-end align-middle pe-0 ps-4" scope="col">{{ _('Actions') }}</th>
|
<th class="sort white-space-nowrap align-middle text-uppercase ps-0"
|
||||||
</tr>
|
scope="col"
|
||||||
</thead>
|
style="width:20%">{{ _("Last Name") }}</th>
|
||||||
<tbody class="list" id="leal-tables-body">
|
<th class="sort white-space-nowrap align-middle text-uppercase ps-0"
|
||||||
{% for customer in customers %}
|
scope="col"
|
||||||
<tr>
|
style="width:20%">{{ _("Email") }}</th>
|
||||||
<td class="ps-0">{{ customer.first_name }}</td>
|
<th class="sort align-middle ps-4 pe-5 text-uppercase"
|
||||||
<td class="ps-0">{{ customer.last_name }}</td>
|
scope="col"
|
||||||
<td class="ps-0">{{ customer.email }}</td>
|
style="width:15%">{{ _("Status") }}</th>
|
||||||
<td class="ps-0">
|
<th class="sort align-middle ps-4 pe-5 text-uppercase"
|
||||||
{% if customer.active %}
|
scope="col"
|
||||||
<span class="fas fa-check-circle text-success"></span> {{ _('Active') }}
|
style="width:15%">{{ _("Created date") }}</th>
|
||||||
{% else %}
|
<th class="sort text-end align-middle pe-0 ps-4" scope="col">{{ _("Actions") }}</th>
|
||||||
<span class="fas fa-times-circle text-danger"></span> {{ _('Inactive') }}
|
</tr>
|
||||||
{% endif %}
|
</thead>
|
||||||
</td>
|
<tbody class="list" id="leal-tables-body">
|
||||||
<td class="ps-0">{{ customer.created|naturalday|capfirst }}</td>
|
{% for customer in customers %}
|
||||||
<td class="align-middle white-space-nowrap text-end">
|
<tr>
|
||||||
<div class="btn-reveal-trigger position-static">
|
<td class="ps-0">{{ customer.first_name }}</td>
|
||||||
<button
|
<td class="ps-0">{{ customer.last_name }}</td>
|
||||||
class="btn btn-sm dropdown-toggle dropdown-caret-none transition-none btn-reveal fs-10"
|
<td class="ps-0">{{ customer.email }}</td>
|
||||||
type="button"
|
<td class="ps-0">
|
||||||
data-bs-toggle="dropdown"
|
{% if customer.active %}
|
||||||
data-boundary="window"
|
<span class="fas fa-check-circle text-success"></span> {{ _("Active") }}
|
||||||
aria-haspopup="true"
|
{% else %}
|
||||||
aria-expanded="false"
|
<span class="fas fa-times-circle text-danger"></span> {{ _("Inactive") }}
|
||||||
data-bs-reference="parent">
|
{% endif %}
|
||||||
<span class="fas fa-ellipsis-h fs-10"></span>
|
</td>
|
||||||
</button>
|
<td class="ps-0">{{ customer.created|naturalday|capfirst }}</td>
|
||||||
<div class="dropdown-menu dropdown-menu-end py-2">
|
<td class="align-middle white-space-nowrap text-end">
|
||||||
<a href="{% url 'activate_account' request.dealer.slug 'customer' customer.slug %}"><button class="dropdown-item text-primary">{% trans "Activate" %}</button></a>
|
<div class="btn-reveal-trigger position-static">
|
||||||
<div class="dropdown-divider"></div>
|
<button class="btn btn-sm dropdown-toggle dropdown-caret-none transition-none btn-reveal fs-10"
|
||||||
<a href="{% url 'permenant_delete_account' request.dealer.slug 'customer' customer.slug %}"><button class="dropdown-item text-danger">{% trans "Permenantly Delete" %}</button></a>
|
type="button"
|
||||||
|
data-bs-toggle="dropdown"
|
||||||
|
data-boundary="window"
|
||||||
|
aria-haspopup="true"
|
||||||
|
aria-expanded="false"
|
||||||
|
data-bs-reference="parent">
|
||||||
|
<span class="fas fa-ellipsis-h fs-10"></span>
|
||||||
|
</button>
|
||||||
|
<div class="dropdown-menu dropdown-menu-end py-2">
|
||||||
|
<a href="{% url 'activate_account' request.dealer.slug 'customer' customer.slug %}">
|
||||||
|
<button class="dropdown-item text-primary">{% trans "Activate" %}</button>
|
||||||
|
</a>
|
||||||
|
<div class="dropdown-divider"></div>
|
||||||
|
<a href="{% url 'permenant_delete_account' request.dealer.slug 'customer' customer.slug %}">
|
||||||
|
<button class="dropdown-item text-danger">{% trans "Permenantly Delete" %}</button>
|
||||||
|
</a>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</td>
|
||||||
|
</tr>
|
||||||
|
{% empty %}
|
||||||
|
<td colspan="6" class="text-center">{% trans 'No data available in table' %}</td>
|
||||||
|
{% endfor %}
|
||||||
|
</tbody>
|
||||||
|
</table>
|
||||||
|
</div>
|
||||||
|
<div class="d-flex justify-content-end mt-3">
|
||||||
|
<div class="d-flex">
|
||||||
|
{% if is_paginated %}
|
||||||
|
{% include 'partials/pagination.html' %}
|
||||||
|
{% endif %}
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</td>
|
</div>
|
||||||
</tr>
|
|
||||||
{% empty %}
|
|
||||||
<td colspan="6" class="text-center">{% trans 'No data available in table' %}</td>
|
|
||||||
{% endfor %}
|
|
||||||
</tbody>
|
|
||||||
</table>
|
|
||||||
</div>
|
|
||||||
<div class="d-flex justify-content-end mt-3">
|
|
||||||
<div class="d-flex">
|
|
||||||
{% if is_paginated %}
|
|
||||||
{% include 'partials/pagination.html' %}
|
|
||||||
{% endif %}
|
|
||||||
</div>
|
</div>
|
||||||
</div>
|
<div class="row mt-5">
|
||||||
</div>
|
<div class="col-12">
|
||||||
</div>
|
<h3 class="mb-3">{% trans 'Organizations' %}</h3>
|
||||||
<div class="row mt-5">
|
<div class="table-responsive scrollbar mx-n1 px-1">
|
||||||
<div class="col-12">
|
<table class="table align-items-center table-flush table-hover">
|
||||||
<h3 class="mb-3">{% trans 'Organizations' %}</h3>
|
<thead>
|
||||||
<div class="table-responsive scrollbar mx-n1 px-1">
|
<tr class="bg-body-highlight">
|
||||||
<table class="table align-items-center table-flush table-hover">
|
<th class="sort white-space-nowrap align-middle text-uppercase ps-0"
|
||||||
<thead>
|
scope="col"
|
||||||
<tr class="bg-body-highlight">
|
style="width:20%">{{ _("Name") }}</th>
|
||||||
<th class="sort white-space-nowrap align-middle text-uppercase ps-0" scope="col" style="width:20%;">{{ _('Name') }}</th>
|
<th class="sort white-space-nowrap align-middle text-uppercase ps-0"
|
||||||
<th class="sort white-space-nowrap align-middle text-uppercase ps-0" scope="col" style="width:20%;">{{ _('Arabic Name') }}</th>
|
scope="col"
|
||||||
<th class="sort white-space-nowrap align-middle text-uppercase ps-0" scope="col" style="width:20%;">{{ _('Email') }}</th>
|
style="width:20%">{{ _("Arabic Name") }}</th>
|
||||||
<th class="sort align-middle ps-4 pe-5 text-uppercase " scope="col" style="width:15%;">{{ _('Status') }}</th>
|
<th class="sort white-space-nowrap align-middle text-uppercase ps-0"
|
||||||
<th class="sort align-middle ps-4 pe-5 text-uppercase" scope="col" style="width:15%;">{{ _('Create date') }}</th>
|
scope="col"
|
||||||
<th class="sort text-end align-middle pe-0 ps-4" scope="col">{{ _('Actions') }}</th>
|
style="width:20%">{{ _("Email") }}</th>
|
||||||
</tr>
|
<th class="sort align-middle ps-4 pe-5 text-uppercase"
|
||||||
</thead>
|
scope="col"
|
||||||
<tbody class="list" id="leal-tables-body">
|
style="width:15%">{{ _("Status") }}</th>
|
||||||
{% for organization in organizations %}
|
<th class="sort align-middle ps-4 pe-5 text-uppercase"
|
||||||
<tr>
|
scope="col"
|
||||||
<td class="ps-0">{{ organization.name }}</td>
|
style="width:15%">{{ _("Create date") }}</th>
|
||||||
<td class="ps-0">{{ organization.arabic_name }}</td>
|
<th class="sort text-end align-middle pe-0 ps-4" scope="col">{{ _("Actions") }}</th>
|
||||||
<td class="ps-0">{{ organization.email }}</td>
|
</tr>
|
||||||
<td class="ps-0">
|
</thead>
|
||||||
{% if customer.active %}
|
<tbody class="list" id="leal-tables-body">
|
||||||
<span class="fas fa-check-circle text-success"></span> {{ _('Active') }}
|
{% for organization in organizations %}
|
||||||
{% else %}
|
<tr>
|
||||||
<span class="fas fa-times-circle text-danger"></span> {{ _('Inactive') }}
|
<td class="ps-0">{{ organization.name }}</td>
|
||||||
{% endif %}
|
<td class="ps-0">{{ organization.arabic_name }}</td>
|
||||||
</td>
|
<td class="ps-0">{{ organization.email }}</td>
|
||||||
<td class="ps-0">{{ organization.created|naturalday|capfirst }}</td>
|
<td class="ps-0">
|
||||||
<td class="align-middle white-space-nowrap text-end">
|
{% if customer.active %}
|
||||||
<div class="btn-reveal-trigger position-static">
|
<span class="fas fa-check-circle text-success"></span> {{ _("Active") }}
|
||||||
<button
|
{% else %}
|
||||||
class="btn btn-sm dropdown-toggle dropdown-caret-none transition-none btn-reveal fs-10"
|
<span class="fas fa-times-circle text-danger"></span> {{ _("Inactive") }}
|
||||||
type="button"
|
{% endif %}
|
||||||
data-bs-toggle="dropdown"
|
</td>
|
||||||
data-boundary="window"
|
<td class="ps-0">{{ organization.created|naturalday|capfirst }}</td>
|
||||||
aria-haspopup="true"
|
<td class="align-middle white-space-nowrap text-end">
|
||||||
aria-expanded="false"
|
<div class="btn-reveal-trigger position-static">
|
||||||
data-bs-reference="parent">
|
<button class="btn btn-sm dropdown-toggle dropdown-caret-none transition-none btn-reveal fs-10"
|
||||||
<span class="fas fa-ellipsis-h fs-10"></span>
|
type="button"
|
||||||
</button>
|
data-bs-toggle="dropdown"
|
||||||
<div class="dropdown-menu dropdown-menu-end py-2">
|
data-boundary="window"
|
||||||
<a href="{% url 'activate_account' request.dealer.slug 'organization' organization.slug %}"><button class="dropdown-item text-primary">{% trans "Activate" %}</button></a>
|
aria-haspopup="true"
|
||||||
<div class="dropdown-divider"></div>
|
aria-expanded="false"
|
||||||
<a href="{% url 'permenant_delete_account' request.dealer.slug 'organization' organization.slug %}"><button class="dropdown-item text-danger">{% trans "Permenantly Delete" %}</button></a>
|
data-bs-reference="parent">
|
||||||
|
<span class="fas fa-ellipsis-h fs-10"></span>
|
||||||
|
</button>
|
||||||
|
<div class="dropdown-menu dropdown-menu-end py-2">
|
||||||
|
<a href="{% url 'activate_account' request.dealer.slug 'organization' organization.slug %}">
|
||||||
|
<button class="dropdown-item text-primary">{% trans "Activate" %}</button>
|
||||||
|
</a>
|
||||||
|
<div class="dropdown-divider"></div>
|
||||||
|
<a href="{% url 'permenant_delete_account' request.dealer.slug 'organization' organization.slug %}">
|
||||||
|
<button class="dropdown-item text-danger">{% trans "Permenantly Delete" %}</button>
|
||||||
|
</a>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</td>
|
||||||
|
</tr>
|
||||||
|
{% empty %}
|
||||||
|
<td colspan="6" class="text-center">{% trans 'No data available in table' %}</td>
|
||||||
|
{% endfor %}
|
||||||
|
</tbody>
|
||||||
|
</table>
|
||||||
|
</div>
|
||||||
|
<div class="d-flex justify-content-end mt-3">
|
||||||
|
<div class="d-flex">
|
||||||
|
{% if is_paginated %}
|
||||||
|
{% include 'partials/pagination.html' %}
|
||||||
|
{% endif %}
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</td>
|
</div>
|
||||||
</tr>
|
|
||||||
{% empty %}
|
|
||||||
<td colspan="6" class="text-center">{% trans 'No data available in table' %}</td>
|
|
||||||
{% endfor %}
|
|
||||||
</tbody>
|
|
||||||
</table>
|
|
||||||
</div>
|
|
||||||
<div class="d-flex justify-content-end mt-3">
|
|
||||||
<div class="d-flex">
|
|
||||||
{% if is_paginated %}
|
|
||||||
{% include 'partials/pagination.html' %}
|
|
||||||
{% endif %}
|
|
||||||
</div>
|
</div>
|
||||||
</div>
|
<div class="row mt-5">
|
||||||
</div>
|
<div class="col-12">
|
||||||
</div>
|
<h3 class="mb-3">{% trans 'Vendors' %}</h3>
|
||||||
<div class="row mt-5">
|
<div class="table-responsive scrollbar mx-n1 px-1">
|
||||||
<div class="col-12">
|
<table class="table align-items-center table-flush table-hover">
|
||||||
<h3 class="mb-3">{% trans 'Vendors' %}</h3>
|
<thead>
|
||||||
<div class="table-responsive scrollbar mx-n1 px-1">
|
<tr class="bg-body-highlight">
|
||||||
<table class="table align-items-center table-flush table-hover">
|
<th class="sort white-space-nowrap align-middle text-uppercase ps-0"
|
||||||
<thead>
|
scope="col"
|
||||||
<tr class="bg-body-highlight">
|
style="width:20%">{{ _("Name") }}</th>
|
||||||
<th class="sort white-space-nowrap align-middle text-uppercase ps-0" scope="col" style="width:20%;">{{ _('Name') }}</th>
|
<th class="sort white-space-nowrap align-middle text-uppercase ps-0"
|
||||||
<th class="sort white-space-nowrap align-middle text-uppercase ps-0" scope="col" style="width:20%;">{{ _('Arabic Name') }}</th>
|
scope="col"
|
||||||
<th class="sort white-space-nowrap align-middle text-uppercase ps-0" scope="col" style="width:20%;">{{ _('Email') }}</th>
|
style="width:20%">{{ _("Arabic Name") }}</th>
|
||||||
<th class="sort white-space-nowrap align-middle text-uppercase ps-0" scope="col" style="width:15%;">{{ _('Status') }}</th>
|
<th class="sort white-space-nowrap align-middle text-uppercase ps-0"
|
||||||
<th class="sort align-middle ps-4 pe-5 text-uppercase" scope="col" style="width:15%;">{{ _('Create date') }}</th>
|
scope="col"
|
||||||
<th class="sort text-end align-middle pe-0 ps-4" scope="col">{{ _('Actions') }}</th>
|
style="width:20%">{{ _("Email") }}</th>
|
||||||
</tr>
|
<th class="sort white-space-nowrap align-middle text-uppercase ps-0"
|
||||||
</thead>
|
scope="col"
|
||||||
<tbody class="list" id="leal-tables-body">
|
style="width:15%">{{ _("Status") }}</th>
|
||||||
{% for vendor in vendors %}
|
<th class="sort align-middle ps-4 pe-5 text-uppercase"
|
||||||
<tr>
|
scope="col"
|
||||||
<td class="ps-0">{{ vendor.name }}</td>
|
style="width:15%">{{ _("Create date") }}</th>
|
||||||
<td class="ps-0">{{ vendor.arabic_name }}</td>
|
<th class="sort text-end align-middle pe-0 ps-4" scope="col">{{ _("Actions") }}</th>
|
||||||
<td class="ps-0">{{ vendor.email }}</td>
|
</tr>
|
||||||
<td class="ps-0">
|
</thead>
|
||||||
{% if customer.active %}
|
<tbody class="list" id="leal-tables-body">
|
||||||
<span class="fas fa-check-circle text-success"></span> {{ _('Active') }}
|
{% for vendor in vendors %}
|
||||||
{% else %}
|
<tr>
|
||||||
<span class="fas fa-times-circle text-danger"></span> {{ _('Inactive') }}
|
<td class="ps-0">{{ vendor.name }}</td>
|
||||||
{% endif %}
|
<td class="ps-0">{{ vendor.arabic_name }}</td>
|
||||||
</td>
|
<td class="ps-0">{{ vendor.email }}</td>
|
||||||
<td class="ps-0">{{ vendor.created_at|naturalday|capfirst }}</td>
|
<td class="ps-0">
|
||||||
<td class="align-middle white-space-nowrap text-end">
|
{% if customer.active %}
|
||||||
<div class="btn-reveal-trigger position-static">
|
<span class="fas fa-check-circle text-success"></span> {{ _("Active") }}
|
||||||
<button
|
{% else %}
|
||||||
class="btn btn-sm dropdown-toggle dropdown-caret-none transition-none btn-reveal fs-10"
|
<span class="fas fa-times-circle text-danger"></span> {{ _("Inactive") }}
|
||||||
type="button"
|
{% endif %}
|
||||||
data-bs-toggle="dropdown"
|
</td>
|
||||||
data-boundary="window"
|
<td class="ps-0">{{ vendor.created_at|naturalday|capfirst }}</td>
|
||||||
aria-haspopup="true"
|
<td class="align-middle white-space-nowrap text-end">
|
||||||
aria-expanded="false"
|
<div class="btn-reveal-trigger position-static">
|
||||||
data-bs-reference="parent">
|
<button class="btn btn-sm dropdown-toggle dropdown-caret-none transition-none btn-reveal fs-10"
|
||||||
<span class="fas fa-ellipsis-h fs-10"></span>
|
type="button"
|
||||||
</button>
|
data-bs-toggle="dropdown"
|
||||||
<div class="dropdown-menu dropdown-menu-end py-2">
|
data-boundary="window"
|
||||||
<a href="{% url 'activate_account' request.dealer.slug 'vendor' vendor.slug %}"><button class="dropdown-item text-primary">{% trans "Activate" %}</button></a>
|
aria-haspopup="true"
|
||||||
<div class="dropdown-divider"></div>
|
aria-expanded="false"
|
||||||
<a href="{% url 'permenant_delete_account' request.dealer.slug 'vendor' vendor.slug %}"><button class="dropdown-item text-danger">{% trans "Permenantly Delete" %}</button></a>
|
data-bs-reference="parent">
|
||||||
|
<span class="fas fa-ellipsis-h fs-10"></span>
|
||||||
|
</button>
|
||||||
|
<div class="dropdown-menu dropdown-menu-end py-2">
|
||||||
|
<a href="{% url 'activate_account' request.dealer.slug 'vendor' vendor.slug %}">
|
||||||
|
<button class="dropdown-item text-primary">{% trans "Activate" %}</button>
|
||||||
|
</a>
|
||||||
|
<div class="dropdown-divider"></div>
|
||||||
|
<a href="{% url 'permenant_delete_account' request.dealer.slug 'vendor' vendor.slug %}">
|
||||||
|
<button class="dropdown-item text-danger">{% trans "Permenantly Delete" %}</button>
|
||||||
|
</a>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</td>
|
||||||
|
</tr>
|
||||||
|
{% empty %}
|
||||||
|
<td colspan="6" class="text-center">{% trans 'No data available in table' %}</td>
|
||||||
|
{% endfor %}
|
||||||
|
</tbody>
|
||||||
|
</table>
|
||||||
|
</div>
|
||||||
|
<div class="d-flex justify-content-end mt-3">
|
||||||
|
<div class="d-flex">
|
||||||
|
{% if is_paginated %}
|
||||||
|
{% include 'partials/pagination.html' %}
|
||||||
|
{% endif %}
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</td>
|
</div>
|
||||||
</tr>
|
|
||||||
{% empty %}
|
|
||||||
<td colspan="6" class="text-center">{% trans 'No data available in table' %}</td>
|
|
||||||
{% endfor %}
|
|
||||||
</tbody>
|
|
||||||
</table>
|
|
||||||
</div>
|
|
||||||
<div class="d-flex justify-content-end mt-3">
|
|
||||||
<div class="d-flex">
|
|
||||||
{% if is_paginated %}
|
|
||||||
{% include 'partials/pagination.html' %}
|
|
||||||
{% endif %}
|
|
||||||
</div>
|
</div>
|
||||||
</div>
|
<div class="row mt-5">
|
||||||
</div>
|
<div class="col-12">
|
||||||
</div>
|
<h3 class="mb-3">{% trans 'Staff' %}</h3>
|
||||||
<div class="row mt-5">
|
<div class="table-responsive scrollbar mx-n1 px-1">
|
||||||
<div class="col-12">
|
<table class="table align-items-center table-flush table-hover">
|
||||||
<h3 class="mb-3">{% trans 'Staff' %}</h3>
|
<thead>
|
||||||
<div class="table-responsive scrollbar mx-n1 px-1">
|
<tr class="bg-body-highlight">
|
||||||
<table class="table align-items-center table-flush table-hover">
|
<th class="sort white-space-nowrap align-middle text-uppercase ps-0"
|
||||||
<thead>
|
scope="col"
|
||||||
<tr class="bg-body-highlight">
|
style="width:20%">{{ _("Name") }}</th>
|
||||||
<th class="sort white-space-nowrap align-middle text-uppercase ps-0" scope="col" style="width:20%;">{{ _('Name') }}</th>
|
<th class="sort white-space-nowrap align-middle text-uppercase ps-0"
|
||||||
<th class="sort white-space-nowrap align-middle text-uppercase ps-0" scope="col" style="width:20%;">{{ _('Arabic Name') }}</th>
|
scope="col"
|
||||||
<th class="sort white-space-nowrap align-middle text-uppercase ps-0" scope="col" style="width:20%;">{{ _('Email') }}</th>
|
style="width:20%">{{ _("Arabic Name") }}</th>
|
||||||
<th class="sort white-space-nowrap align-middle text-uppercase ps-0" scope="col" style="width:15%;">{{ _('Status') }}</th>
|
<th class="sort white-space-nowrap align-middle text-uppercase ps-0"
|
||||||
<th class="sort align-middle ps-4 pe-5 text-uppercase" scope="col" style="width:15%;">{{ _('Create date') }}</th>
|
scope="col"
|
||||||
<th class="sort text-end align-middle pe-0 ps-4" scope="col">{{ _('Actions') }}</th>
|
style="width:20%">{{ _("Email") }}</th>
|
||||||
</tr>
|
<th class="sort white-space-nowrap align-middle text-uppercase ps-0"
|
||||||
</thead>
|
scope="col"
|
||||||
<tbody class="list" id="leal-tables-body">
|
style="width:15%">{{ _("Status") }}</th>
|
||||||
{% for obj in staff %}
|
<th class="sort align-middle ps-4 pe-5 text-uppercase"
|
||||||
<tr>
|
scope="col"
|
||||||
<td class="ps-0">{{ obj.name }}</td>
|
style="width:15%">{{ _("Create date") }}</th>
|
||||||
<td class="ps-0">{{ obj.arabic_name }}</td>
|
<th class="sort text-end align-middle pe-0 ps-4" scope="col">{{ _("Actions") }}</th>
|
||||||
<td class="ps-0">{{ obj.email }}</td>
|
</tr>
|
||||||
<td class="ps-0">
|
</thead>
|
||||||
{% if obj.active %}
|
<tbody class="list" id="leal-tables-body">
|
||||||
<span class="fas fa-check-circle text-success"></span> {{ _('Active') }}
|
{% for obj in staff %}
|
||||||
{% else %}
|
<tr>
|
||||||
<span class="fas fa-times-circle text-danger"></span> {{ _('Inactive') }}
|
<td class="ps-0">{{ obj.name }}</td>
|
||||||
{% endif %}
|
<td class="ps-0">{{ obj.arabic_name }}</td>
|
||||||
</td>
|
<td class="ps-0">{{ obj.email }}</td>
|
||||||
<td class="ps-0">{{ obj.created|naturalday|capfirst }}</td>
|
<td class="ps-0">
|
||||||
<td class="align-middle white-space-nowrap text-end">
|
{% if obj.active %}
|
||||||
<div class="btn-reveal-trigger position-static">
|
<span class="fas fa-check-circle text-success"></span> {{ _("Active") }}
|
||||||
<button
|
{% else %}
|
||||||
class="btn btn-sm dropdown-toggle dropdown-caret-none transition-none btn-reveal fs-10"
|
<span class="fas fa-times-circle text-danger"></span> {{ _("Inactive") }}
|
||||||
type="button"
|
{% endif %}
|
||||||
data-bs-toggle="dropdown"
|
</td>
|
||||||
data-boundary="window"
|
<td class="ps-0">{{ obj.created|naturalday|capfirst }}</td>
|
||||||
aria-haspopup="true"
|
<td class="align-middle white-space-nowrap text-end">
|
||||||
aria-expanded="false"
|
<div class="btn-reveal-trigger position-static">
|
||||||
data-bs-reference="parent">
|
<button class="btn btn-sm dropdown-toggle dropdown-caret-none transition-none btn-reveal fs-10"
|
||||||
<span class="fas fa-ellipsis-h fs-10"></span>
|
type="button"
|
||||||
</button>
|
data-bs-toggle="dropdown"
|
||||||
<div class="dropdown-menu dropdown-menu-end py-2">
|
data-boundary="window"
|
||||||
<a href="{% url 'activate_account' request.dealer.slug 'staff' obj.slug %}"><button class="dropdown-item text-primary">{% trans "Activate" %}</button></a>
|
aria-haspopup="true"
|
||||||
<div class="dropdown-divider"></div>
|
aria-expanded="false"
|
||||||
<a href="{% url 'permenant_delete_account' request.dealer.slug 'staff' obj.slug %}"><button class="dropdown-item text-danger">{% trans "Permenantly Delete" %}</button></a>
|
data-bs-reference="parent">
|
||||||
|
<span class="fas fa-ellipsis-h fs-10"></span>
|
||||||
|
</button>
|
||||||
|
<div class="dropdown-menu dropdown-menu-end py-2">
|
||||||
|
<a href="{% url 'activate_account' request.dealer.slug 'staff' obj.slug %}">
|
||||||
|
<button class="dropdown-item text-primary">{% trans "Activate" %}</button>
|
||||||
|
</a>
|
||||||
|
<div class="dropdown-divider"></div>
|
||||||
|
<a href="{% url 'permenant_delete_account' request.dealer.slug 'staff' obj.slug %}">
|
||||||
|
<button class="dropdown-item text-danger">{% trans "Permenantly Delete" %}</button>
|
||||||
|
</a>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</td>
|
||||||
|
</tr>
|
||||||
|
{% empty %}
|
||||||
|
<td colspan="6" class="text-center">{% trans 'No data available in table' %}</td>
|
||||||
|
{% endfor %}
|
||||||
|
</tbody>
|
||||||
|
</table>
|
||||||
|
</div>
|
||||||
|
<div class="d-flex justify-content-end mt-3">
|
||||||
|
<div class="d-flex">
|
||||||
|
{% if is_paginated %}
|
||||||
|
{% include 'partials/pagination.html' %}
|
||||||
|
{% endif %}
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</td>
|
</div>
|
||||||
</tr>
|
|
||||||
{% empty %}
|
|
||||||
<td colspan="6" class="text-center">{% trans 'No data available in table' %}</td>
|
|
||||||
{% endfor %}
|
|
||||||
</tbody>
|
|
||||||
</table>
|
|
||||||
</div>
|
|
||||||
<div class="d-flex justify-content-end mt-3">
|
|
||||||
<div class="d-flex">
|
|
||||||
{% if is_paginated %}
|
|
||||||
{% include 'partials/pagination.html' %}
|
|
||||||
{% endif %}
|
|
||||||
</div>
|
</div>
|
||||||
</div>
|
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</section>
|
||||||
</div>
|
|
||||||
</section>
|
|
||||||
{% endblock %}
|
{% endblock %}
|
||||||
|
|
||||||
|
|||||||
@ -1,10 +1,8 @@
|
|||||||
{% extends BASE_TEMPLATE %}
|
{% extends BASE_TEMPLATE %}
|
||||||
{% load i18n %}
|
{% load i18n %}
|
||||||
{% load static %}
|
{% load static %}
|
||||||
|
|
||||||
{% block title %}{{ page_title }}{% endblock %}
|
{% block title %}{{ page_title }}{% endblock %}
|
||||||
{% block description %}{{ page_description }}{% endblock %}
|
{% block description %}{{ page_description }}{% endblock %}
|
||||||
|
|
||||||
{% block content %}
|
{% block content %}
|
||||||
<div class="container py-5">
|
<div class="container py-5">
|
||||||
<div class="card bg-body">
|
<div class="card bg-body">
|
||||||
@ -81,7 +79,6 @@
|
|||||||
</div>
|
</div>
|
||||||
<div class="col-md-6">
|
<div class="col-md-6">
|
||||||
<div class="d-flex align-items-center">
|
<div class="d-flex align-items-center">
|
||||||
|
|
||||||
<span class="icon-saudi_riyal text-primary"></span>
|
<span class="icon-saudi_riyal text-primary"></span>
|
||||||
<strong class="me-2">{% trans 'Service price' %}:</strong> {{ appointment.get_appointment_amount_to_pay_text }}
|
<strong class="me-2">{% trans 'Service price' %}:</strong> {{ appointment.get_appointment_amount_to_pay_text }}
|
||||||
</div>
|
</div>
|
||||||
@ -91,9 +88,11 @@
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
{% endblock %}
|
{% endblock %}
|
||||||
|
|
||||||
{% block extra_js %}
|
{% block extra_js %}
|
||||||
<script src="https://cdnjs.cloudflare.com/ajax/libs/moment.js/2.30.1/moment.js" crossorigin="anonymous"></script>
|
<script src="https://cdnjs.cloudflare.com/ajax/libs/moment.js/2.30.1/moment.js"
|
||||||
<script src="https://cdnjs.cloudflare.com/ajax/libs/moment-timezone/0.5.45/moment-timezone-with-data.min.js" crossorigin="anonymous"></script>
|
crossorigin="anonymous"></script>
|
||||||
<script src="https://cdnjs.cloudflare.com/ajax/libs/fullcalendar/6.1.10/index.global.min.js" crossorigin="anonymous"></script>
|
<script src="https://cdnjs.cloudflare.com/ajax/libs/moment-timezone/0.5.45/moment-timezone-with-data.min.js"
|
||||||
|
crossorigin="anonymous"></script>
|
||||||
|
<script src="https://cdnjs.cloudflare.com/ajax/libs/fullcalendar/6.1.10/index.global.min.js"
|
||||||
|
crossorigin="anonymous"></script>
|
||||||
{% endblock %}
|
{% endblock %}
|
||||||
|
|||||||
@ -2,7 +2,9 @@
|
|||||||
{% load i18n %}
|
{% load i18n %}
|
||||||
{% load static %}
|
{% load static %}
|
||||||
{% block customCSS %}
|
{% block customCSS %}
|
||||||
<link rel="stylesheet" type="text/css" href="{% static 'css/verification_code.css' %}"/>
|
<link rel="stylesheet"
|
||||||
|
type="text/css"
|
||||||
|
href="{% static 'css/verification_code.css' %}" />
|
||||||
{% endblock %}
|
{% endblock %}
|
||||||
{% block title %}
|
{% block title %}
|
||||||
{% trans 'Enter Verification Code' %}
|
{% trans 'Enter Verification Code' %}
|
||||||
@ -19,7 +21,8 @@
|
|||||||
<form method="post"
|
<form method="post"
|
||||||
action="{% url 'appointment:email_change_verification_code' %}">
|
action="{% url 'appointment:email_change_verification_code' %}">
|
||||||
{% csrf_token %}
|
{% csrf_token %}
|
||||||
<label>{% trans 'Code' %}:
|
<label>
|
||||||
|
{% trans 'Code' %}:
|
||||||
<input type="text" name="code" placeholder="X1Y2Z3" required>
|
<input type="text" name="code" placeholder="X1Y2Z3" required>
|
||||||
</label>
|
</label>
|
||||||
<button class="btn btn-phoenix-primary" type="submit">{% trans 'Submit' %}</button>
|
<button class="btn btn-phoenix-primary" type="submit">{% trans 'Submit' %}</button>
|
||||||
@ -29,7 +32,15 @@
|
|||||||
<div class="messages" style="margin: 20px 0">
|
<div class="messages" style="margin: 20px 0">
|
||||||
{% if messages %}
|
{% if messages %}
|
||||||
{% for message in messages %}
|
{% for message in messages %}
|
||||||
<div class="alert alert-dismissible {% if message.tags %}alert-{% if message.level == DEFAULT_MESSAGE_LEVELS.ERROR %}danger{% else %}{{ message.tags }}{% endif %}{% endif %}"
|
<div class="alert alert-dismissible
|
||||||
|
{% if message.tags %}
|
||||||
|
alert-
|
||||||
|
{% if message.level == DEFAULT_MESSAGE_LEVELS.ERROR %}
|
||||||
|
danger
|
||||||
|
{% else %}
|
||||||
|
{{ message.tags }}
|
||||||
|
{% endif %}
|
||||||
|
{% endif %}"
|
||||||
role="alert">{{ message }}</div>
|
role="alert">{{ message }}</div>
|
||||||
{% endfor %}
|
{% endfor %}
|
||||||
{% endif %}
|
{% endif %}
|
||||||
|
|||||||
@ -1,21 +1,22 @@
|
|||||||
{% extends BASE_TEMPLATE %}
|
{% extends BASE_TEMPLATE %}
|
||||||
{% load i18n %}
|
{% load i18n %}
|
||||||
{% load static %}
|
{% load static %}
|
||||||
|
|
||||||
{% block customCSS %}
|
{% block customCSS %}
|
||||||
<link rel="stylesheet" type="text/css" href="{% static 'css/app_admin/days_off.css' %}"/>
|
<link rel="stylesheet"
|
||||||
|
type="text/css"
|
||||||
|
href="{% static 'css/app_admin/days_off.css' %}" />
|
||||||
<!-- jQuery UI CSS -->
|
<!-- jQuery UI CSS -->
|
||||||
<link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/jqueryui/1.13.2/themes/base/jquery-ui.css"
|
<link rel="stylesheet"
|
||||||
|
href="https://cdnjs.cloudflare.com/ajax/libs/jqueryui/1.13.2/themes/base/jquery-ui.css"
|
||||||
integrity="sha512-lCk0aEL6CvAGQvaZ47hoq1v/hNsunE8wD4xmmBelkJjg51DauW6uVdaWEJlwgAE6PxcY7/SThs1T4+IMwwpN7w=="
|
integrity="sha512-lCk0aEL6CvAGQvaZ47hoq1v/hNsunE8wD4xmmBelkJjg51DauW6uVdaWEJlwgAE6PxcY7/SThs1T4+IMwwpN7w=="
|
||||||
crossorigin="anonymous" referrerpolicy="no-referrer"/>
|
crossorigin="anonymous"
|
||||||
|
referrerpolicy="no-referrer" />
|
||||||
{% endblock %}
|
{% endblock %}
|
||||||
|
|
||||||
{% block body %}
|
{% block body %}
|
||||||
<section class="content content-wrapper">
|
<section class="content content-wrapper">
|
||||||
<div class="days-off-form-wrapper">
|
<div class="days-off-form-wrapper">
|
||||||
<div class="do-form-content">
|
<div class="do-form-content">
|
||||||
<h2>{% trans "Manage Days Off" %}</h2>
|
<h2>{% trans "Manage Days Off" %}</h2>
|
||||||
|
|
||||||
<form method="post" action="">
|
<form method="post" action="">
|
||||||
{% csrf_token %}
|
{% csrf_token %}
|
||||||
<!-- Staff Member -->
|
<!-- Staff Member -->
|
||||||
@ -26,60 +27,66 @@
|
|||||||
});
|
});
|
||||||
</script>
|
</script>
|
||||||
{% endif %}
|
{% endif %}
|
||||||
|
|
||||||
{% if days_off_form.staff_member %}
|
{% if days_off_form.staff_member %}
|
||||||
<div class="form-group">
|
<div class="form-group">
|
||||||
<label for="{{ days_off_form.staff_member.id_for_label }}">{% trans 'Staff Member' %}:</label>
|
<label for="{{ days_off_form.staff_member.id_for_label }}">{% trans 'Staff Member' %}:</label>
|
||||||
{{ days_off_form.staff_member }}
|
{{ days_off_form.staff_member }}
|
||||||
</div>
|
</div>
|
||||||
{% endif %}
|
{% endif %}
|
||||||
|
|
||||||
<!-- Start Date Display (read-only) -->
|
<!-- Start Date Display (read-only) -->
|
||||||
<div class="form-group">
|
<div class="form-group">
|
||||||
<label for="{{ day_off_form.start_date.id_for_label }}_display">{% trans 'Start date' %}:</label>
|
<label for="{{ day_off_form.start_date.id_for_label }}_display">{% trans 'Start date' %}:</label>
|
||||||
<input type="text" id="{{ day_off_form.start_date.id_for_label }}_display"
|
<input type="text"
|
||||||
|
id="{{ day_off_form.start_date.id_for_label }}_display"
|
||||||
class="datepicker-display"
|
class="datepicker-display"
|
||||||
value="{{ day_off_form.start_date.value }}" readonly>
|
value="{{ day_off_form.start_date.value }}"
|
||||||
|
readonly>
|
||||||
<!-- Actual value to be submitted -->
|
<!-- Actual value to be submitted -->
|
||||||
<input type="hidden" id="{{ day_off_form.start_date.id_for_label }}"
|
<input type="hidden"
|
||||||
name="{{ day_off_form.start_date.name }}" class="datepicker-actual"
|
id="{{ day_off_form.start_date.id_for_label }}"
|
||||||
|
name="{{ day_off_form.start_date.name }}"
|
||||||
|
class="datepicker-actual"
|
||||||
value="{{ day_off_form.start_date.value }}">
|
value="{{ day_off_form.start_date.value }}">
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<!-- End Date Display (read-only) -->
|
<!-- End Date Display (read-only) -->
|
||||||
<div class="form-group">
|
<div class="form-group">
|
||||||
<label for="{{ day_off_form.end_date.id_for_label }}_display">{% trans 'End date' %}:</label>
|
<label for="{{ day_off_form.end_date.id_for_label }}_display">{% trans 'End date' %}:</label>
|
||||||
<input type="text" id="{{ day_off_form.end_date.id_for_label }}_display"
|
<input type="text"
|
||||||
|
id="{{ day_off_form.end_date.id_for_label }}_display"
|
||||||
class="datepicker-display"
|
class="datepicker-display"
|
||||||
value="{{ day_off_form.end_date.value }}" readonly>
|
value="{{ day_off_form.end_date.value }}"
|
||||||
|
readonly>
|
||||||
<!-- Actual value to be submitted -->
|
<!-- Actual value to be submitted -->
|
||||||
<input type="hidden" id="{{ day_off_form.end_date.id_for_label }}"
|
<input type="hidden"
|
||||||
name="{{ day_off_form.end_date.name }}" class="datepicker-actual"
|
id="{{ day_off_form.end_date.id_for_label }}"
|
||||||
|
name="{{ day_off_form.end_date.name }}"
|
||||||
|
class="datepicker-actual"
|
||||||
value="{{ day_off_form.end_date.value }}">
|
value="{{ day_off_form.end_date.value }}">
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
|
||||||
<div class="form-group">
|
<div class="form-group">
|
||||||
<label for="{{ day_off_form.description.id_for_label }}">{% trans 'Description' %}:</label>
|
<label for="{{ day_off_form.description.id_for_label }}">{% trans 'Description' %}:</label>
|
||||||
<input type="text" id="{{ day_off_form.description.id_for_label }}"
|
<input type="text"
|
||||||
name="{{ day_off_form.description.name }}" value="{{ day_off_form.description.value }}">
|
id="{{ day_off_form.description.id_for_label }}"
|
||||||
|
name="{{ day_off_form.description.name }}"
|
||||||
|
value="{{ day_off_form.description.value }}">
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
|
||||||
<button type="submit" class="btn btn-phoenix-primary">{% trans "Submit" %}</button>
|
<button type="submit" class="btn btn-phoenix-primary">{% trans "Submit" %}</button>
|
||||||
</form>
|
</form>
|
||||||
<div class="row-form-errors" style="margin: 10px 0">
|
<div class="row-form-errors" style="margin: 10px 0">
|
||||||
{% if days_off_form.errors %}
|
{% if days_off_form.errors %}<div class="alert alert-danger">{{ days_off_form.errors }}</div>{% endif %}
|
||||||
<div class="alert alert-danger">
|
|
||||||
{{ days_off_form.errors }}
|
|
||||||
</div>
|
|
||||||
{% endif %}
|
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class="messages" style="margin: 20px 0">
|
<div class="messages" style="margin: 20px 0">
|
||||||
{% if messages %}
|
{% if messages %}
|
||||||
{% for message in messages %}
|
{% for message in messages %}
|
||||||
<div class="alert alert-dismissible {% if message.tags %}alert-{% if message.level == DEFAULT_MESSAGE_LEVELS.ERROR %}danger{% else %}{{ message.tags }}{% endif %}{% endif %}"
|
<div class="alert alert-dismissible
|
||||||
|
{% if message.tags %}
|
||||||
|
alert-
|
||||||
|
{% if message.level == DEFAULT_MESSAGE_LEVELS.ERROR %}
|
||||||
|
danger
|
||||||
|
{% else %}
|
||||||
|
{{ message.tags }}
|
||||||
|
{% endif %}
|
||||||
|
{% endif %}"
|
||||||
role="alert">{{ message }}</div>
|
role="alert">{{ message }}</div>
|
||||||
{% endfor %}
|
{% endfor %}
|
||||||
{% endif %}
|
{% endif %}
|
||||||
@ -89,15 +96,16 @@
|
|||||||
</div>
|
</div>
|
||||||
</section>
|
</section>
|
||||||
{% endblock %}
|
{% endblock %}
|
||||||
|
|
||||||
{% block customJS %}
|
{% block customJS %}
|
||||||
<!-- JS -->
|
<!-- JS -->
|
||||||
<script src="https://cdnjs.cloudflare.com/ajax/libs/jquery/3.7.1/jquery.min.js"
|
<script src="https://cdnjs.cloudflare.com/ajax/libs/jquery/3.7.1/jquery.min.js"
|
||||||
integrity="sha512-v2CJ7UaYy4JwqLDIrZUI/4hqeoQieOmAZNXBeQyjo21dadnwR+8ZaIJVT8EE2iyI61OV8e6M8PP2/4hpQINQ/g=="
|
integrity="sha512-v2CJ7UaYy4JwqLDIrZUI/4hqeoQieOmAZNXBeQyjo21dadnwR+8ZaIJVT8EE2iyI61OV8e6M8PP2/4hpQINQ/g=="
|
||||||
crossorigin="anonymous" referrerpolicy="no-referrer"></script>
|
crossorigin="anonymous"
|
||||||
|
referrerpolicy="no-referrer"></script>
|
||||||
<script src="https://cdnjs.cloudflare.com/ajax/libs/jqueryui/1.13.2/jquery-ui.js"
|
<script src="https://cdnjs.cloudflare.com/ajax/libs/jqueryui/1.13.2/jquery-ui.js"
|
||||||
integrity="sha512-ynDTbjF5rUHsWBjz7nsljrrSWqLTPJaORzSe5aGCFxOigRZRmwM05y+kuCtxaoCSzVGB1Ky3XeRZsDhbSLdzXQ=="
|
integrity="sha512-ynDTbjF5rUHsWBjz7nsljrrSWqLTPJaORzSe5aGCFxOigRZRmwM05y+kuCtxaoCSzVGB1Ky3XeRZsDhbSLdzXQ=="
|
||||||
crossorigin="anonymous" referrerpolicy="no-referrer"></script>
|
crossorigin="anonymous"
|
||||||
|
referrerpolicy="no-referrer"></script>
|
||||||
<script type="text/javascript">
|
<script type="text/javascript">
|
||||||
$(document).ready(function () {
|
$(document).ready(function () {
|
||||||
$.datepicker._defaults.monthNamesShort = ["Jan", "Feb", "Mar", "Apr", "May", "Jun", "Jul", "Aug", "Sept", "Oct", "Nov", "Dec"];
|
$.datepicker._defaults.monthNamesShort = ["Jan", "Feb", "Mar", "Apr", "May", "Jun", "Jul", "Aug", "Sept", "Oct", "Nov", "Dec"];
|
||||||
|
|||||||
@ -1,12 +1,8 @@
|
|||||||
{% extends BASE_TEMPLATE %}
|
{% extends BASE_TEMPLATE %}
|
||||||
{% load crispy_forms_filters custom_filters%}
|
{% load crispy_forms_filters custom_filters %}
|
||||||
{% load i18n %}
|
{% load i18n %}
|
||||||
{% load static %}
|
{% load static %}
|
||||||
|
{% block customCSS %}{% endblock %}
|
||||||
{% block customCSS %}
|
|
||||||
|
|
||||||
{% endblock %}
|
|
||||||
|
|
||||||
{% block content %}
|
{% block content %}
|
||||||
{% translate "Confirm Deletion" as modal_title %}
|
{% translate "Confirm Deletion" as modal_title %}
|
||||||
{% translate "Delete" as delete_btn_modal %}
|
{% translate "Delete" as delete_btn_modal %}
|
||||||
@ -15,98 +11,65 @@
|
|||||||
<h3 class="mb-3">{{ page_title }}</h3>
|
<h3 class="mb-3">{{ page_title }}</h3>
|
||||||
<form class="form" method="post" enctype="multipart/form-data">
|
<form class="form" method="post" enctype="multipart/form-data">
|
||||||
{% csrf_token %}
|
{% csrf_token %}
|
||||||
|
<!-- Name Field -->
|
||||||
<!-- Name Field -->
|
|
||||||
<div class="form-floating mb-3">
|
<div class="form-floating mb-3">
|
||||||
{{ form.name|add_class:"form-control form-control-sm" }}
|
{{ form.name|add_class:"form-control form-control-sm" }}
|
||||||
<label for="{{ form.name.id_for_label }}">{{ _("Name") }}</label>
|
<label for="{{ form.name.id_for_label }}">{{ _("Name") }}</label>
|
||||||
{% if form.name.errors %}
|
{% if form.name.errors %}<div class="alert alert-danger mt-2">{{ form.name.errors }}</div>{% endif %}
|
||||||
<div class="alert alert-danger mt-2">
|
|
||||||
{{ form.name.errors }}
|
|
||||||
</div>
|
|
||||||
{% endif %}
|
|
||||||
</div>
|
</div>
|
||||||
|
<!-- Description Field -->
|
||||||
<!-- Description Field -->
|
|
||||||
<div class="form-floating mb-3">
|
<div class="form-floating mb-3">
|
||||||
{{ form.description|add_class:"form-control form-control-sm" }}
|
{{ form.description|add_class:"form-control form-control-sm" }}
|
||||||
<label for="{{ form.description.id_for_label }}">{{ _("Description") }}</label>
|
<label for="{{ form.description.id_for_label }}">{{ _("Description") }}</label>
|
||||||
{% if form.description.errors %}
|
{% if form.description.errors %}<div class="alert alert-danger mt-2">{{ form.description.errors }}</div>{% endif %}
|
||||||
<div class="alert alert-danger mt-2">
|
|
||||||
{{ form.description.errors }}
|
|
||||||
</div>
|
|
||||||
{% endif %}
|
|
||||||
</div>
|
</div>
|
||||||
|
<!-- Duration Field -->
|
||||||
<!-- Duration Field -->
|
|
||||||
<div class="form-floating mb-3">
|
<div class="form-floating mb-3">
|
||||||
{{ form.duration|add_class:"form-control form-control-sm" }}
|
{{ form.duration|add_class:"form-control form-control-sm" }}
|
||||||
<label for="{{ form.duration.id_for_label }}">{{ _("Duration") }}</label>
|
<label for="{{ form.duration.id_for_label }}">{{ _("Duration") }}</label>
|
||||||
{% if form.duration.errors %}
|
{% if form.duration.errors %}<div class="alert alert-danger mt-2">{{ form.duration.errors }}</div>{% endif %}
|
||||||
<div class="alert alert-danger mt-2">
|
|
||||||
{{ form.duration.errors }}
|
|
||||||
</div>
|
|
||||||
{% endif %}
|
|
||||||
</div>
|
</div>
|
||||||
|
<!-- Price Field -->
|
||||||
<!-- Price Field -->
|
|
||||||
<div class="form-floating mb-3">
|
<div class="form-floating mb-3">
|
||||||
{{ form.price|add_class:"form-control form-control-sm" }}
|
{{ form.price|add_class:"form-control form-control-sm" }}
|
||||||
<label for="{{ form.price.id_for_label }}">{{ _("Price") }}</label>
|
<label for="{{ form.price.id_for_label }}">{{ _("Price") }}</label>
|
||||||
{% if form.price.errors %}
|
{% if form.price.errors %}<div class="alert alert-danger mt-2">{{ form.price.errors }}</div>{% endif %}
|
||||||
<div class="alert alert-danger mt-2">
|
|
||||||
{{ form.price.errors }}
|
|
||||||
</div>
|
|
||||||
{% endif %}
|
|
||||||
</div>
|
</div>
|
||||||
|
<!-- Down Payment Field -->
|
||||||
<!-- Down Payment Field -->
|
|
||||||
<div class="form-floating mb-3">
|
<div class="form-floating mb-3">
|
||||||
{{ form.down_payment|add_class:"form-control form-control-sm" }}
|
{{ form.down_payment|add_class:"form-control form-control-sm" }}
|
||||||
<label for="{{ form.down_payment.id_for_label }}">{{ _("Down Payment")}}</label>
|
<label for="{{ form.down_payment.id_for_label }}">{{ _("Down Payment") }}</label>
|
||||||
{% if form.down_payment.errors %}
|
{% if form.down_payment.errors %}<div class="alert alert-danger mt-2">{{ form.down_payment.errors }}</div>{% endif %}
|
||||||
<div class="alert alert-danger mt-2">
|
|
||||||
{{ form.down_payment.errors }}
|
|
||||||
</div>
|
|
||||||
{% endif %}
|
|
||||||
</div>
|
</div>
|
||||||
|
<!-- Image Field -->
|
||||||
<!-- Image Field -->
|
|
||||||
<div class="mb-3">
|
<div class="mb-3">
|
||||||
<label for="{{ form.image.id_for_label }}" class="form-label">{{ _("Image") }}</label>
|
<label for="{{ form.image.id_for_label }}" class="form-label">{{ _("Image") }}</label>
|
||||||
{{ form.image }}
|
{{ form.image }}
|
||||||
{% if form.image.errors %}
|
{% if form.image.errors %}<div class="alert alert-danger mt-2">{{ form.image.errors }}</div>{% endif %}
|
||||||
<div class="alert alert-danger mt-2">
|
|
||||||
{{ form.image.errors }}
|
|
||||||
</div>
|
|
||||||
{% endif %}
|
|
||||||
</div>
|
</div>
|
||||||
|
<!-- Currency Field -->
|
||||||
<!-- Currency Field -->
|
|
||||||
<div class="form-floating mb-3">
|
<div class="form-floating mb-3">
|
||||||
<select name="currency" id="id_currency" class="form-select form-control-sm" >
|
<select name="currency" id="id_currency" class="form-select form-control-sm">
|
||||||
<option class="icon-saudi_riyal" value="SAR"><span class="icon-saudi_riyal"></span></option>
|
<option class="icon-saudi_riyal" value="SAR">
|
||||||
|
<span class="icon-saudi_riyal"></span>
|
||||||
|
</option>
|
||||||
</select>
|
</select>
|
||||||
<label for="id_currency"> <span class="icon-saudi_riyal"></span></label>
|
<label for="id_currency">
|
||||||
{% if form.currency.errors %}
|
<span class="icon-saudi_riyal"></span>
|
||||||
<div class="alert alert-danger mt-2">
|
</label>
|
||||||
{{ form.currency.errors }}
|
{% if form.currency.errors %}<div class="alert alert-danger mt-2">{{ form.currency.errors }}</div>{% endif %}
|
||||||
</div>
|
|
||||||
{% endif %}
|
|
||||||
</div>
|
</div>
|
||||||
|
<!-- Background Color Field -->
|
||||||
<!-- Background Color Field -->
|
|
||||||
<div class="form-floating mb-3">
|
<div class="form-floating mb-3">
|
||||||
<input type="color" value="#000000" id="{{ form.background_color.id_for_label }}" class="form-control form-control-sm" >
|
<input type="color"
|
||||||
<label for="{{ form.background_color.id_for_label }}">{{ _("Background Color")}}</label>
|
value="#000000"
|
||||||
|
id="{{ form.background_color.id_for_label }}"
|
||||||
|
class="form-control form-control-sm">
|
||||||
|
<label for="{{ form.background_color.id_for_label }}">{{ _("Background Color") }}</label>
|
||||||
{% if form.background_color.errors %}
|
{% if form.background_color.errors %}
|
||||||
<div class="alert alert-danger mt-2">
|
<div class="alert alert-danger mt-2">{{ form.background_color.errors }}</div>
|
||||||
{{ form.background_color.errors }}
|
|
||||||
</div>
|
|
||||||
{% endif %}
|
{% endif %}
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
|
||||||
{% if btn_text %}
|
{% if btn_text %}
|
||||||
<button type="submit" class="btn btn-sm btn-phoenix-primary">{{ btn_text }}</button>
|
<button type="submit" class="btn btn-sm btn-phoenix-primary">{{ btn_text }}</button>
|
||||||
{% else %}
|
{% else %}
|
||||||
@ -126,14 +89,11 @@
|
|||||||
{% endif %}
|
{% endif %}
|
||||||
{% endif %}
|
{% endif %}
|
||||||
</form>
|
</form>
|
||||||
|
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
{% include 'modal/confirm_modal.html' %}
|
{% include 'modal/confirm_modal.html' %}
|
||||||
{% endblock %}
|
{% endblock %}
|
||||||
|
|
||||||
{% block customJS %}
|
{% block customJS %}
|
||||||
<script src="{% static 'js/modal/show_modal.js' %}"></script>
|
<script src="{% static 'js/modal/show_modal.js' %}"></script>
|
||||||
|
|
||||||
<script src="{% static 'js/js-utils.js' %}"></script>
|
<script src="{% static 'js/js-utils.js' %}"></script>
|
||||||
{% endblock %}
|
{% endblock %}
|
||||||
|
|||||||
@ -1,11 +1,11 @@
|
|||||||
{% extends BASE_TEMPLATE %}
|
{% extends BASE_TEMPLATE %}
|
||||||
{% load i18n %}
|
{% load i18n %}
|
||||||
{% load static %}
|
{% load static %}
|
||||||
|
|
||||||
{% block customCSS %}
|
{% block customCSS %}
|
||||||
<link rel="stylesheet" type="text/css" href="{% static 'css/app_admin/staff_member.css' %}"/>
|
<link rel="stylesheet"
|
||||||
|
type="text/css"
|
||||||
|
href="{% static 'css/app_admin/staff_member.css' %}" />
|
||||||
{% endblock %}
|
{% endblock %}
|
||||||
|
|
||||||
{% block body %}
|
{% block body %}
|
||||||
<section class="main-container">
|
<section class="main-container">
|
||||||
<div class="container-fluid">
|
<div class="container-fluid">
|
||||||
@ -25,54 +25,55 @@
|
|||||||
</small>
|
</small>
|
||||||
</div>
|
</div>
|
||||||
{% endif %}
|
{% endif %}
|
||||||
|
|
||||||
<div class="form-group">
|
<div class="form-group">
|
||||||
{{ form.services_offered.label_tag }}
|
{{ form.services_offered.label_tag }}
|
||||||
{{ form.services_offered.errors }}
|
{{ form.services_offered.errors }}
|
||||||
{{ form.services_offered }}
|
{{ form.services_offered }}
|
||||||
<br><small>{% trans 'Hold down “Control”, or “Command” on a Mac, to select more than one.' %}</small>
|
<br>
|
||||||
|
<small>{% trans 'Hold down “Control”, or “Command” on a Mac, to select more than one.' %}</small>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class="form-group">
|
<div class="form-group">
|
||||||
{{ form.slot_duration.label_tag }}
|
{{ form.slot_duration.label_tag }}
|
||||||
{{ form.slot_duration }}
|
{{ form.slot_duration }}
|
||||||
<small>{{ form.slot_duration.help_text }}</small>
|
<small>{{ form.slot_duration.help_text }}</small>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class="form-group">
|
<div class="form-group">
|
||||||
{{ form.lead_time.label_tag }}
|
{{ form.lead_time.label_tag }}
|
||||||
{{ form.lead_time }}
|
{{ form.lead_time }}
|
||||||
<small>{{ form.lead_time.help_text }}</small>
|
<small>{{ form.lead_time.help_text }}</small>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class="form-group">
|
<div class="form-group">
|
||||||
{{ form.finish_time.label_tag }}
|
{{ form.finish_time.label_tag }}
|
||||||
{{ form.finish_time }}
|
{{ form.finish_time }}
|
||||||
<small>{{ form.finish_time.help_text }}</small>
|
<small>{{ form.finish_time.help_text }}</small>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class="form-group">
|
<div class="form-group">
|
||||||
{{ form.appointment_buffer_time.label_tag }}
|
{{ form.appointment_buffer_time.label_tag }}
|
||||||
{{ form.appointment_buffer_time }}
|
{{ form.appointment_buffer_time }}
|
||||||
<small>{{ form.appointment_buffer_time.help_text }}</small>
|
<small>{{ form.appointment_buffer_time.help_text }}</small>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class="form-check">
|
<div class="form-check">
|
||||||
{{ form.work_on_saturday }}
|
{{ form.work_on_saturday }}
|
||||||
{{ form.work_on_saturday.label_tag }}
|
{{ form.work_on_saturday.label_tag }}
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class="form-check">
|
<div class="form-check">
|
||||||
{{ form.work_on_sunday }}
|
{{ form.work_on_sunday }}
|
||||||
{{ form.work_on_sunday.label_tag }}
|
{{ form.work_on_sunday.label_tag }}
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<button type="submit" class="btn btn-phoenix-primary">{% trans 'Save' %}</button>
|
<button type="submit" class="btn btn-phoenix-primary">{% trans 'Save' %}</button>
|
||||||
</form>
|
</form>
|
||||||
<div class="messages" style="margin: 20px 0">
|
<div class="messages" style="margin: 20px 0">
|
||||||
{% if messages %}
|
{% if messages %}
|
||||||
{% for message in messages %}
|
{% for message in messages %}
|
||||||
<div class="alert alert-dismissible {% if message.tags %}alert-{% if message.level == DEFAULT_MESSAGE_LEVELS.ERROR %}danger{% else %}{{ message.tags }}{% endif %}{% endif %}"
|
<div class="alert alert-dismissible
|
||||||
|
{% if message.tags %}
|
||||||
|
alert-
|
||||||
|
{% if message.level == DEFAULT_MESSAGE_LEVELS.ERROR %}
|
||||||
|
danger
|
||||||
|
{% else %}
|
||||||
|
{{ message.tags }}
|
||||||
|
{% endif %}
|
||||||
|
{% endif %}"
|
||||||
role="alert">{{ message }}</div>
|
role="alert">{{ message }}</div>
|
||||||
{% endfor %}
|
{% endfor %}
|
||||||
{% endif %}
|
{% endif %}
|
||||||
@ -81,7 +82,6 @@
|
|||||||
</div>
|
</div>
|
||||||
</section>
|
</section>
|
||||||
{% endblock %}
|
{% endblock %}
|
||||||
|
|
||||||
{% block customJS %}
|
{% block customJS %}
|
||||||
<script src="{% static 'js/js-utils.js' %}"></script>
|
<script src="{% static 'js/js-utils.js' %}"></script>
|
||||||
{% endblock %}
|
{% endblock %}
|
||||||
|
|||||||
@ -1,40 +1,39 @@
|
|||||||
{% extends BASE_TEMPLATE %}
|
{% extends BASE_TEMPLATE %}
|
||||||
{% load i18n %}
|
{% load i18n %}
|
||||||
{% load static %}
|
{% load static %}
|
||||||
|
{% block customCSS %}{% endblock %}
|
||||||
{% block customCSS %}
|
|
||||||
{% endblock %}
|
|
||||||
|
|
||||||
{% block content %}
|
{% block content %}
|
||||||
<div class="row">
|
<div class="row">
|
||||||
|
|
||||||
<div class="col-6">
|
<div class="col-6">
|
||||||
<h3 class="mb-3">{% trans 'Staff Personal Information' %}</h3>
|
<h3 class="mb-3">{% trans 'Staff Personal Information' %}</h3>
|
||||||
<form id="updatePersonalInfoForm" method="post" action="">
|
<form id="updatePersonalInfoForm" method="post" action="">
|
||||||
{% csrf_token %}
|
{% csrf_token %}
|
||||||
|
|
||||||
<div class="form-floating mb-3">
|
<div class="form-floating mb-3">
|
||||||
{{ form.first_name }}
|
{{ form.first_name }}
|
||||||
<label for="first_name">{{ _("First Name") }}</label>
|
<label for="first_name">{{ _("First Name") }}</label>
|
||||||
|
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class="form-floating mb-3">
|
<div class="form-floating mb-3">
|
||||||
{{ form.last_name }}
|
{{ form.last_name }}
|
||||||
<label for="last_name">{{ _("Last Name") }}</label>
|
<label for="last_name">{{ _("Last Name") }}</label>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class="form-floating mb-3">
|
<div class="form-floating mb-3">
|
||||||
{{ form.email }}
|
{{ form.email }}
|
||||||
<label for="email">{{ _("Email") }}</label>
|
<label for="email">{{ _("Email") }}</label>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<button type="submit" class="btn btn-phoenix-primary">{{ btn_text }}</button>
|
<button type="submit" class="btn btn-phoenix-primary">{{ btn_text }}</button>
|
||||||
</form>
|
</form>
|
||||||
<div class="messages" style="margin: 20px 0">
|
<div class="messages" style="margin: 20px 0">
|
||||||
{% if messages %}
|
{% if messages %}
|
||||||
{% for message in messages %}
|
{% for message in messages %}
|
||||||
<div class="alert alert-dismissible {% if message.tags %}alert-{% if message.level == DEFAULT_MESSAGE_LEVELS.ERROR %}danger{% else %}{{ message.tags }}{% endif %}{% endif %}"
|
<div class="alert alert-dismissible
|
||||||
|
{% if message.tags %}
|
||||||
|
alert-
|
||||||
|
{% if message.level == DEFAULT_MESSAGE_LEVELS.ERROR %}
|
||||||
|
danger
|
||||||
|
{% else %}
|
||||||
|
{{ message.tags }}
|
||||||
|
{% endif %}
|
||||||
|
{% endif %}"
|
||||||
role="alert">{{ message }}</div>
|
role="alert">{{ message }}</div>
|
||||||
{% endfor %}
|
{% endfor %}
|
||||||
{% endif %}
|
{% endif %}
|
||||||
@ -42,7 +41,6 @@
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
{% endblock %}
|
{% endblock %}
|
||||||
|
|
||||||
{% block customJS %}
|
{% block customJS %}
|
||||||
<script src="{% static 'js/js-utils.js' %}"></script>
|
<script src="{% static 'js/js-utils.js' %}"></script>
|
||||||
{% endblock %}
|
{% endblock %}
|
||||||
|
|||||||
@ -2,70 +2,107 @@
|
|||||||
{% load i18n %}
|
{% load i18n %}
|
||||||
{% load static %}
|
{% load static %}
|
||||||
{% load custom_filters %}
|
{% load custom_filters %}
|
||||||
|
{% block customCSS %}<!-- additional CSS -->{% endblock %}
|
||||||
{% block customCSS %}
|
|
||||||
<!-- additional CSS -->
|
|
||||||
|
|
||||||
{% endblock %}
|
|
||||||
|
|
||||||
{% block content %}
|
{% block content %}
|
||||||
<div class="row">
|
<div class="row">
|
||||||
<div class="col-6">
|
<div class="col-6">
|
||||||
|
|
||||||
<h3>{% trans "Manage Working Hours" %}</h3>
|
<h3>{% trans "Manage Working Hours" %}</h3>
|
||||||
<form class="form" method="post" action="" id="workingHoursForm"
|
<form class="form"
|
||||||
data-action="{% if working_hours_instance %}update{% else %}create{% endif %}"
|
method="post"
|
||||||
|
action=""
|
||||||
|
id="workingHoursForm"
|
||||||
|
data-action="{% if working_hours_instance %}
|
||||||
|
update
|
||||||
|
{% else %}
|
||||||
|
create
|
||||||
|
{% endif %}"
|
||||||
data-working-hours-id="
|
data-working-hours-id="
|
||||||
{% if working_hours_instance %}{{ working_hours_instance.id }}{% else %}0{% endif %}"
|
{% if working_hours_instance %}
|
||||||
data-staff-user-id="{% if staff_user_id %}{{ staff_user_id }}{% else %}0{% endif %}">
|
{{ working_hours_instance.id }}
|
||||||
|
{% else %}
|
||||||
|
0
|
||||||
|
{% endif %}"
|
||||||
|
data-staff-user-id="{% if staff_user_id %}
|
||||||
|
{{ staff_user_id }}
|
||||||
|
{% else %}
|
||||||
|
0
|
||||||
|
{% endif %}">
|
||||||
{% csrf_token %}
|
{% csrf_token %}
|
||||||
|
|
||||||
{% if working_hours_form.staff_member %}
|
{% if working_hours_form.staff_member %}
|
||||||
<div class="form-group mb-3">
|
<div class="form-group mb-3">
|
||||||
<label class="form-label" for="{{ working_hours_form.staff_member.id_for_label }}">{% trans 'Staff Member' %}:</label>
|
<label class="form-label"
|
||||||
|
for="{{ working_hours_form.staff_member.id_for_label }}">
|
||||||
|
{% trans 'Staff Member' %}:
|
||||||
|
</label>
|
||||||
{{ working_hours_form.staff_member }}
|
{{ working_hours_form.staff_member }}
|
||||||
</div>
|
</div>
|
||||||
{% endif %}
|
{% endif %}
|
||||||
|
|
||||||
<div class="form-group mb-3">
|
<div class="form-group mb-3">
|
||||||
<label class="form-label" for="{{ working_hours_form.day_of_week.id_for_label }}">{% trans 'Day of Week' %}:</label>
|
<label class="form-label"
|
||||||
|
for="{{ working_hours_form.day_of_week.id_for_label }}">{% trans 'Day of Week' %}:</label>
|
||||||
{{ working_hours_form.day_of_week|add_class:"form-select form-select-sm" }}
|
{{ working_hours_form.day_of_week|add_class:"form-select form-select-sm" }}
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class="form-group mb-3">
|
<div class="form-group mb-3">
|
||||||
<label class="form-label" for="{{ working_hours_form.start_time.id_for_label }}">{% trans 'Start time' %}:</label>
|
<label class="form-label"
|
||||||
<div class="input-group time24hr" id="start-timepicker" data-target-input="nearest">
|
for="{{ working_hours_form.start_time.id_for_label }}">{% trans 'Start time' %}:</label>
|
||||||
<input type="text" class="form-control form-control-sm datetimepicker-input" data-toggle="datetimepicker"
|
<div class="input-group time24hr"
|
||||||
data-target="#start-timepicker" name="{{ working_hours_form.start_time.name }}"
|
id="start-timepicker"
|
||||||
|
data-target-input="nearest">
|
||||||
|
<input type="text"
|
||||||
|
class="form-control form-control-sm datetimepicker-input"
|
||||||
|
data-toggle="datetimepicker"
|
||||||
|
data-target="#start-timepicker"
|
||||||
|
name="{{ working_hours_form.start_time.name }}"
|
||||||
value="{{ working_hours_form.start_time.value|default:'09:00 AM' }}"
|
value="{{ working_hours_form.start_time.value|default:'09:00 AM' }}"
|
||||||
id="{{ working_hours_form.start_time.id_for_label }}">
|
id="{{ working_hours_form.start_time.id_for_label }}">
|
||||||
<div class="input-group-text" data-toggle="datetimepicker" data-target="#start-timepicker"><i class="far fa-clock"></i></div>
|
<div class="input-group-text"
|
||||||
|
data-toggle="datetimepicker"
|
||||||
|
data-target="#start-timepicker">
|
||||||
|
<i class="far fa-clock"></i>
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class="form-group mb-3">
|
<div class="form-group mb-3">
|
||||||
<label class="form-label" for="{{ working_hours_form.end_time.id_for_label }}">{% trans 'End time' %}:</label>
|
<label class="form-label"
|
||||||
<div class="input-group date" id="end-timepicker" data-target-input="nearest">
|
for="{{ working_hours_form.end_time.id_for_label }}">{% trans 'End time' %}:</label>
|
||||||
<input type="text" class="form-control form-control-sm datetimepicker-input" data-toggle="datetimepicker"
|
<div class="input-group date"
|
||||||
data-target="#end-timepicker" name="{{ working_hours_form.end_time.name }}"
|
id="end-timepicker"
|
||||||
|
data-target-input="nearest">
|
||||||
|
<input type="text"
|
||||||
|
class="form-control form-control-sm datetimepicker-input"
|
||||||
|
data-toggle="datetimepicker"
|
||||||
|
data-target="#end-timepicker"
|
||||||
|
name="{{ working_hours_form.end_time.name }}"
|
||||||
value="{{ working_hours_form.end_time.value|default:'05:00 PM' }}"
|
value="{{ working_hours_form.end_time.value|default:'05:00 PM' }}"
|
||||||
id="{{ working_hours_form.end_time.id_for_label }}">
|
id="{{ working_hours_form.end_time.id_for_label }}">
|
||||||
<div class="input-group-text" data-toggle="datetimepicker" data-target="#end-timepicker"><i class="far fa-clock"></i></div>
|
<div class="input-group-text"
|
||||||
|
data-toggle="datetimepicker"
|
||||||
|
data-target="#end-timepicker">
|
||||||
|
<i class="far fa-clock"></i>
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<button type="submit" class="btn btn-sm btn-phoenix-primary">{{ button_text }}</button>
|
<button type="submit" class="btn btn-sm btn-phoenix-primary">{{ button_text }}</button>
|
||||||
<input type="hidden" id="addWorkingHoursUrl"
|
<input type="hidden"
|
||||||
|
id="addWorkingHoursUrl"
|
||||||
value="{% url 'appointment:add_working_hours_id' staff_user_id|default:user.id %}">
|
value="{% url 'appointment:add_working_hours_id' staff_user_id|default:user.id %}">
|
||||||
<input type="hidden" id="updateWorkingHoursUrl"
|
<input type="hidden"
|
||||||
|
id="updateWorkingHoursUrl"
|
||||||
value="{% url 'appointment:update_working_hours_id' working_hours_id|default:0 staff_user_id|default:user.id %}">
|
value="{% url 'appointment:update_working_hours_id' working_hours_id|default:0 staff_user_id|default:user.id %}">
|
||||||
|
|
||||||
</form>
|
</form>
|
||||||
{% include 'modal/error_modal.html' %}
|
{% include 'modal/error_modal.html' %}
|
||||||
<div class="messages" style="margin: 20px 0">
|
<div class="messages" style="margin: 20px 0">
|
||||||
{% if messages %}
|
{% if messages %}
|
||||||
{% for message in messages %}
|
{% for message in messages %}
|
||||||
<div class="alert alert-dismissible {% if message.tags %}alert-{% if message.level == DEFAULT_MESSAGE_LEVELS.ERROR %}danger{% else %}{{ message.tags }}{% endif %}{% endif %}"
|
<div class="alert alert-dismissible
|
||||||
|
{% if message.tags %}
|
||||||
|
alert-
|
||||||
|
{% if message.level == DEFAULT_MESSAGE_LEVELS.ERROR %}
|
||||||
|
danger
|
||||||
|
{% else %}
|
||||||
|
{{ message.tags }}
|
||||||
|
{% endif %}
|
||||||
|
{% endif %}"
|
||||||
role="alert">{{ message }}</div>
|
role="alert">{{ message }}</div>
|
||||||
{% endfor %}
|
{% endfor %}
|
||||||
{% endif %}
|
{% endif %}
|
||||||
@ -75,19 +112,22 @@
|
|||||||
{% endblock %}
|
{% endblock %}
|
||||||
{% block customJS %}
|
{% block customJS %}
|
||||||
<!-- JS -->
|
<!-- JS -->
|
||||||
|
|
||||||
<script src="https://cdnjs.cloudflare.com/ajax/libs/popper.js/2.11.8/umd/popper.min.js"
|
<script src="https://cdnjs.cloudflare.com/ajax/libs/popper.js/2.11.8/umd/popper.min.js"
|
||||||
integrity="sha512-TPh2Oxlg1zp+kz3nFA0C5vVC6leG/6mm1z9+mA81MI5eaUVqasPLO8Cuk4gMF4gUfP5etR73rgU/8PNMsSesoQ=="
|
integrity="sha512-TPh2Oxlg1zp+kz3nFA0C5vVC6leG/6mm1z9+mA81MI5eaUVqasPLO8Cuk4gMF4gUfP5etR73rgU/8PNMsSesoQ=="
|
||||||
crossorigin="anonymous" referrerpolicy="no-referrer"></script>
|
crossorigin="anonymous"
|
||||||
|
referrerpolicy="no-referrer"></script>
|
||||||
<script src="https://cdnjs.cloudflare.com/ajax/libs/bootstrap/4.6.2/js/bootstrap.min.js"
|
<script src="https://cdnjs.cloudflare.com/ajax/libs/bootstrap/4.6.2/js/bootstrap.min.js"
|
||||||
integrity="sha512-7rusk8kGPFynZWu26OKbTeI+QPoYchtxsmPeBqkHIEXJxeun4yJ4ISYe7C6sz9wdxeE1Gk3VxsIWgCZTc+vX3g=="
|
integrity="sha512-7rusk8kGPFynZWu26OKbTeI+QPoYchtxsmPeBqkHIEXJxeun4yJ4ISYe7C6sz9wdxeE1Gk3VxsIWgCZTc+vX3g=="
|
||||||
crossorigin="anonymous" referrerpolicy="no-referrer"></script>
|
crossorigin="anonymous"
|
||||||
|
referrerpolicy="no-referrer"></script>
|
||||||
<script src="https://cdnjs.cloudflare.com/ajax/libs/moment.js/2.30.1/moment.js"
|
<script src="https://cdnjs.cloudflare.com/ajax/libs/moment.js/2.30.1/moment.js"
|
||||||
integrity="sha512-3CuraBvy05nIgcoXjVN33mACRyI89ydVHg7y/HMN9wcTVbHeur0SeBzweSd/rxySapO7Tmfu68+JlKkLTnDFNg=="
|
integrity="sha512-3CuraBvy05nIgcoXjVN33mACRyI89ydVHg7y/HMN9wcTVbHeur0SeBzweSd/rxySapO7Tmfu68+JlKkLTnDFNg=="
|
||||||
crossorigin="anonymous" referrerpolicy="no-referrer"></script>
|
crossorigin="anonymous"
|
||||||
|
referrerpolicy="no-referrer"></script>
|
||||||
<script src="https://cdnjs.cloudflare.com/ajax/libs/tempusdominus-bootstrap-4/5.39.0/js/tempusdominus-bootstrap-4.min.js"
|
<script src="https://cdnjs.cloudflare.com/ajax/libs/tempusdominus-bootstrap-4/5.39.0/js/tempusdominus-bootstrap-4.min.js"
|
||||||
integrity="sha512-k6/Bkb8Fxf/c1Tkyl39yJwcOZ1P4cRrJu77p83zJjN2Z55prbFHxPs9vN7q3l3+tSMGPDdoH51AEU8Vgo1cgAA=="
|
integrity="sha512-k6/Bkb8Fxf/c1Tkyl39yJwcOZ1P4cRrJu77p83zJjN2Z55prbFHxPs9vN7q3l3+tSMGPDdoH51AEU8Vgo1cgAA=="
|
||||||
crossorigin="anonymous" referrerpolicy="no-referrer"></script>
|
crossorigin="anonymous"
|
||||||
|
referrerpolicy="no-referrer"></script>
|
||||||
<script type="text/javascript">
|
<script type="text/javascript">
|
||||||
const addWorkingHoursUrl = $('#addWorkingHoursUrl').val();
|
const addWorkingHoursUrl = $('#addWorkingHoursUrl').val();
|
||||||
const updateWorkingHoursUrl = $('#updateWorkingHoursUrl').val();
|
const updateWorkingHoursUrl = $('#updateWorkingHoursUrl').val();
|
||||||
@ -163,4 +203,3 @@
|
|||||||
<script src="{% static 'js/modal/error_modal.js' %}"></script>
|
<script src="{% static 'js/modal/error_modal.js' %}"></script>
|
||||||
<script src="{% static 'js/js-utils.js' %}"></script>
|
<script src="{% static 'js/js-utils.js' %}"></script>
|
||||||
{% endblock %}
|
{% endblock %}
|
||||||
|
|
||||||
|
|||||||
@ -1,7 +1,6 @@
|
|||||||
{% extends 'base.html' %}
|
{% extends 'base.html' %}
|
||||||
{% load i18n %}
|
{% load i18n %}
|
||||||
{% load static %}
|
{% load static %}
|
||||||
|
|
||||||
{% block title %}
|
{% block title %}
|
||||||
{% trans 'Service List' %}
|
{% trans 'Service List' %}
|
||||||
{% endblock %}
|
{% endblock %}
|
||||||
@ -29,7 +28,13 @@
|
|||||||
<tbody>
|
<tbody>
|
||||||
{% for service in services %}
|
{% for service in services %}
|
||||||
<tr>
|
<tr>
|
||||||
<td><img class="rounded-soft" src="{{ service.get_image_url }}" alt="" style="width: 35px; height: 35px;"/></td>
|
<td>
|
||||||
|
<img class="rounded-soft"
|
||||||
|
src="{{ service.get_image_url }}"
|
||||||
|
alt=""
|
||||||
|
style="width: 35px;
|
||||||
|
height: 35px" />
|
||||||
|
</td>
|
||||||
<td>{{ service.name }}</td>
|
<td>{{ service.name }}</td>
|
||||||
<td>{{ service.get_duration }}</td>
|
<td>{{ service.get_duration }}</td>
|
||||||
<td>{{ service.get_price_text }}</td>
|
<td>{{ service.get_price_text }}</td>
|
||||||
@ -64,11 +69,8 @@
|
|||||||
</div>
|
</div>
|
||||||
{% include 'modal/confirm_modal.html' %}
|
{% include 'modal/confirm_modal.html' %}
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
{% endblock %}
|
{% endblock %}
|
||||||
|
|
||||||
{% block customJS %}
|
{% block customJS %}
|
||||||
<script src="{% static 'js/modal/show_modal.js' %}"></script>
|
<script src="{% static 'js/modal/show_modal.js' %}"></script>
|
||||||
<script src="{% static 'js/js-utils.js' %}"></script>
|
<script src="{% static 'js/js-utils.js' %}"></script>
|
||||||
|
|
||||||
{% endblock %}
|
{% endblock %}
|
||||||
|
|||||||
@ -1,18 +1,16 @@
|
|||||||
{% extends BASE_TEMPLATE %}
|
{% extends BASE_TEMPLATE %}
|
||||||
{% load i18n %}
|
{% load i18n %}
|
||||||
{% load static %}
|
{% load static %}
|
||||||
{% block customMetaTag %}
|
{% block customMetaTag %}<meta name="csrf-token" content="{{ csrf_token }}">{% endblock %}
|
||||||
<meta name="csrf-token" content="{{ csrf_token }}">
|
|
||||||
{% endblock %}
|
|
||||||
{% block customCSS %}
|
{% block customCSS %}
|
||||||
<link rel="stylesheet" type="text/css" href="{% static 'css/appt-common.css' %}"/>
|
<link rel="stylesheet"
|
||||||
<link rel="stylesheet" type="text/css" href="{% static 'css/app_admin/admin.css' %}"/>
|
type="text/css"
|
||||||
|
href="{% static 'css/appt-common.css' %}" />
|
||||||
|
<link rel="stylesheet"
|
||||||
|
type="text/css"
|
||||||
|
href="{% static 'css/app_admin/admin.css' %}" />
|
||||||
{% endblock %}
|
{% endblock %}
|
||||||
{% block title %}
|
{% block title %}{{ page_title }}{% endblock %}
|
||||||
{{ page_title }}
|
|
||||||
{% endblock %}
|
|
||||||
|
|
||||||
{% block content %}
|
{% block content %}
|
||||||
<section class="content content-wrapper">
|
<section class="content content-wrapper">
|
||||||
<div class="container">
|
<div class="container">
|
||||||
@ -20,40 +18,49 @@
|
|||||||
<div id="calendar" class="calendarbox"></div>
|
<div id="calendar" class="calendarbox"></div>
|
||||||
<div id="event-list-container" class="event-list-container"></div>
|
<div id="event-list-container" class="event-list-container"></div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
{% include 'modal/event_details_modal.html' %}
|
{% include 'modal/event_details_modal.html' %}
|
||||||
{% include 'modal/error_modal.html' %}
|
{% include 'modal/error_modal.html' %}
|
||||||
|
|
||||||
<div class="messages" style="margin: 20px 0">
|
<div class="messages" style="margin: 20px 0">
|
||||||
{% if messages %}
|
{% if messages %}
|
||||||
{% for message in messages %}
|
{% for message in messages %}
|
||||||
<div class="alert alert-dismissible {% if message.tags %}alert-{% if message.level == DEFAULT_MESSAGE_LEVELS.ERROR %}danger{% else %}{{ message.tags }}{% endif %}{% endif %}"
|
<div class="alert alert-dismissible
|
||||||
|
{% if message.tags %}
|
||||||
|
alert-
|
||||||
|
{% if message.level == DEFAULT_MESSAGE_LEVELS.ERROR %}
|
||||||
|
danger
|
||||||
|
{% else %}
|
||||||
|
{{ message.tags }}
|
||||||
|
{% endif %}
|
||||||
|
{% endif %}"
|
||||||
role="alert">{{ message }}</div>
|
role="alert">{{ message }}</div>
|
||||||
{% endfor %}
|
{% endfor %}
|
||||||
{% endif %}
|
{% endif %}
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</section>
|
</section>
|
||||||
<div id="customContextMenu" style="display: none; position: absolute; z-index: 1000;">
|
<div id="customContextMenu"
|
||||||
|
style="display: none;
|
||||||
<a id="newAppointmentOption" class="btn btn-sm btn-phoenix-success rounded-pill me-1 mb-1" href="#">{{ _("New Appointment")}}</a>
|
position: absolute;
|
||||||
|
z-index: 1000">
|
||||||
|
<a id="newAppointmentOption"
|
||||||
|
class="btn btn-sm btn-phoenix-success rounded-pill me-1 mb-1"
|
||||||
|
href="#">{{ _("New Appointment") }}</a>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
{% include 'modal/confirm_modal.html' %}
|
{% include 'modal/confirm_modal.html' %}
|
||||||
{% endblock %}
|
{% endblock %}
|
||||||
|
|
||||||
|
|
||||||
{% block customJS %}
|
{% block customJS %}
|
||||||
<script src="https://cdnjs.cloudflare.com/ajax/libs/moment.js/2.30.1/moment.js"
|
<script src="https://cdnjs.cloudflare.com/ajax/libs/moment.js/2.30.1/moment.js"
|
||||||
integrity="sha512-3CuraBvy05nIgcoXjVN33mACRyI89ydVHg7y/HMN9wcTVbHeur0SeBzweSd/rxySapO7Tmfu68+JlKkLTnDFNg=="
|
integrity="sha512-3CuraBvy05nIgcoXjVN33mACRyI89ydVHg7y/HMN9wcTVbHeur0SeBzweSd/rxySapO7Tmfu68+JlKkLTnDFNg=="
|
||||||
crossorigin="anonymous" referrerpolicy="no-referrer"></script>
|
crossorigin="anonymous"
|
||||||
|
referrerpolicy="no-referrer"></script>
|
||||||
<script src="https://cdnjs.cloudflare.com/ajax/libs/moment-timezone/0.5.45/moment-timezone-with-data.min.js"
|
<script src="https://cdnjs.cloudflare.com/ajax/libs/moment-timezone/0.5.45/moment-timezone-with-data.min.js"
|
||||||
integrity="sha512-t/mY3un180WRfsSkWy4Yi0tAxEDGcY2rAEx873hb5BrkvLA0QLk54+SjfYgFBBoCdJDV1H86M8uyZdJhAOHeyA=="
|
integrity="sha512-t/mY3un180WRfsSkWy4Yi0tAxEDGcY2rAEx873hb5BrkvLA0QLk54+SjfYgFBBoCdJDV1H86M8uyZdJhAOHeyA=="
|
||||||
crossorigin="anonymous" referrerpolicy="no-referrer"></script>
|
crossorigin="anonymous"
|
||||||
|
referrerpolicy="no-referrer"></script>
|
||||||
<script src="https://cdnjs.cloudflare.com/ajax/libs/fullcalendar/6.1.10/index.global.min.js"
|
<script src="https://cdnjs.cloudflare.com/ajax/libs/fullcalendar/6.1.10/index.global.min.js"
|
||||||
integrity="sha512-JCQkxdym6GmQ+AFVioDUq8dWaWN6tbKRhRyHvYZPupQ6DxpXzkW106FXS1ORgo/m3gxtt5lHRMqSdm2OfPajtg=="
|
integrity="sha512-JCQkxdym6GmQ+AFVioDUq8dWaWN6tbKRhRyHvYZPupQ6DxpXzkW106FXS1ORgo/m3gxtt5lHRMqSdm2OfPajtg=="
|
||||||
crossorigin="anonymous" referrerpolicy="no-referrer"></script>
|
crossorigin="anonymous"
|
||||||
|
referrerpolicy="no-referrer"></script>
|
||||||
<script>
|
<script>
|
||||||
const timezone = "{{ timezone }}";
|
const timezone = "{{ timezone }}";
|
||||||
const locale = "{{ locale }}";
|
const locale = "{{ locale }}";
|
||||||
@ -88,13 +95,11 @@
|
|||||||
const noServiceOfferedTxt = "{% trans "You don't offer any service. Add new service from your profile." %}";
|
const noServiceOfferedTxt = "{% trans "You don't offer any service. Add new service from your profile." %}";
|
||||||
const noStaffMemberTxt = "{% trans "No staff members found." %}";
|
const noStaffMemberTxt = "{% trans "No staff members found." %}";
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<script src="{% static 'js/modal/error_modal.js' %}"></script>
|
<script src="{% static 'js/modal/error_modal.js' %}"></script>
|
||||||
<script src="{% static 'js/app_admin/staff_index.js' %}"></script>
|
<script src="{% static 'js/app_admin/staff_index.js' %}"></script>
|
||||||
<script src="{% static 'js/modal/show_modal.js' %}"></script>
|
<script src="{% static 'js/modal/show_modal.js' %}"></script>
|
||||||
<script src="{% static 'js/js-utils.js' %}"></script>
|
<script src="{% static 'js/js-utils.js' %}"></script>
|
||||||
|
<script>
|
||||||
<script>
|
|
||||||
function createCommonInputFields(appointment, servicesDropdown, isEditMode, defaultStartTime, staffDropdown) {
|
function createCommonInputFields(appointment, servicesDropdown, isEditMode, defaultStartTime, staffDropdown) {
|
||||||
const startTimeValue = isEditMode ? moment(appointment.start_time).format('HH:mm:ss') : defaultStartTime;
|
const startTimeValue = isEditMode ? moment(appointment.start_time).format('HH:mm:ss') : defaultStartTime;
|
||||||
const disabledAttribute = isEditMode ? '' : 'disabled';
|
const disabledAttribute = isEditMode ? '' : 'disabled';
|
||||||
|
|||||||
@ -1,26 +1,17 @@
|
|||||||
{% extends 'base.html' %}
|
{% extends 'base.html' %}
|
||||||
{% load i18n %}
|
{% load i18n %}
|
||||||
{% load static %}
|
{% load static %}
|
||||||
{% block customCSS %}
|
{% block customCSS %}{% endblock %}
|
||||||
|
{% block title %}{{ _("Staff Members List") }}{% endblock %}
|
||||||
{% endblock %}
|
|
||||||
{% block title %}
|
|
||||||
{{ _("Staff Members List")}}
|
|
||||||
{% endblock %}
|
|
||||||
{% block description %}
|
{% block description %}
|
||||||
{% trans 'List of all staff members' %}.
|
{% trans 'List of all staff members' %}.
|
||||||
{% endblock %}
|
{% endblock %}
|
||||||
{% block body %}
|
{% block body %}
|
||||||
|
|
||||||
|
|
||||||
<div class="row">
|
<div class="row">
|
||||||
<div class="col-6">
|
<div class="col-6">
|
||||||
<h3 class="section-header-itm">{% trans 'Staff Members' %}</h3>
|
<h3 class="section-header-itm">{% trans 'Staff Members' %}</h3>
|
||||||
<div class="buttons-container section-header-itm">
|
<div class="buttons-container section-header-itm">
|
||||||
<a href="{{ btn_staff_me_link }}"
|
<a href="{{ btn_staff_me_link }}" class="btn btn-sm btn-phoenix-info">{{ btn_staff_me }}</a>
|
||||||
class="btn btn-sm btn-phoenix-info">
|
|
||||||
{{ btn_staff_me }}
|
|
||||||
</a>
|
|
||||||
<a href="{% url 'appointment:add_staff_member_info' %}"
|
<a href="{% url 'appointment:add_staff_member_info' %}"
|
||||||
class="btn btn-sm btn-phoenix-success">{{ _("Add") }}
|
class="btn btn-sm btn-phoenix-success">{{ _("Add") }}
|
||||||
<i class="fas fa-add"></i>
|
<i class="fas fa-add"></i>
|
||||||
@ -53,16 +44,12 @@
|
|||||||
<td colspan="3">{% trans 'No staff members found' %}.</td>
|
<td colspan="3">{% trans 'No staff members found' %}.</td>
|
||||||
</tr>
|
</tr>
|
||||||
{% endfor %}
|
{% endfor %}
|
||||||
<small>
|
<small>{% trans "PS: Remove means, deleting the staff status of the user. The user account is still active." %}</small>
|
||||||
{% trans "PS: Remove means, deleting the staff status of the user. The user account is still active." %}
|
|
||||||
</small>
|
|
||||||
</tbody>
|
</tbody>
|
||||||
</table>
|
</table>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
{% endblock %}
|
{% endblock %}
|
||||||
|
|
||||||
{% block customJS %}
|
{% block customJS %}
|
||||||
<script src="{% static 'js/js-utils.js' %}"></script>
|
<script src="{% static 'js/js-utils.js' %}"></script>
|
||||||
{% endblock %}
|
{% endblock %}
|
||||||
|
|||||||
@ -1,28 +1,26 @@
|
|||||||
{% extends BASE_TEMPLATE %}
|
{% extends BASE_TEMPLATE %}
|
||||||
{% load i18n %}
|
{% load i18n %}
|
||||||
{% load static %}
|
{% load static %}
|
||||||
{% block customCSS %}
|
{% block customCSS %}{% endblock %}
|
||||||
|
{% block title %}{{ page_title }}{% endblock %}
|
||||||
{% endblock %}
|
{% block description %}{{ page_description }}{% endblock %}
|
||||||
{% block title %}
|
|
||||||
{{ page_title }}
|
|
||||||
{% endblock %}
|
|
||||||
{% block description %}
|
|
||||||
{{ page_description }}
|
|
||||||
{% endblock %}
|
|
||||||
{% block content %}
|
{% block content %}
|
||||||
|
|
||||||
<div class="row mt-4">
|
<div class="row mt-4">
|
||||||
<div class="col-6">
|
<div class="col-6">
|
||||||
{% translate "Confirm Deletion" as modal_title %}
|
{% translate "Confirm Deletion" as modal_title %}
|
||||||
{% translate "Delete" as delete_btn_modal %}
|
{% translate "Delete" as delete_btn_modal %}
|
||||||
|
|
||||||
<h3>{% trans 'Personal Information' %}</h3>
|
<h3>{% trans 'Personal Information' %}</h3>
|
||||||
<!-- Display fields from PersonalInformationForm -->
|
<!-- Display fields from PersonalInformationForm -->
|
||||||
<div class="section-content">
|
<div class="section-content">
|
||||||
<p><strong>{% trans 'First name' %}:</strong> {{ user.first_name }}</p>
|
<p>
|
||||||
<p><strong>{% trans 'Last name' %}:</strong> {{ user.last_name }}</p>
|
<strong>{% trans 'First name' %}:</strong> {{ user.first_name }}
|
||||||
<p><strong>{% trans 'Email' %}:</strong> {{ user.email }}</p>
|
</p>
|
||||||
|
<p>
|
||||||
|
<strong>{% trans 'Last name' %}:</strong> {{ user.last_name }}
|
||||||
|
</p>
|
||||||
|
<p>
|
||||||
|
<strong>{% trans 'Email' %}:</strong> {{ user.email }}
|
||||||
|
</p>
|
||||||
</div>
|
</div>
|
||||||
<a href="{% url 'appointment:update_user_info' user.id %}"
|
<a href="{% url 'appointment:update_user_info' user.id %}"
|
||||||
class="btn btn-sm btn-phoenix-primary">
|
class="btn btn-sm btn-phoenix-primary">
|
||||||
@ -30,32 +28,34 @@
|
|||||||
</a>
|
</a>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
<!-- Appointment Information Section -->
|
||||||
<!-- Appointment Information Section -->
|
|
||||||
<div class="row mt-4">
|
<div class="row mt-4">
|
||||||
<div class="col-6">
|
<div class="col-6">
|
||||||
<h3>{% trans 'Appointments Information' %}</h3>
|
<h3>{% trans 'Appointments Information' %}</h3>
|
||||||
<small>
|
<small>{{ service_msg }}</small>
|
||||||
{{ service_msg }}
|
|
||||||
</small>
|
|
||||||
{% if staff_member %}
|
{% if staff_member %}
|
||||||
<div class="section-content">
|
<div class="section-content">
|
||||||
<p>
|
<p>
|
||||||
<strong>{% trans 'Slot duration' %}:</strong> {{ staff_member.get_slot_duration_text }}
|
<strong>{% trans 'Slot duration' %}:</strong> {{ staff_member.get_slot_duration_text }}
|
||||||
<i class="fas fa-info-circle" data-toggle="tooltip"
|
<i class="fas fa-info-circle"
|
||||||
|
data-toggle="tooltip"
|
||||||
title="{{ slot_duration_help_text }}"></i>
|
title="{{ slot_duration_help_text }}"></i>
|
||||||
</p>
|
</p>
|
||||||
<p><strong>{% trans 'General start time' %}:</strong> {{ staff_member.get_lead_time }}</p>
|
<p>
|
||||||
<p><strong>{% trans 'General end time' %}:</strong> {{ staff_member.get_finish_time }}</p>
|
<strong>{% trans 'General start time' %}:</strong> {{ staff_member.get_lead_time }}
|
||||||
|
</p>
|
||||||
|
<p>
|
||||||
|
<strong>{% trans 'General end time' %}:</strong> {{ staff_member.get_finish_time }}
|
||||||
|
</p>
|
||||||
<p>
|
<p>
|
||||||
<strong>{% trans 'Weekend days you work' %}:</strong> {{ staff_member.get_weekend_days_worked_text }}
|
<strong>{% trans 'Weekend days you work' %}:</strong> {{ staff_member.get_weekend_days_worked_text }}
|
||||||
</p>
|
</p>
|
||||||
|
|
||||||
<p>
|
<p>
|
||||||
<strong>{% trans 'Appointment buffer time' %}:</strong> {{ staff_member.get_appointment_buffer_time_text }}
|
<strong>{% trans 'Appointment buffer time' %}:</strong> {{ staff_member.get_appointment_buffer_time_text }}
|
||||||
<i class="fas fa-info-circle" data-toggle="tooltip" title="{{ buffer_time_help_text }}"></i>
|
<i class="fas fa-info-circle"
|
||||||
|
data-toggle="tooltip"
|
||||||
|
title="{{ buffer_time_help_text }}"></i>
|
||||||
</p>
|
</p>
|
||||||
|
|
||||||
</div>
|
</div>
|
||||||
<a href="{% url 'appointment:update_staff_other_info' staff_member.user.id %}"
|
<a href="{% url 'appointment:update_staff_other_info' staff_member.user.id %}"
|
||||||
class="btn btn-sm section-content-button modify-btn button-color-blue btn-phoenix-primary">
|
class="btn btn-sm section-content-button modify-btn button-color-blue btn-phoenix-primary">
|
||||||
@ -72,8 +72,7 @@
|
|||||||
{% endif %}
|
{% endif %}
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
<!-- Days Off Information Section -->
|
||||||
<!-- Days Off Information Section -->
|
|
||||||
<section class="profile">
|
<section class="profile">
|
||||||
<div class="container">
|
<div class="container">
|
||||||
<h3>{% trans 'Days Off' %}</h3>
|
<h3>{% trans 'Days Off' %}</h3>
|
||||||
@ -140,8 +139,7 @@
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</section>
|
</section>
|
||||||
|
<!-- Working Hours Information Section -->
|
||||||
<!-- Working Hours Information Section -->
|
|
||||||
<section class="profile">
|
<section class="profile">
|
||||||
<div class="container">
|
<div class="container">
|
||||||
<h3>{% trans 'Working Hours' %}</h3>
|
<h3>{% trans 'Working Hours' %}</h3>
|
||||||
@ -165,7 +163,6 @@
|
|||||||
<tbody>
|
<tbody>
|
||||||
{% for working_hour in working_hours %}
|
{% for working_hour in working_hours %}
|
||||||
<tr>
|
<tr>
|
||||||
|
|
||||||
{% if working_hour.day_of_week == 0 %}
|
{% if working_hour.day_of_week == 0 %}
|
||||||
<td>{{ _("Sunday") }}</td>
|
<td>{{ _("Sunday") }}</td>
|
||||||
{% elif working_hour.day_of_week == 1 %}
|
{% elif working_hour.day_of_week == 1 %}
|
||||||
@ -181,7 +178,6 @@
|
|||||||
{% elif working_hour.day_of_week == 6 %}
|
{% elif working_hour.day_of_week == 6 %}
|
||||||
<td>{{ _("Saturday") }}</td>
|
<td>{{ _("Saturday") }}</td>
|
||||||
{% endif %}
|
{% endif %}
|
||||||
|
|
||||||
<td>{{ working_hour.start_time|time:"g:i A" }}</td>
|
<td>{{ working_hour.start_time|time:"g:i A" }}</td>
|
||||||
<td>{{ working_hour.end_time|time:"g:i A" }}</td>
|
<td>{{ working_hour.end_time|time:"g:i A" }}</td>
|
||||||
<td>
|
<td>
|
||||||
@ -224,8 +220,7 @@
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</section>
|
</section>
|
||||||
|
<!-- Service Information Section -->
|
||||||
<!-- Service Information Section -->
|
|
||||||
<section class="profile">
|
<section class="profile">
|
||||||
<div class="container">
|
<div class="container">
|
||||||
<h3>{% trans 'Service Offered' %}</h3>
|
<h3>{% trans 'Service Offered' %}</h3>
|
||||||
@ -265,17 +260,11 @@
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</section>
|
</section>
|
||||||
|
|
||||||
|
|
||||||
{% include 'modal/confirm_modal.html' %}
|
{% include 'modal/confirm_modal.html' %}
|
||||||
{% endblock %}
|
{% endblock %}
|
||||||
{% block customJS %}
|
{% block customJS %}
|
||||||
<!-- Bootstrap's JS and CSS (if not already included) -->
|
<!-- Bootstrap's JS and CSS (if not already included) -->
|
||||||
|
|
||||||
|
|
||||||
<!-- Our custom modal JS -->
|
<!-- Our custom modal JS -->
|
||||||
<script src="{% static 'js/modal/show_modal.js' %}"></script>
|
<script src="{% static 'js/modal/show_modal.js' %}"></script>
|
||||||
|
|
||||||
<script src="{% static 'js/js-utils.js' %}"></script>
|
<script src="{% static 'js/js-utils.js' %}"></script>
|
||||||
{% endblock %}
|
{% endblock %}
|
||||||
|
|
||||||
|
|||||||
@ -2,7 +2,9 @@
|
|||||||
{% load i18n %}
|
{% load i18n %}
|
||||||
{% load static %}
|
{% load static %}
|
||||||
{% block customCSS %}
|
{% block customCSS %}
|
||||||
<link rel="stylesheet" type="text/css" href="{% static 'css/appointments-user-details.css' %}"/>
|
<link rel="stylesheet"
|
||||||
|
type="text/css"
|
||||||
|
href="{% static 'css/appointments-user-details.css' %}" />
|
||||||
{% endblock %}
|
{% endblock %}
|
||||||
{% block title %}
|
{% block title %}
|
||||||
{% translate 'Client Information' %} - {{ ar.get_service_name }}
|
{% translate 'Client Information' %} - {{ ar.get_service_name }}
|
||||||
@ -22,9 +24,7 @@
|
|||||||
{% csrf_token %}
|
{% csrf_token %}
|
||||||
<div class="appointment-user-info">
|
<div class="appointment-user-info">
|
||||||
<div class="appointment-user-info-title">
|
<div class="appointment-user-info-title">
|
||||||
<div class="title">
|
<div class="title">{% trans "Fill out your details" %}</div>
|
||||||
{% trans "Fill out your details" %}
|
|
||||||
</div>
|
|
||||||
</div>
|
</div>
|
||||||
<hr class="second-part">
|
<hr class="second-part">
|
||||||
<div class="user-info-input">
|
<div class="user-info-input">
|
||||||
@ -37,10 +37,14 @@
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div class="name-email">
|
<div class="name-email">
|
||||||
<label for="{{ form.name.id_for_label }}" class="name">{% trans "Full Name" %} *<br>
|
<label for="{{ form.name.id_for_label }}" class="name">
|
||||||
|
{% trans "Full Name" %} *
|
||||||
|
<br>
|
||||||
{{ client_data_form.name }}
|
{{ client_data_form.name }}
|
||||||
</label>
|
</label>
|
||||||
<label for="{{ form.email.id_for_label }}" class="email">{% trans "Email" %} *<br>
|
<label for="{{ form.email.id_for_label }}" class="email">
|
||||||
|
{% trans "Email" %} *
|
||||||
|
<br>
|
||||||
{{ client_data_form.email }}
|
{{ client_data_form.email }}
|
||||||
</label>
|
</label>
|
||||||
</div>
|
</div>
|
||||||
@ -52,19 +56,22 @@
|
|||||||
</div>
|
</div>
|
||||||
<div class="phone-number">
|
<div class="phone-number">
|
||||||
<label for="{{ form.phone.id_for_label }}">
|
<label for="{{ form.phone.id_for_label }}">
|
||||||
{% trans "Phone" %} *<br>
|
{% trans "Phone" %} *
|
||||||
|
<br>
|
||||||
</label>
|
</label>
|
||||||
<div class="phone-input-container">
|
<div class="phone-input-container">{{ form.phone }}</div>
|
||||||
{{ form.phone }}
|
|
||||||
</div>
|
|
||||||
</div>
|
</div>
|
||||||
<div class="address">
|
<div class="address">
|
||||||
<label for="{{ form.address.id_for_label }}">{% trans "City and State" %} * :<br>
|
<label for="{{ form.address.id_for_label }}">
|
||||||
|
{% trans "City and State" %} * :
|
||||||
|
<br>
|
||||||
{{ form.address }}
|
{{ form.address }}
|
||||||
</label>
|
</label>
|
||||||
</div>
|
</div>
|
||||||
<div class="additional-information">
|
<div class="additional-information">
|
||||||
<label for="{{ form.additional_info.id_for_label }}">{% trans 'Additional Information' %}<br>
|
<label for="{{ form.additional_info.id_for_label }}">
|
||||||
|
{% trans 'Additional Information' %}
|
||||||
|
<br>
|
||||||
{{ form.additional_info }}
|
{{ form.additional_info }}
|
||||||
</label>
|
</label>
|
||||||
</div>
|
</div>
|
||||||
@ -73,18 +80,14 @@
|
|||||||
</div>
|
</div>
|
||||||
<div class="service-description-and-pay">
|
<div class="service-description-and-pay">
|
||||||
<div class="service-details-title">{% trans "Service Details" %}</div>
|
<div class="service-details-title">{% trans "Service Details" %}</div>
|
||||||
|
|
||||||
<hr class="second-part">
|
<hr class="second-part">
|
||||||
|
|
||||||
<div class="service-description-content">
|
<div class="service-description-content">
|
||||||
<div class="item-name">{{ ar.get_service_name }}</div>
|
<div class="item-name">{{ ar.get_service_name }}</div>
|
||||||
<div id="service-datetime-chosen"
|
<div id="service-datetime-chosen" class="service-datetime-chosen">
|
||||||
class="service-datetime-chosen">
|
|
||||||
{{ ar.date }} {% trans "at" %} {{ ar.start_time }}
|
{{ ar.date }} {% trans "at" %} {{ ar.start_time }}
|
||||||
</div>
|
</div>
|
||||||
<div>{{ ar.service.get_duration }}</div>
|
<div>{{ ar.service.get_duration }}</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<hr class="second-part">
|
<hr class="second-part">
|
||||||
{% if ar.is_a_paid_service %}
|
{% if ar.is_a_paid_service %}
|
||||||
{% if APPOINTMENT_PAYMENT_URL %}
|
{% if APPOINTMENT_PAYMENT_URL %}
|
||||||
@ -95,12 +98,13 @@
|
|||||||
<div>${{ ar.get_service_price }}</div>
|
<div>${{ ar.get_service_price }}</div>
|
||||||
</div>
|
</div>
|
||||||
<div class="payment-options">
|
<div class="payment-options">
|
||||||
<button type="submit" class="btn btn-phoenix-primary btn-pay-full" name="payment_type"
|
<button type="submit"
|
||||||
value="full">
|
class="btn btn-phoenix-primary btn-pay-full"
|
||||||
{% trans "Pay" %}
|
name="payment_type"
|
||||||
</button>
|
value="full">{% trans "Pay" %}</button>
|
||||||
{% if ar.accepts_down_payment %}
|
{% if ar.accepts_down_payment %}
|
||||||
<button type="submit" class="btn btn-phoenix-primary btn-pay-down-payment"
|
<button type="submit"
|
||||||
|
class="btn btn-phoenix-primary btn-pay-down-payment"
|
||||||
name="payment_type"
|
name="payment_type"
|
||||||
value="down">
|
value="down">
|
||||||
{% trans "Down Payment" %} (${{ ar.get_service_down_payment }})
|
{% trans "Down Payment" %} (${{ ar.get_service_down_payment }})
|
||||||
@ -109,16 +113,16 @@
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
{% else %}
|
{% else %}
|
||||||
<button type="submit" class="btn btn-phoenix-primary btn-submit-appointment" name="payment_type"
|
<button type="submit"
|
||||||
value="full">
|
class="btn btn-phoenix-primary btn-submit-appointment"
|
||||||
{% trans "Finish" %}
|
name="payment_type"
|
||||||
</button>
|
value="full">{% trans "Finish" %}</button>
|
||||||
{% endif %}
|
{% endif %}
|
||||||
{% else %}
|
{% else %}
|
||||||
<button type="submit" class="btn btn-phoenix-primary btn-submit-appointment" name="payment_type"
|
<button type="submit"
|
||||||
value="full">
|
class="btn btn-phoenix-primary btn-submit-appointment"
|
||||||
{% trans "Finish" %}
|
name="payment_type"
|
||||||
</button>
|
value="full">{% trans "Finish" %}</button>
|
||||||
{% endif %}
|
{% endif %}
|
||||||
</div>
|
</div>
|
||||||
</form>
|
</form>
|
||||||
|
|||||||