Compare commits
2 Commits
1b5d0fbf7d
...
2bbcae2e7a
| Author | SHA1 | Date | |
|---|---|---|---|
| 2bbcae2e7a | |||
| 4d63b17e68 |
@ -10,9 +10,11 @@ https://docs.djangoproject.com/en/5.0/howto/deployment/asgi/
|
|||||||
# asgi.py
|
# asgi.py
|
||||||
|
|
||||||
import os
|
import os
|
||||||
|
|
||||||
os.environ.setdefault("DJANGO_SETTINGS_MODULE", "car_inventory.settings")
|
os.environ.setdefault("DJANGO_SETTINGS_MODULE", "car_inventory.settings")
|
||||||
|
|
||||||
import django
|
import django
|
||||||
|
|
||||||
django.setup()
|
django.setup()
|
||||||
|
|
||||||
|
|
||||||
@ -30,11 +32,17 @@ from django.core.asgi import get_asgi_application
|
|||||||
# # "websocket": AuthMiddlewareStack(URLRouter(routing.websocket_urlpatterns)),
|
# # "websocket": AuthMiddlewareStack(URLRouter(routing.websocket_urlpatterns)),
|
||||||
# }
|
# }
|
||||||
# )
|
# )
|
||||||
application = ProtocolTypeRouter({
|
application = ProtocolTypeRouter(
|
||||||
|
{
|
||||||
"http": AuthMiddlewareStack(
|
"http": AuthMiddlewareStack(
|
||||||
URLRouter([
|
URLRouter(
|
||||||
|
[
|
||||||
path("sse/notifications/", NotificationSSEApp()),
|
path("sse/notifications/", NotificationSSEApp()),
|
||||||
re_path(r"", get_asgi_application()), # All other routes go to Django
|
re_path(
|
||||||
])
|
r"", get_asgi_application()
|
||||||
|
), # All other routes go to Django
|
||||||
|
]
|
||||||
|
)
|
||||||
),
|
),
|
||||||
})
|
}
|
||||||
|
)
|
||||||
|
|||||||
@ -56,7 +56,7 @@ from .models import (
|
|||||||
DealerSettings,
|
DealerSettings,
|
||||||
Tasks,
|
Tasks,
|
||||||
Recall,
|
Recall,
|
||||||
Ticket
|
Ticket,
|
||||||
)
|
)
|
||||||
from django_ledger import models as ledger_models
|
from django_ledger import models as ledger_models
|
||||||
from django.forms import (
|
from django.forms import (
|
||||||
@ -146,9 +146,16 @@ class StaffForm(forms.ModelForm):
|
|||||||
)
|
)
|
||||||
|
|
||||||
class Meta:
|
class Meta:
|
||||||
|
|
||||||
model = Staff
|
model = Staff
|
||||||
fields = ["first_name","last_name", "arabic_name", "phone_number", "address", "logo", "group"]
|
fields = [
|
||||||
|
"first_name",
|
||||||
|
"last_name",
|
||||||
|
"arabic_name",
|
||||||
|
"phone_number",
|
||||||
|
"address",
|
||||||
|
"logo",
|
||||||
|
"group",
|
||||||
|
]
|
||||||
|
|
||||||
|
|
||||||
# Dealer Form
|
# Dealer Form
|
||||||
@ -439,13 +446,15 @@ class CarFinanceForm(forms.ModelForm):
|
|||||||
marked_price = cleaned_data.get("marked_price")
|
marked_price = cleaned_data.get("marked_price")
|
||||||
|
|
||||||
if cost_price > marked_price:
|
if cost_price > marked_price:
|
||||||
raise forms.ValidationError({"cost_price": "Cost price should not be greater than marked price"})
|
raise forms.ValidationError(
|
||||||
|
{"cost_price": "Cost price should not be greater than marked price"}
|
||||||
|
)
|
||||||
|
|
||||||
return cleaned_data
|
return cleaned_data
|
||||||
|
|
||||||
class Meta:
|
class Meta:
|
||||||
model = Car
|
model = Car
|
||||||
fields = ["cost_price","marked_price"]
|
fields = ["cost_price", "marked_price"]
|
||||||
|
|
||||||
|
|
||||||
class CarLocationForm(forms.ModelForm):
|
class CarLocationForm(forms.ModelForm):
|
||||||
@ -1168,7 +1177,7 @@ class ScheduleForm(forms.ModelForm):
|
|||||||
scheduled_at = forms.DateTimeField(
|
scheduled_at = forms.DateTimeField(
|
||||||
widget=DateTimeInput(attrs={"type": "datetime-local"})
|
widget=DateTimeInput(attrs={"type": "datetime-local"})
|
||||||
)
|
)
|
||||||
reminder = forms.BooleanField(help_text=_("Send a reminder?"),required=False)
|
reminder = forms.BooleanField(help_text=_("Send a reminder?"), required=False)
|
||||||
|
|
||||||
class Meta:
|
class Meta:
|
||||||
model = Schedule
|
model = Schedule
|
||||||
@ -1289,6 +1298,7 @@ class OpportunityForm(forms.ModelForm):
|
|||||||
if self.instance and self.instance.pk:
|
if self.instance and self.instance.pk:
|
||||||
self.fields["probability"].initial = self.instance.probability
|
self.fields["probability"].initial = self.instance.probability
|
||||||
|
|
||||||
|
|
||||||
class OpportunityStageForm(forms.ModelForm):
|
class OpportunityStageForm(forms.ModelForm):
|
||||||
"""
|
"""
|
||||||
Represents a form for creating or editing Opportunity instances.
|
Represents a form for creating or editing Opportunity instances.
|
||||||
@ -1305,17 +1315,13 @@ class OpportunityStageForm(forms.ModelForm):
|
|||||||
:type Meta.fields: list
|
:type Meta.fields: list
|
||||||
"""
|
"""
|
||||||
|
|
||||||
|
|
||||||
class Meta:
|
class Meta:
|
||||||
model = Opportunity
|
model = Opportunity
|
||||||
fields = [
|
fields = [
|
||||||
"stage",
|
"stage",
|
||||||
|
|
||||||
]
|
]
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
class InvoiceModelCreateForm(InvoiceModelCreateFormBase):
|
class InvoiceModelCreateForm(InvoiceModelCreateFormBase):
|
||||||
"""
|
"""
|
||||||
Represents a form for creating an Invoice model that inherits from a base
|
Represents a form for creating an Invoice model that inherits from a base
|
||||||
@ -1633,8 +1639,7 @@ class PermissionForm(forms.ModelForm):
|
|||||||
"django_ledger.billmodeldjango_ledger.itemmodel",
|
"django_ledger.billmodeldjango_ledger.itemmodel",
|
||||||
"django_ledger.invoicemodel",
|
"django_ledger.invoicemodel",
|
||||||
"django_ledger.vendormodel",
|
"django_ledger.vendormodel",
|
||||||
"django_ledger.journalentrymodel"
|
"django_ledger.journalentrymodeldjango_ledger.purchaseordermodel",
|
||||||
"django_ledger.purchaseordermodel",
|
|
||||||
]
|
]
|
||||||
|
|
||||||
permissions = cache.get(
|
permissions = cache.get(
|
||||||
@ -2138,91 +2143,115 @@ class VatRateForm(forms.ModelForm):
|
|||||||
class CustomSetPasswordForm(SetPasswordForm):
|
class CustomSetPasswordForm(SetPasswordForm):
|
||||||
new_password1 = forms.CharField(
|
new_password1 = forms.CharField(
|
||||||
label="New Password",
|
label="New Password",
|
||||||
widget=forms.PasswordInput(attrs={'class': 'form-control', 'placeholder': 'New Password'})
|
widget=forms.PasswordInput(
|
||||||
|
attrs={"class": "form-control", "placeholder": "New Password"}
|
||||||
|
),
|
||||||
)
|
)
|
||||||
new_password2 = forms.CharField(
|
new_password2 = forms.CharField(
|
||||||
label="Confirm New Password",
|
label="Confirm New Password",
|
||||||
widget=forms.PasswordInput(attrs={'class': 'form-control', 'placeholder': 'Confirm New Password'})
|
widget=forms.PasswordInput(
|
||||||
|
attrs={"class": "form-control", "placeholder": "Confirm New Password"}
|
||||||
|
),
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
# forms.py
|
# forms.py
|
||||||
class RecallFilterForm(forms.Form):
|
class RecallFilterForm(forms.Form):
|
||||||
make = forms.ModelChoiceField(
|
make = forms.ModelChoiceField(
|
||||||
queryset=CarMake.objects.all(),
|
queryset=CarMake.objects.all(),
|
||||||
required=False,
|
required=False,
|
||||||
label=_("Make"),
|
label=_("Make"),
|
||||||
widget=forms.Select(attrs={'class': 'form-control'})
|
widget=forms.Select(attrs={"class": "form-control"}),
|
||||||
)
|
)
|
||||||
model = forms.ModelChoiceField(
|
model = forms.ModelChoiceField(
|
||||||
queryset=CarModel.objects.none(),
|
queryset=CarModel.objects.none(),
|
||||||
required=False,
|
required=False,
|
||||||
label=_("Model"),
|
label=_("Model"),
|
||||||
widget=forms.Select(attrs={'class': 'form-control'})
|
widget=forms.Select(attrs={"class": "form-control"}),
|
||||||
)
|
)
|
||||||
serie = forms.ModelChoiceField(
|
serie = forms.ModelChoiceField(
|
||||||
queryset=CarSerie.objects.none(),
|
queryset=CarSerie.objects.none(),
|
||||||
required=False,
|
required=False,
|
||||||
label=_("Series"),
|
label=_("Series"),
|
||||||
widget=forms.Select(attrs={'class': 'form-control'})
|
widget=forms.Select(attrs={"class": "form-control"}),
|
||||||
)
|
)
|
||||||
trim = forms.ModelChoiceField(
|
trim = forms.ModelChoiceField(
|
||||||
queryset=CarTrim.objects.none(),
|
queryset=CarTrim.objects.none(),
|
||||||
required=False,
|
required=False,
|
||||||
label=_("Trim"),
|
label=_("Trim"),
|
||||||
widget=forms.Select(attrs={'class': 'form-control'})
|
widget=forms.Select(attrs={"class": "form-control"}),
|
||||||
|
)
|
||||||
|
year_from = forms.IntegerField(
|
||||||
|
required=False,
|
||||||
|
label=_("From Year"),
|
||||||
|
widget=forms.NumberInput(attrs={"class": "form-control"}),
|
||||||
|
)
|
||||||
|
year_to = forms.IntegerField(
|
||||||
|
required=False,
|
||||||
|
label=_("To Year"),
|
||||||
|
widget=forms.NumberInput(attrs={"class": "form-control"}),
|
||||||
)
|
)
|
||||||
year_from = forms.IntegerField(required=False, label=_("From Year"),
|
|
||||||
widget=forms.NumberInput(attrs={'class': 'form-control'}))
|
|
||||||
year_to = forms.IntegerField(required=False, label=_("To Year"),
|
|
||||||
widget=forms.NumberInput(attrs={'class': 'form-control'}))
|
|
||||||
|
|
||||||
def __init__(self, *args, **kwargs):
|
def __init__(self, *args, **kwargs):
|
||||||
make_id = kwargs.pop('make_id', None)
|
make_id = kwargs.pop("make_id", None)
|
||||||
model_id = kwargs.pop('model_id', None)
|
model_id = kwargs.pop("model_id", None)
|
||||||
serie_id = kwargs.pop('serie_id', None)
|
serie_id = kwargs.pop("serie_id", None)
|
||||||
super().__init__(*args, **kwargs)
|
super().__init__(*args, **kwargs)
|
||||||
|
|
||||||
if make_id:
|
if make_id:
|
||||||
self.fields['model'].queryset = CarModel.objects.filter(id_car_make_id=make_id)
|
self.fields["model"].queryset = CarModel.objects.filter(
|
||||||
|
id_car_make_id=make_id
|
||||||
|
)
|
||||||
if model_id:
|
if model_id:
|
||||||
self.fields['serie'].queryset = CarSerie.objects.filter(id_car_model_id=model_id)
|
self.fields["serie"].queryset = CarSerie.objects.filter(
|
||||||
|
id_car_model_id=model_id
|
||||||
|
)
|
||||||
if serie_id:
|
if serie_id:
|
||||||
self.fields['trim'].queryset = CarTrim.objects.filter(id_car_serie_id=serie_id)
|
self.fields["trim"].queryset = CarTrim.objects.filter(
|
||||||
|
id_car_serie_id=serie_id
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
class RecallCreateForm(forms.ModelForm):
|
class RecallCreateForm(forms.ModelForm):
|
||||||
class Meta:
|
class Meta:
|
||||||
model = Recall
|
model = Recall
|
||||||
fields = ['title', 'description', 'make', 'model', 'serie', 'trim', 'year_from', 'year_to']
|
fields = [
|
||||||
|
"title",
|
||||||
|
"description",
|
||||||
|
"make",
|
||||||
|
"model",
|
||||||
|
"serie",
|
||||||
|
"trim",
|
||||||
|
"year_from",
|
||||||
|
"year_to",
|
||||||
|
]
|
||||||
widgets = {
|
widgets = {
|
||||||
'make': forms.Select(attrs={'class': 'form-control'}),
|
"make": forms.Select(attrs={"class": "form-control"}),
|
||||||
'model': forms.Select(attrs={'class': 'form-control'}),
|
"model": forms.Select(attrs={"class": "form-control"}),
|
||||||
'serie': forms.Select(attrs={'class': 'form-control'}),
|
"serie": forms.Select(attrs={"class": "form-control"}),
|
||||||
'trim': forms.Select(attrs={'class': 'form-control'}),
|
"trim": forms.Select(attrs={"class": "form-control"}),
|
||||||
'title': forms.TextInput(attrs={'class': 'form-control'}),
|
"title": forms.TextInput(attrs={"class": "form-control"}),
|
||||||
'description': forms.Textarea(attrs={'class': 'form-control'}),
|
"description": forms.Textarea(attrs={"class": "form-control"}),
|
||||||
'year_from': forms.NumberInput(attrs={'class': 'form-control'}),
|
"year_from": forms.NumberInput(attrs={"class": "form-control"}),
|
||||||
'year_to': forms.NumberInput(attrs={'class': 'form-control'}),
|
"year_to": forms.NumberInput(attrs={"class": "form-control"}),
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
class TicketForm(forms.ModelForm):
|
class TicketForm(forms.ModelForm):
|
||||||
class Meta:
|
class Meta:
|
||||||
model = Ticket
|
model = Ticket
|
||||||
fields = ['subject', 'description', 'priority']
|
fields = ["subject", "description", "priority"]
|
||||||
widgets = {
|
widgets = {
|
||||||
'description': forms.Textarea(attrs={'class': 'form-control', 'rows': 10}),
|
"description": forms.Textarea(attrs={"class": "form-control", "rows": 10}),
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
class TicketResolutionForm(forms.ModelForm):
|
class TicketResolutionForm(forms.ModelForm):
|
||||||
|
|
||||||
class Meta:
|
class Meta:
|
||||||
model = Ticket
|
model = Ticket
|
||||||
fields = ['status', 'resolution_notes']
|
fields = ["status", "resolution_notes"]
|
||||||
|
|
||||||
def __init__(self, *args, **kwargs):
|
def __init__(self, *args, **kwargs):
|
||||||
super().__init__(*args, **kwargs)
|
super().__init__(*args, **kwargs)
|
||||||
# Limit status choices to resolution options
|
# Limit status choices to resolution options
|
||||||
self.fields['status'].choices = [
|
self.fields["status"].choices = [("resolved", "Resolved"), ("closed", "Closed")]
|
||||||
('resolved', 'Resolved'),
|
|
||||||
('closed', 'Closed')
|
|
||||||
]
|
|
||||||
|
|||||||
@ -1,9 +1,10 @@
|
|||||||
import logging
|
import logging
|
||||||
from inventory.models import Dealer
|
from inventory.models import Dealer
|
||||||
from .utils import get_accounts_data,create_account
|
from .utils import get_accounts_data, create_account
|
||||||
|
|
||||||
logger = logging.getLogger(__name__)
|
logger = logging.getLogger(__name__)
|
||||||
|
|
||||||
|
|
||||||
def check_create_coa_accounts(task):
|
def check_create_coa_accounts(task):
|
||||||
logger.info("Checking if all accounts are created")
|
logger.info("Checking if all accounts are created")
|
||||||
instance = task.kwargs["dealer"]
|
instance = task.kwargs["dealer"]
|
||||||
@ -17,7 +18,8 @@ def check_create_coa_accounts(task):
|
|||||||
logger.info(f"Default account does not exist: {account_data['code']}")
|
logger.info(f"Default account does not exist: {account_data['code']}")
|
||||||
create_account(entity, coa, account_data)
|
create_account(entity, coa, account_data)
|
||||||
|
|
||||||
|
|
||||||
def print_results(task):
|
def print_results(task):
|
||||||
dealer= task.kwargs["dealer"]
|
dealer = task.kwargs["dealer"]
|
||||||
print("HOOK: ",dealer)
|
print("HOOK: ", dealer)
|
||||||
print("HOOK: ",dealer.pk)
|
print("HOOK: ", dealer.pk)
|
||||||
|
|||||||
@ -8,11 +8,12 @@ from django.core.management.base import BaseCommand
|
|||||||
from django.template.loader import render_to_string
|
from django.template.loader import render_to_string
|
||||||
from django.utils.translation import gettext_lazy as _
|
from django.utils.translation import gettext_lazy as _
|
||||||
from django.contrib.contenttypes.models import ContentType
|
from django.contrib.contenttypes.models import ContentType
|
||||||
from django_ledger.models import InvoiceModel,EstimateModel
|
from django_ledger.models import InvoiceModel, EstimateModel
|
||||||
from inventory.models import ExtraInfo,Notification,CustomGroup
|
from inventory.models import ExtraInfo, Notification, CustomGroup
|
||||||
|
|
||||||
logger = logging.getLogger(__name__)
|
logger = logging.getLogger(__name__)
|
||||||
|
|
||||||
|
|
||||||
class Command(BaseCommand):
|
class Command(BaseCommand):
|
||||||
help = "Handles invoices due date reminders"
|
help = "Handles invoices due date reminders"
|
||||||
|
|
||||||
@ -33,27 +34,30 @@ class Command(BaseCommand):
|
|||||||
|
|
||||||
def invocie_expiration_reminders(self):
|
def invocie_expiration_reminders(self):
|
||||||
"""Queue email reminders for expiring plans"""
|
"""Queue email reminders for expiring plans"""
|
||||||
reminder_days = getattr(settings, 'INVOICE_PAST_DUE_REMIND', [3, 7, 14])
|
reminder_days = getattr(settings, "INVOICE_PAST_DUE_REMIND", [3, 7, 14])
|
||||||
today = timezone.now().date()
|
today = timezone.now().date()
|
||||||
|
|
||||||
for days in reminder_days:
|
for days in reminder_days:
|
||||||
target_date = today + timedelta(days=days)
|
target_date = today + timedelta(days=days)
|
||||||
expiring_plans = InvoiceModel.objects.filter(
|
expiring_plans = InvoiceModel.objects.filter(
|
||||||
date_due=target_date
|
date_due=target_date
|
||||||
).select_related('customer','ce_model')
|
).select_related("customer", "ce_model")
|
||||||
|
|
||||||
for inv in expiring_plans:
|
for inv in expiring_plans:
|
||||||
# dealer = inv.customer.customer_set.first().dealer
|
# dealer = inv.customer.customer_set.first().dealer
|
||||||
subject = f"Your invoice is due in {days} days"
|
subject = f"Your invoice is due in {days} days"
|
||||||
message = render_to_string('emails/invoice_past_due_reminder.txt', {
|
message = render_to_string(
|
||||||
'customer_name': inv.customer.customer_name,
|
"emails/invoice_past_due_reminder.txt",
|
||||||
'invoice_number': inv.invoice_number,
|
{
|
||||||
'amount_due': inv.amount_due,
|
"customer_name": inv.customer.customer_name,
|
||||||
'days_past_due': inv.due_in_days(),
|
"invoice_number": inv.invoice_number,
|
||||||
'SITE_NAME': settings.SITE_NAME
|
"amount_due": inv.amount_due,
|
||||||
})
|
"days_past_due": inv.due_in_days(),
|
||||||
|
"SITE_NAME": settings.SITE_NAME,
|
||||||
|
},
|
||||||
|
)
|
||||||
send_email(
|
send_email(
|
||||||
'noreply@yourdomain.com',
|
"noreply@yourdomain.com",
|
||||||
inv.customer.email,
|
inv.customer.email,
|
||||||
subject,
|
subject,
|
||||||
message,
|
message,
|
||||||
@ -65,21 +69,24 @@ class Command(BaseCommand):
|
|||||||
"""Queue email reminders for expiring plans"""
|
"""Queue email reminders for expiring plans"""
|
||||||
today = timezone.now().date()
|
today = timezone.now().date()
|
||||||
expiring_plans = InvoiceModel.objects.filter(
|
expiring_plans = InvoiceModel.objects.filter(
|
||||||
date_due__lte = today
|
date_due__lte=today
|
||||||
).select_related('customer','ce_model')
|
).select_related("customer", "ce_model")
|
||||||
|
|
||||||
# Send email
|
# Send email
|
||||||
for inv in expiring_plans:
|
for inv in expiring_plans:
|
||||||
dealer = inv.customer.customer_set.first().dealer
|
dealer = inv.customer.customer_set.first().dealer
|
||||||
|
|
||||||
subject = f"Your invoice is past due"
|
subject = f"Your invoice is past due"
|
||||||
message = render_to_string('emails/invoice_past_due.txt', {
|
message = render_to_string(
|
||||||
'customer_name': inv.customer.customer_name,
|
"emails/invoice_past_due.txt",
|
||||||
'invoice_number': inv.invoice_number,
|
{
|
||||||
'amount_due': inv.amount_due,
|
"customer_name": inv.customer.customer_name,
|
||||||
'days_past_due': (today - inv.date_due).days,
|
"invoice_number": inv.invoice_number,
|
||||||
'SITE_NAME': settings.SITE_NAME
|
"amount_due": inv.amount_due,
|
||||||
})
|
"days_past_due": (today - inv.date_due).days,
|
||||||
|
"SITE_NAME": settings.SITE_NAME,
|
||||||
|
},
|
||||||
|
)
|
||||||
|
|
||||||
# send notification to accountatnt
|
# send notification to accountatnt
|
||||||
recipients = (
|
recipients = (
|
||||||
@ -100,14 +107,18 @@ class Command(BaseCommand):
|
|||||||
invoice_number=inv.invoice_number,
|
invoice_number=inv.invoice_number,
|
||||||
url=reverse(
|
url=reverse(
|
||||||
"invoice_detail",
|
"invoice_detail",
|
||||||
kwargs={"dealer_slug": dealer.slug, "entity_slug": dealer.entity.slug, "pk": inv.pk},
|
kwargs={
|
||||||
|
"dealer_slug": dealer.slug,
|
||||||
|
"entity_slug": dealer.entity.slug,
|
||||||
|
"pk": inv.pk,
|
||||||
|
},
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
)
|
)
|
||||||
|
|
||||||
# send email to customer
|
# send email to customer
|
||||||
send_email(
|
send_email(
|
||||||
'noreply@yourdomain.com',
|
"noreply@yourdomain.com",
|
||||||
inv.customer.email,
|
inv.customer.email,
|
||||||
subject,
|
subject,
|
||||||
message,
|
message,
|
||||||
|
|||||||
@ -2,9 +2,11 @@ from decimal import Decimal
|
|||||||
import random
|
import random
|
||||||
from django.core.management.base import BaseCommand
|
from django.core.management.base import BaseCommand
|
||||||
from inventory.models import Car
|
from inventory.models import Car
|
||||||
from django_ledger.models import EntityModel,InvoiceModel,ItemModel
|
from django_ledger.models import EntityModel, InvoiceModel, ItemModel
|
||||||
from inventory.utils import CarFinanceCalculator
|
from inventory.utils import CarFinanceCalculator
|
||||||
from rich import print
|
from rich import print
|
||||||
|
|
||||||
|
|
||||||
class Command(BaseCommand):
|
class Command(BaseCommand):
|
||||||
help = ""
|
help = ""
|
||||||
|
|
||||||
@ -14,27 +16,43 @@ class Command(BaseCommand):
|
|||||||
admin = e.admin
|
admin = e.admin
|
||||||
# estimate = e.get_estimates().first()
|
# estimate = e.get_estimates().first()
|
||||||
# e.create_invoice(coa_model=e.get_default_coa(), customer_model=customer, terms="net_30")
|
# e.create_invoice(coa_model=e.get_default_coa(), customer_model=customer, terms="net_30")
|
||||||
i=InvoiceModel.objects.first()
|
i = InvoiceModel.objects.first()
|
||||||
|
|
||||||
calc = CarFinanceCalculator(i)
|
calc = CarFinanceCalculator(i)
|
||||||
data = calc.get_finance_data()
|
data = calc.get_finance_data()
|
||||||
for car_data in data['cars']:
|
for car_data in data["cars"]:
|
||||||
car = i.get_itemtxs_data()[0].filter(
|
car = (
|
||||||
item_model__car__vin=car_data['vin']
|
i.get_itemtxs_data()[0]
|
||||||
).first().item_model.car
|
.filter(item_model__car__vin=car_data["vin"])
|
||||||
|
.first()
|
||||||
|
.item_model.car
|
||||||
|
)
|
||||||
print("car", car)
|
print("car", car)
|
||||||
qty = Decimal(car_data['quantity'])
|
qty = Decimal(car_data["quantity"])
|
||||||
print("qty", qty)
|
print("qty", qty)
|
||||||
|
|
||||||
# amounts from calculator
|
# amounts from calculator
|
||||||
net_car_price = Decimal(car_data['total']) # after discount
|
net_car_price = Decimal(car_data["total"]) # after discount
|
||||||
net_add_price = Decimal(data['total_additionals']) # per car or split however you want
|
net_add_price = Decimal(
|
||||||
vat_amount = Decimal(data['total_vat_amount']) * qty # prorate if multi-qty
|
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 = net_car_price + net_add_price + vat_amount
|
||||||
grand_total = Decimal(data['grand_total'])
|
grand_total = Decimal(data["grand_total"])
|
||||||
cost_total = Decimal(car_data['cost_price']) * qty
|
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)
|
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_cars = e.get_coa_accounts().get(name="Inventory (Cars)")
|
||||||
# acc_sales = e.get_coa_accounts().get(name="Car Sales")
|
# acc_sales = e.get_coa_accounts().get(name="Car Sales")
|
||||||
|
|||||||
@ -2,8 +2,11 @@ from django.core.management.base import BaseCommand
|
|||||||
from django.contrib.auth import get_user_model
|
from django.contrib.auth import get_user_model
|
||||||
import datetime
|
import datetime
|
||||||
from inventory.models import Dealer
|
from inventory.models import Dealer
|
||||||
from plans.models import Plan, Order,PlanPricing
|
from plans.models import Plan, Order, PlanPricing
|
||||||
|
|
||||||
User = get_user_model()
|
User = get_user_model()
|
||||||
|
|
||||||
|
|
||||||
class Command(BaseCommand):
|
class Command(BaseCommand):
|
||||||
help = ""
|
help = ""
|
||||||
|
|
||||||
|
|||||||
@ -11,6 +11,7 @@ from inventory.tasks import send_bilingual_reminder, handle_email_result
|
|||||||
|
|
||||||
logger = logging.getLogger(__name__)
|
logger = logging.getLogger(__name__)
|
||||||
|
|
||||||
|
|
||||||
class Command(BaseCommand):
|
class Command(BaseCommand):
|
||||||
help = "Handles subscription plan maintenance tasks"
|
help = "Handles subscription plan maintenance tasks"
|
||||||
|
|
||||||
@ -30,17 +31,18 @@ class Command(BaseCommand):
|
|||||||
|
|
||||||
def send_expiration_reminders(self):
|
def send_expiration_reminders(self):
|
||||||
"""Queue email reminders for expiring plans"""
|
"""Queue email reminders for expiring plans"""
|
||||||
reminder_days = getattr(settings, 'PLANS_EXPIRATION_REMIND', [3, 7, 14])
|
reminder_days = getattr(settings, "PLANS_EXPIRATION_REMIND", [3, 7, 14])
|
||||||
today = timezone.now().date()
|
today = timezone.now().date()
|
||||||
|
|
||||||
for days in reminder_days:
|
for days in reminder_days:
|
||||||
target_date = today + timedelta(days=days)
|
target_date = today + timedelta(days=days)
|
||||||
expiring_plans = UserPlan.objects.filter(
|
expiring_plans = UserPlan.objects.filter(
|
||||||
active=True,
|
active=True, expire=target_date
|
||||||
expire=target_date
|
).select_related("user", "plan")
|
||||||
).select_related('user', 'plan')
|
|
||||||
|
|
||||||
self.stdout.write(f"Queuing {days}-day reminders for {expiring_plans.count()} plans")
|
self.stdout.write(
|
||||||
|
f"Queuing {days}-day reminders for {expiring_plans.count()} plans"
|
||||||
|
)
|
||||||
|
|
||||||
for user_plan in expiring_plans:
|
for user_plan in expiring_plans:
|
||||||
# Queue email task
|
# Queue email task
|
||||||
@ -50,14 +52,13 @@ class Command(BaseCommand):
|
|||||||
user_plan.plan_id,
|
user_plan.plan_id,
|
||||||
user_plan.expire,
|
user_plan.expire,
|
||||||
days,
|
days,
|
||||||
hook=handle_email_result
|
hook=handle_email_result,
|
||||||
)
|
)
|
||||||
|
|
||||||
def deactivate_expired_plans(self):
|
def deactivate_expired_plans(self):
|
||||||
"""Deactivate plans that have expired (synchronous)"""
|
"""Deactivate plans that have expired (synchronous)"""
|
||||||
expired_plans = UserPlan.objects.filter(
|
expired_plans = UserPlan.objects.filter(
|
||||||
active=True,
|
active=True, expire__lt=timezone.now().date()
|
||||||
expire__lt=timezone.now().date()
|
|
||||||
)
|
)
|
||||||
count = expired_plans.update(active=False)
|
count = expired_plans.update(active=False)
|
||||||
self.stdout.write(f"Deactivated {count} expired plans")
|
self.stdout.write(f"Deactivated {count} expired plans")
|
||||||
@ -66,7 +67,6 @@ class Command(BaseCommand):
|
|||||||
"""Delete incomplete orders older than 30 days"""
|
"""Delete incomplete orders older than 30 days"""
|
||||||
cutoff = timezone.now() - timedelta(days=30)
|
cutoff = timezone.now() - timedelta(days=30)
|
||||||
count, _ = Order.objects.filter(
|
count, _ = Order.objects.filter(
|
||||||
created__lt=cutoff,
|
created__lt=cutoff, status=Order.STATUS.NEW
|
||||||
status=Order.STATUS.NEW
|
|
||||||
).delete()
|
).delete()
|
||||||
self.stdout.write(f"Cleaned up {count} old incomplete orders")
|
self.stdout.write(f"Cleaned up {count} old incomplete orders")
|
||||||
@ -5,5 +5,10 @@ from django_q.tasks import async_task, result
|
|||||||
class Command(BaseCommand):
|
class Command(BaseCommand):
|
||||||
def handle(self, *args, **kwargs):
|
def handle(self, *args, **kwargs):
|
||||||
from inventory.models import Dealer
|
from inventory.models import Dealer
|
||||||
|
|
||||||
instance = Dealer.objects.first()
|
instance = Dealer.objects.first()
|
||||||
async_task(func="inventory.tasks.test_task",dealer=instance,hook="inventory.hooks.print_results")
|
async_task(
|
||||||
|
func="inventory.tasks.test_task",
|
||||||
|
dealer=instance,
|
||||||
|
hook="inventory.hooks.print_results",
|
||||||
|
)
|
||||||
|
|||||||
@ -3,21 +3,24 @@ import json, random, string, decimal
|
|||||||
from django.core.management.base import BaseCommand
|
from django.core.management.base import BaseCommand
|
||||||
from django.test import Client
|
from django.test import Client
|
||||||
from django.contrib.auth import get_user_model
|
from django.contrib.auth import get_user_model
|
||||||
from plans.models import Order, PlanPricing, AbstractOrder, UserPlan, BillingInfo,Plan
|
from plans.models import Order, PlanPricing, AbstractOrder, UserPlan, BillingInfo, Plan
|
||||||
from inventory.tasks import create_user_dealer
|
from inventory.tasks import create_user_dealer
|
||||||
from inventory import models # adjust import to your app
|
from inventory import models # adjust import to your app
|
||||||
from django_q.tasks import async_task
|
from django_q.tasks import async_task
|
||||||
|
|
||||||
User = get_user_model()
|
User = get_user_model()
|
||||||
|
|
||||||
|
|
||||||
class Command(BaseCommand):
|
class Command(BaseCommand):
|
||||||
help = "Seed a full dealership via the real signup & downstream views"
|
help = "Seed a full dealership via the real signup & downstream views"
|
||||||
|
|
||||||
def add_arguments(self, parser):
|
def add_arguments(self, parser):
|
||||||
parser.add_argument('--count', type=int, default=1, help='Number of dealers to seed')
|
parser.add_argument(
|
||||||
|
"--count", type=int, default=1, help="Number of dealers to seed"
|
||||||
|
)
|
||||||
|
|
||||||
def handle(self, *args, **opts):
|
def handle(self, *args, **opts):
|
||||||
count = opts['count']
|
count = opts["count"]
|
||||||
client = Client() # lives inside management command
|
client = Client() # lives inside management command
|
||||||
|
|
||||||
for n in range(6, 9):
|
for n in range(6, 9):
|
||||||
@ -43,7 +46,16 @@ class Command(BaseCommand):
|
|||||||
"address": f"Street {n}, Riyadh",
|
"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'])
|
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
|
user = dealer.user
|
||||||
self._assign_random_plan(user)
|
self._assign_random_plan(user)
|
||||||
self._services(dealer)
|
self._services(dealer)
|
||||||
@ -61,7 +73,7 @@ class Command(BaseCommand):
|
|||||||
|
|
||||||
return payload["email"]
|
return payload["email"]
|
||||||
|
|
||||||
def _assign_random_plan(self,user):
|
def _assign_random_plan(self, user):
|
||||||
"""
|
"""
|
||||||
Pick a random Plan and create + initialize a UserPlan for the user.
|
Pick a random Plan and create + initialize a UserPlan for the user.
|
||||||
"""
|
"""
|
||||||
@ -72,14 +84,13 @@ class Command(BaseCommand):
|
|||||||
plan = random.choice(plans)
|
plan = random.choice(plans)
|
||||||
|
|
||||||
user_plan, created = UserPlan.objects.get_or_create(
|
user_plan, created = UserPlan.objects.get_or_create(
|
||||||
user=user,
|
user=user, defaults={"plan": plan, "active": True}
|
||||||
defaults={'plan': plan, 'active': True}
|
|
||||||
)
|
)
|
||||||
if created:
|
if created:
|
||||||
user_plan.initialize()
|
user_plan.initialize()
|
||||||
return user_plan
|
return user_plan
|
||||||
|
|
||||||
def _services(self,dealer):
|
def _services(self, dealer):
|
||||||
additional_services = [
|
additional_services = [
|
||||||
{
|
{
|
||||||
"name": "Vehicle registration transfer assistance",
|
"name": "Vehicle registration transfer assistance",
|
||||||
@ -114,5 +125,5 @@ class Command(BaseCommand):
|
|||||||
price=additional_service["price"],
|
price=additional_service["price"],
|
||||||
description=additional_service["description"],
|
description=additional_service["description"],
|
||||||
dealer=dealer,
|
dealer=dealer,
|
||||||
uom="Unit"
|
uom="Unit",
|
||||||
)
|
)
|
||||||
|
|||||||
@ -4,11 +4,31 @@ import json, random, string, decimal
|
|||||||
from django.core.management.base import BaseCommand
|
from django.core.management.base import BaseCommand
|
||||||
from django.test import Client
|
from django.test import Client
|
||||||
from django.contrib.auth import get_user_model
|
from django.contrib.auth import get_user_model
|
||||||
from plans.models import Order, PlanPricing, AbstractOrder, UserPlan, BillingInfo,Plan
|
from plans.models import Order, PlanPricing, AbstractOrder, UserPlan, BillingInfo, Plan
|
||||||
from inventory.services import decodevin
|
from inventory.services import decodevin
|
||||||
from inventory.tasks import create_user_dealer
|
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 inventory.models import (
|
||||||
from django_ledger.models import PurchaseOrderModel,ItemTransactionModel,ItemModel,EntityModel
|
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 django_q.tasks import async_task
|
||||||
from faker import Faker
|
from faker import Faker
|
||||||
from appointment.models import Appointment, AppointmentRequest, Service, StaffMember
|
from appointment.models import Appointment, AppointmentRequest, Service, StaffMember
|
||||||
@ -16,6 +36,7 @@ from appointment.models import Appointment, AppointmentRequest, Service, StaffMe
|
|||||||
User = get_user_model()
|
User = get_user_model()
|
||||||
fake = Faker()
|
fake = Faker()
|
||||||
|
|
||||||
|
|
||||||
class Command(BaseCommand):
|
class Command(BaseCommand):
|
||||||
help = "Seed a full dealership via the real signup & downstream views"
|
help = "Seed a full dealership via the real signup & downstream views"
|
||||||
|
|
||||||
@ -31,7 +52,6 @@ class Command(BaseCommand):
|
|||||||
# self._create_randome_services(dealer)
|
# self._create_randome_services(dealer)
|
||||||
# self._create_random_lead(dealer)
|
# self._create_random_lead(dealer)
|
||||||
|
|
||||||
|
|
||||||
# dealer = Dealer.objects.get(name="Dealer #6")
|
# dealer = Dealer.objects.get(name="Dealer #6")
|
||||||
# coa_model = dealer.entity.get_default_coa()
|
# coa_model = dealer.entity.get_default_coa()
|
||||||
# inventory_account = dealer.entity.get_all_accounts().get(name="Inventory (Cars)")
|
# inventory_account = dealer.entity.get_all_accounts().get(name="Inventory (Cars)")
|
||||||
@ -43,20 +63,32 @@ class Command(BaseCommand):
|
|||||||
self.stdout.write(self.style.SUCCESS(f"✅ PO created for {dealers}"))
|
self.stdout.write(self.style.SUCCESS(f"✅ PO created for {dealers}"))
|
||||||
|
|
||||||
def _create_random_po(self, dealer):
|
def _create_random_po(self, dealer):
|
||||||
for i in range(random.randint(1,70)):
|
for i in range(random.randint(1, 70)):
|
||||||
try:
|
try:
|
||||||
e: EntityModel = dealer.entity
|
e: EntityModel = dealer.entity
|
||||||
e.create_purchase_order(po_title=f"Test PO {random.randint(1,9999)}-{i}")
|
e.create_purchase_order(
|
||||||
|
po_title=f"Test PO {random.randint(1, 9999)}-{i}"
|
||||||
|
)
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
self.stderr.write(self.style.ERROR(f"Error : {e}"))
|
self.stderr.write(self.style.ERROR(f"Error : {e}"))
|
||||||
|
|
||||||
def _create_random_vendors(self, dealer):
|
def _create_random_vendors(self, dealer):
|
||||||
for i in range(random.randint(1,50)):
|
for i in range(random.randint(1, 50)):
|
||||||
try:
|
try:
|
||||||
name = fake.name()
|
name = fake.name()
|
||||||
n = random.randint(1,9999)
|
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)}"
|
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}")
|
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:
|
except Exception as e:
|
||||||
pass
|
pass
|
||||||
|
|
||||||
@ -65,7 +97,9 @@ class Command(BaseCommand):
|
|||||||
name = f"{fake.name()}{i}"
|
name = f"{fake.name()}{i}"
|
||||||
email = fake.email()
|
email = fake.email()
|
||||||
password = "Tenhal@123"
|
password = "Tenhal@123"
|
||||||
user = User.objects.create_user(username=email, email=email, password=password)
|
user = User.objects.create_user(
|
||||||
|
username=email, email=email, password=password
|
||||||
|
)
|
||||||
user.is_staff = True
|
user.is_staff = True
|
||||||
user.save()
|
user.save()
|
||||||
|
|
||||||
@ -74,7 +108,14 @@ class Command(BaseCommand):
|
|||||||
# for service in services:
|
# for service in services:
|
||||||
# staff_member.services_offered.add(service)
|
# staff_member.services_offered.add(service)
|
||||||
|
|
||||||
staff = Staff.objects.create(dealer=dealer,user=user,name=name,arabic_name=name,phone_number=fake.phone_number(),active=True)
|
staff = Staff.objects.create(
|
||||||
|
dealer=dealer,
|
||||||
|
user=user,
|
||||||
|
name=name,
|
||||||
|
arabic_name=name,
|
||||||
|
phone_number=fake.phone_number(),
|
||||||
|
active=True,
|
||||||
|
)
|
||||||
|
|
||||||
groups = CustomGroup.objects.filter(dealer=dealer)
|
groups = CustomGroup.objects.filter(dealer=dealer)
|
||||||
random_group = random.choice(list(groups))
|
random_group = random.choice(list(groups))
|
||||||
@ -84,7 +125,7 @@ class Command(BaseCommand):
|
|||||||
# phone = f"05678{random.randint(1,9999)}{random.randint(1,9999)}{random.randint(1,9999)}{random.randint(1,9999)}{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}")
|
# 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):
|
def _create_random_cars(self, dealer):
|
||||||
vendors = Vendor.objects.filter(dealer=dealer).all()
|
vendors = Vendor.objects.filter(dealer=dealer).all()
|
||||||
|
|
||||||
vin_list = [
|
vin_list = [
|
||||||
@ -103,18 +144,20 @@ class Command(BaseCommand):
|
|||||||
]
|
]
|
||||||
for vin in vin_list:
|
for vin in vin_list:
|
||||||
try:
|
try:
|
||||||
for _ in range(random.randint(1,2)):
|
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)}"
|
vin = f"{vin[:-4]}{random.randint(0, 9)}{random.randint(0, 9)}{random.randint(0, 9)}{random.randint(0, 9)}"
|
||||||
result = decodevin(vin)
|
result = decodevin(vin)
|
||||||
make = CarMake.objects.get(name=result["maker"])
|
make = CarMake.objects.get(name=result["maker"])
|
||||||
model = make.carmodel_set.filter(name__contains=result["model"]).first()
|
model = make.carmodel_set.filter(
|
||||||
|
name__contains=result["model"]
|
||||||
|
).first()
|
||||||
if not model or model == "":
|
if not model or model == "":
|
||||||
model = random.choice(make.carmodel_set.all())
|
model = random.choice(make.carmodel_set.all())
|
||||||
year = result["modelYear"]
|
year = result["modelYear"]
|
||||||
serie = random.choice(model.carserie_set.all())
|
serie = random.choice(model.carserie_set.all())
|
||||||
trim = random.choice(serie.cartrim_set.all())
|
trim = random.choice(serie.cartrim_set.all())
|
||||||
vendor = random.choice(vendors)
|
vendor = random.choice(vendors)
|
||||||
print(make, model, serie, trim, vendor,vin)
|
print(make, model, serie, trim, vendor, vin)
|
||||||
car = Car.objects.create(
|
car = Car.objects.create(
|
||||||
vin=vin,
|
vin=vin,
|
||||||
id_car_make=make,
|
id_car_make=make,
|
||||||
@ -128,9 +171,12 @@ class Command(BaseCommand):
|
|||||||
mileage=0,
|
mileage=0,
|
||||||
)
|
)
|
||||||
print(car)
|
print(car)
|
||||||
cp=random.randint(10000, 100000)
|
cp = random.randint(10000, 100000)
|
||||||
CarFinance.objects.create(
|
CarFinance.objects.create(
|
||||||
car=car, cost_price=cp, selling_price=0,marked_price=cp+random.randint(2000, 7000)
|
car=car,
|
||||||
|
cost_price=cp,
|
||||||
|
selling_price=0,
|
||||||
|
marked_price=cp + random.randint(2000, 7000),
|
||||||
)
|
)
|
||||||
CarColors.objects.create(
|
CarColors.objects.create(
|
||||||
car=car,
|
car=car,
|
||||||
@ -141,8 +187,8 @@ class Command(BaseCommand):
|
|||||||
except Exception as e:
|
except Exception as e:
|
||||||
print(e)
|
print(e)
|
||||||
|
|
||||||
def _create_random_customers(self,dealer):
|
def _create_random_customers(self, dealer):
|
||||||
for i in range(random.randint(1,60)):
|
for i in range(random.randint(1, 60)):
|
||||||
try:
|
try:
|
||||||
c = Customer(
|
c = Customer(
|
||||||
dealer=dealer,
|
dealer=dealer,
|
||||||
@ -161,7 +207,7 @@ class Command(BaseCommand):
|
|||||||
except Exception as e:
|
except Exception as e:
|
||||||
pass
|
pass
|
||||||
|
|
||||||
def _create_randome_services(self,dealer):
|
def _create_randome_services(self, dealer):
|
||||||
additional_services = [
|
additional_services = [
|
||||||
{
|
{
|
||||||
"name": "Vehicle registration transfer assistance",
|
"name": "Vehicle registration transfer assistance",
|
||||||
@ -196,12 +242,11 @@ class Command(BaseCommand):
|
|||||||
price=additional_service["price"],
|
price=additional_service["price"],
|
||||||
description=additional_service["description"],
|
description=additional_service["description"],
|
||||||
dealer=dealer,
|
dealer=dealer,
|
||||||
uom=uom
|
uom=uom,
|
||||||
)
|
)
|
||||||
|
|
||||||
|
def _create_random_lead(self, dealer):
|
||||||
def _create_random_lead(self,dealer):
|
for i in range(random.randint(1, 60)):
|
||||||
for i in range(random.randint(1,60)):
|
|
||||||
try:
|
try:
|
||||||
first_name = fake.name()
|
first_name = fake.name()
|
||||||
last_name = fake.last_name()
|
last_name = fake.last_name()
|
||||||
@ -224,7 +269,7 @@ class Command(BaseCommand):
|
|||||||
id_car_model=model,
|
id_car_model=model,
|
||||||
source="website",
|
source="website",
|
||||||
channel="website",
|
channel="website",
|
||||||
staff=staff
|
staff=staff,
|
||||||
)
|
)
|
||||||
c = Customer(
|
c = Customer(
|
||||||
dealer=dealer,
|
dealer=dealer,
|
||||||
|
|||||||
@ -152,11 +152,22 @@ class DealerSlugMiddleware:
|
|||||||
|
|
||||||
def process_view(self, request, view_func, view_args, view_kwargs):
|
def process_view(self, request, view_func, view_args, view_kwargs):
|
||||||
paths = [
|
paths = [
|
||||||
"/ar/signup/", "/en/signup/", "/ar/login/", "/en/login/",
|
"/ar/signup/",
|
||||||
"/ar/logout/", "/en/logout/", "/en/ledger/", "/ar/ledger/",
|
"/en/signup/",
|
||||||
"/en/notifications/", "/ar/notifications/", "/en/appointment/",
|
"/ar/login/",
|
||||||
"/ar/appointment/", "/en/feature/recall/","/ar/feature/recall/",
|
"/en/login/",
|
||||||
"/ar/help_center/", "/en/help_center/",
|
"/ar/logout/",
|
||||||
|
"/en/logout/",
|
||||||
|
"/en/ledger/",
|
||||||
|
"/ar/ledger/",
|
||||||
|
"/en/notifications/",
|
||||||
|
"/ar/notifications/",
|
||||||
|
"/en/appointment/",
|
||||||
|
"/ar/appointment/",
|
||||||
|
"/en/feature/recall/",
|
||||||
|
"/ar/feature/recall/",
|
||||||
|
"/ar/help_center/",
|
||||||
|
"/en/help_center/",
|
||||||
]
|
]
|
||||||
print("------------------------------------")
|
print("------------------------------------")
|
||||||
print(request.path in paths)
|
print(request.path in paths)
|
||||||
|
|||||||
@ -42,12 +42,14 @@ from django_ledger.models import (
|
|||||||
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
|
||||||
from django.core.serializers.json import DjangoJSONEncoder
|
from django.core.serializers.json import DjangoJSONEncoder
|
||||||
|
|
||||||
# from appointment.models import StaffMember
|
# from appointment.models import StaffMember
|
||||||
from plans.quota import get_user_quota
|
from plans.quota import get_user_quota
|
||||||
from plans.models import UserPlan
|
from plans.models import UserPlan
|
||||||
from django.db.models import Q
|
from django.db.models import Q
|
||||||
from imagekit.models import ImageSpecField
|
from imagekit.models import ImageSpecField
|
||||||
from imagekit.processors import ResizeToFill
|
from imagekit.processors import ResizeToFill
|
||||||
|
|
||||||
# from plans.models import AbstractPlan
|
# from plans.models import AbstractPlan
|
||||||
# from simple_history.models import HistoricalRecords
|
# from simple_history.models import HistoricalRecords
|
||||||
from plans.models import Invoice
|
from plans.models import Invoice
|
||||||
@ -229,7 +231,9 @@ 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,default="user-logo.jpg")
|
logo = models.ImageField(
|
||||||
|
_("logo"), upload_to="car_make", blank=True, null=True, default="user-logo.jpg"
|
||||||
|
)
|
||||||
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)
|
||||||
|
|
||||||
@ -589,7 +593,7 @@ class AdditionalServices(models.Model, LocalizedNameMixin):
|
|||||||
"price_": str(self.price_),
|
"price_": str(self.price_),
|
||||||
"taxable": self.taxable,
|
"taxable": self.taxable,
|
||||||
"uom": self.uom,
|
"uom": self.uom,
|
||||||
"service_tax":str(self.service_tax)
|
"service_tax": str(self.service_tax),
|
||||||
}
|
}
|
||||||
|
|
||||||
@property
|
@property
|
||||||
@ -604,9 +608,7 @@ class AdditionalServices(models.Model, LocalizedNameMixin):
|
|||||||
@property
|
@property
|
||||||
def service_tax(self):
|
def service_tax(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 * vat.rate)
|
||||||
Decimal(self.price * vat.rate)
|
|
||||||
)
|
|
||||||
|
|
||||||
class Meta:
|
class Meta:
|
||||||
verbose_name = _("Additional Services")
|
verbose_name = _("Additional Services")
|
||||||
@ -683,10 +685,13 @@ class Car(Base):
|
|||||||
)
|
)
|
||||||
#
|
#
|
||||||
additional_services = models.ManyToManyField(
|
additional_services = models.ManyToManyField(
|
||||||
AdditionalServices, related_name="additionals", blank=True,null=True
|
AdditionalServices, related_name="additionals", blank=True, null=True
|
||||||
)
|
)
|
||||||
cost_price = models.DecimalField(
|
cost_price = models.DecimalField(
|
||||||
max_digits=14, decimal_places=2, verbose_name=_("Cost Price"),default=Decimal("0.00")
|
max_digits=14,
|
||||||
|
decimal_places=2,
|
||||||
|
verbose_name=_("Cost Price"),
|
||||||
|
default=Decimal("0.00"),
|
||||||
)
|
)
|
||||||
selling_price = models.DecimalField(
|
selling_price = models.DecimalField(
|
||||||
max_digits=14,
|
max_digits=14,
|
||||||
@ -710,7 +715,7 @@ 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"),null=True,blank=True)
|
sold_date = models.DateTimeField(verbose_name=_("Sold Date"), null=True, blank=True)
|
||||||
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")
|
||||||
)
|
)
|
||||||
@ -773,6 +778,7 @@ class Car(Base):
|
|||||||
@property
|
@property
|
||||||
def logo(self):
|
def logo(self):
|
||||||
return getattr(self.id_car_make, "logo", "")
|
return getattr(self.id_car_make, "logo", "")
|
||||||
|
|
||||||
# @property
|
# @property
|
||||||
# def additional_services(self):
|
# def additional_services(self):
|
||||||
# return self.additional_services.all()
|
# return self.additional_services.all()
|
||||||
@ -787,9 +793,15 @@ class Car(Base):
|
|||||||
)
|
)
|
||||||
except Exception:
|
except Exception:
|
||||||
return False
|
return False
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def invoice(self):
|
def invoice(self):
|
||||||
return self.item_model.invoicemodel_set.first if self.item_model.invoicemodel_set.first() else None
|
return (
|
||||||
|
self.item_model.invoicemodel_set.first
|
||||||
|
if self.item_model.invoicemodel_set.first()
|
||||||
|
else None
|
||||||
|
)
|
||||||
|
|
||||||
def get_transfer(self):
|
def get_transfer(self):
|
||||||
return self.transfer_logs.filter(active=True).first()
|
return self.transfer_logs.filter(active=True).first()
|
||||||
|
|
||||||
@ -873,39 +885,54 @@ 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
|
||||||
|
|
||||||
#
|
#
|
||||||
@property
|
@property
|
||||||
def get_additional_services_amount(self):
|
def get_additional_services_amount(self):
|
||||||
return sum([Decimal(x.price) for x in self.additional_services.all()])
|
return sum([Decimal(x.price) for x in self.additional_services.all()])
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def get_additional_services_amount_(self):
|
def get_additional_services_amount_(self):
|
||||||
return sum([Decimal(x.price_) for x in self.additional_services.all()])
|
return sum([Decimal(x.price_) for x in self.additional_services.all()])
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def get_additional_services_vat(self):
|
def get_additional_services_vat(self):
|
||||||
vat = VatRate.objects.filter(dealer=self.dealer,is_active=True).first()
|
vat = VatRate.objects.filter(dealer=self.dealer, is_active=True).first()
|
||||||
return sum([Decimal((x.price)*(vat.rate)) for x in self.additional_services.filter(taxable=True)])
|
return sum(
|
||||||
|
[
|
||||||
|
Decimal((x.price) * (vat.rate))
|
||||||
|
for x in self.additional_services.filter(taxable=True)
|
||||||
|
]
|
||||||
|
)
|
||||||
|
|
||||||
def get_additional_services(self):
|
def get_additional_services(self):
|
||||||
vat = VatRate.objects.filter(dealer=self.dealer,is_active=True).first()
|
vat = VatRate.objects.filter(dealer=self.dealer, is_active=True).first()
|
||||||
return {"services": [[x,((x.price)*(vat.rate) if x.taxable else 0)] for x in self.additional_services.all()],
|
return {
|
||||||
"total_":self.get_additional_services_amount_,
|
"services": [
|
||||||
"total":self.get_additional_services_amount,
|
[x, ((x.price) * (vat.rate) if x.taxable else 0)]
|
||||||
"services_vat":self.get_additional_services_vat}
|
for x in self.additional_services.all()
|
||||||
|
],
|
||||||
|
"total_": self.get_additional_services_amount_,
|
||||||
|
"total": self.get_additional_services_amount,
|
||||||
|
"services_vat": self.get_additional_services_vat,
|
||||||
|
}
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def final_price(self):
|
def final_price(self):
|
||||||
return Decimal(self.marked_price -self.discount)
|
return Decimal(self.marked_price - self.discount)
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def vat_amount(self):
|
def vat_amount(self):
|
||||||
vat = VatRate.objects.filter(dealer=self.dealer,is_active=True).first()
|
vat = VatRate.objects.filter(dealer=self.dealer, is_active=True).first()
|
||||||
return Decimal(self.final_price) * (vat.rate)
|
return Decimal(self.final_price) * (vat.rate)
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def total_services_and_car_vat(self):
|
def total_services_and_car_vat(self):
|
||||||
return self.vat_amount+self.get_additional_services()['services_vat']
|
return self.vat_amount + self.get_additional_services()["services_vat"]
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def final_price_plus_vat(self):
|
def final_price_plus_vat(self):
|
||||||
@ -913,14 +940,19 @@ class Car(Base):
|
|||||||
|
|
||||||
@property
|
@property
|
||||||
def final_price_plus_services_plus_vat(self):
|
def final_price_plus_services_plus_vat(self):
|
||||||
return Decimal(self.final_price_plus_vat) + Decimal(self.get_additional_services()['total_']) #total services with vat and car_sell price with vat
|
return Decimal(self.final_price_plus_vat) + Decimal(
|
||||||
|
self.get_additional_services()["total_"]
|
||||||
|
) # total services with vat and car_sell price with vat
|
||||||
|
|
||||||
# to be used after invoice is created
|
# to be used after invoice is created
|
||||||
@property
|
@property
|
||||||
def invoice(self):
|
def invoice(self):
|
||||||
return self.item_model.invoicemodel_set.first() or None
|
return self.item_model.invoicemodel_set.first() or None
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def estimate(self):
|
def estimate(self):
|
||||||
return getattr(self.invoice,'ce_model',None)
|
return getattr(self.invoice, "ce_model", None)
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def discount(self):
|
def discount(self):
|
||||||
if not self.estimate:
|
if not self.estimate:
|
||||||
@ -931,12 +963,10 @@ class Car(Base):
|
|||||||
content_type=ContentType.objects.get_for_model(EstimateModel),
|
content_type=ContentType.objects.get_for_model(EstimateModel),
|
||||||
object_id=self.estimate.pk,
|
object_id=self.estimate.pk,
|
||||||
)
|
)
|
||||||
return Decimal(instance.data.get('discount',0))
|
return Decimal(instance.data.get("discount", 0))
|
||||||
except ExtraInfo.DoesNotExist:
|
except ExtraInfo.DoesNotExist:
|
||||||
return Decimal(0)
|
return Decimal(0)
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
# def get_discount_amount(self,estimate,user):
|
# def get_discount_amount(self,estimate,user):
|
||||||
# try:
|
# try:
|
||||||
# instance = models.ExtraInfo.objects.get(
|
# instance = models.ExtraInfo.objects.get(
|
||||||
@ -961,10 +991,6 @@ class Car(Base):
|
|||||||
# return round(self.total_discount + self.vat_amount + self.total_additionals, 2)
|
# return round(self.total_discount + self.vat_amount + self.total_additionals, 2)
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
class CarTransfer(models.Model):
|
class CarTransfer(models.Model):
|
||||||
car = models.ForeignKey(
|
car = models.ForeignKey(
|
||||||
"Car",
|
"Car",
|
||||||
@ -1311,7 +1337,11 @@ class Dealer(models.Model, LocalizedNameMixin):
|
|||||||
)
|
)
|
||||||
arabic_name = models.CharField(max_length=255, verbose_name=_("Arabic Name"))
|
arabic_name = models.CharField(max_length=255, verbose_name=_("Arabic Name"))
|
||||||
name = models.CharField(max_length=255, verbose_name=_("English Name"))
|
name = models.CharField(max_length=255, verbose_name=_("English Name"))
|
||||||
phone_number = models.CharField(max_length=255, verbose_name=_("Phone Number"),validators=[SaudiPhoneNumberValidator()])
|
phone_number = models.CharField(
|
||||||
|
max_length=255,
|
||||||
|
verbose_name=_("Phone Number"),
|
||||||
|
validators=[SaudiPhoneNumberValidator()],
|
||||||
|
)
|
||||||
address = models.CharField(
|
address = models.CharField(
|
||||||
max_length=200, blank=True, null=True, verbose_name=_("Address")
|
max_length=200, blank=True, null=True, verbose_name=_("Address")
|
||||||
)
|
)
|
||||||
@ -1365,6 +1395,7 @@ class Dealer(models.Model, LocalizedNameMixin):
|
|||||||
@property
|
@property
|
||||||
def customers(self):
|
def customers(self):
|
||||||
return models.Customer.objects.filter(dealer=self)
|
return models.Customer.objects.filter(dealer=self)
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def user_quota(self):
|
def user_quota(self):
|
||||||
try:
|
try:
|
||||||
@ -1415,6 +1446,7 @@ class Dealer(models.Model, LocalizedNameMixin):
|
|||||||
def invoices(self):
|
def invoices(self):
|
||||||
return Invoice.objects.filter(order__user=self.user)
|
return Invoice.objects.filter(order__user=self.user)
|
||||||
|
|
||||||
|
|
||||||
class StaffTypes(models.TextChoices):
|
class StaffTypes(models.TextChoices):
|
||||||
# MANAGER = "manager", _("Manager")
|
# MANAGER = "manager", _("Manager")
|
||||||
INVENTORY = "inventory", _("Inventory")
|
INVENTORY = "inventory", _("Inventory")
|
||||||
@ -1429,15 +1461,17 @@ class Staff(models.Model):
|
|||||||
# staff_member = models.OneToOneField(
|
# staff_member = models.OneToOneField(
|
||||||
# StaffMember, on_delete=models.CASCADE, related_name="staff"
|
# StaffMember, on_delete=models.CASCADE, related_name="staff"
|
||||||
# )
|
# )
|
||||||
user = models.OneToOneField(
|
user = models.OneToOneField(User, on_delete=models.CASCADE, related_name="staff")
|
||||||
User, on_delete=models.CASCADE, related_name="staff"
|
|
||||||
)
|
|
||||||
dealer = models.ForeignKey(Dealer, on_delete=models.CASCADE, related_name="staff")
|
dealer = models.ForeignKey(Dealer, on_delete=models.CASCADE, related_name="staff")
|
||||||
first_name = models.CharField(max_length=255, verbose_name=_("First Name"))
|
first_name = models.CharField(max_length=255, verbose_name=_("First Name"))
|
||||||
last_name = models.CharField(max_length=255, verbose_name=_("Last Name"))
|
last_name = models.CharField(max_length=255, verbose_name=_("Last Name"))
|
||||||
|
|
||||||
arabic_name = models.CharField(max_length=255, verbose_name=_("Arabic Name"))
|
arabic_name = models.CharField(max_length=255, verbose_name=_("Arabic Name"))
|
||||||
phone_number = models.CharField(max_length=255, verbose_name=_("Phone Number"),validators=[SaudiPhoneNumberValidator()])
|
phone_number = models.CharField(
|
||||||
|
max_length=255,
|
||||||
|
verbose_name=_("Phone Number"),
|
||||||
|
validators=[SaudiPhoneNumberValidator()],
|
||||||
|
)
|
||||||
staff_type = models.CharField(
|
staff_type = models.CharField(
|
||||||
choices=StaffTypes.choices, max_length=255, verbose_name=_("Staff Type")
|
choices=StaffTypes.choices, max_length=255, verbose_name=_("Staff Type")
|
||||||
)
|
)
|
||||||
@ -1445,7 +1479,11 @@ class Staff(models.Model):
|
|||||||
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"),default="default-image/user.jpg"
|
upload_to="logos/staff",
|
||||||
|
blank=True,
|
||||||
|
null=True,
|
||||||
|
verbose_name=_("Image"),
|
||||||
|
default="default-image/user.jpg",
|
||||||
)
|
)
|
||||||
thumbnail = ImageSpecField(
|
thumbnail = ImageSpecField(
|
||||||
source="logo",
|
source="logo",
|
||||||
@ -1480,6 +1518,7 @@ class Staff(models.Model):
|
|||||||
@property
|
@property
|
||||||
def fullname(self):
|
def fullname(self):
|
||||||
return self.first_name + " " + self.last_name
|
return self.first_name + " " + self.last_name
|
||||||
|
|
||||||
def deactivate_account(self):
|
def deactivate_account(self):
|
||||||
self.active = False
|
self.active = False
|
||||||
self.user.is_active = False
|
self.user.is_active = False
|
||||||
@ -1544,8 +1583,7 @@ class Staff(models.Model):
|
|||||||
permissions = []
|
permissions = []
|
||||||
constraints = [
|
constraints = [
|
||||||
models.UniqueConstraint(
|
models.UniqueConstraint(
|
||||||
fields=['dealer', 'user'],
|
fields=["dealer", "user"], name="unique_staff_email_per_dealer"
|
||||||
name='unique_staff_email_per_dealer'
|
|
||||||
)
|
)
|
||||||
]
|
]
|
||||||
|
|
||||||
@ -1648,7 +1686,11 @@ class Customer(models.Model):
|
|||||||
CustomerModel, on_delete=models.SET_NULL, null=True
|
CustomerModel, on_delete=models.SET_NULL, null=True
|
||||||
)
|
)
|
||||||
user = models.OneToOneField(
|
user = models.OneToOneField(
|
||||||
User, on_delete=models.CASCADE, related_name="customer_profile", null=True, blank=True
|
User,
|
||||||
|
on_delete=models.CASCADE,
|
||||||
|
related_name="customer_profile",
|
||||||
|
null=True,
|
||||||
|
blank=True,
|
||||||
)
|
)
|
||||||
title = models.CharField(
|
title = models.CharField(
|
||||||
choices=Title.choices, default=Title.NA, max_length=10, verbose_name=_("Title")
|
choices=Title.choices, default=Title.NA, max_length=10, verbose_name=_("Title")
|
||||||
@ -1676,7 +1718,11 @@ class Customer(models.Model):
|
|||||||
)
|
)
|
||||||
active = models.BooleanField(default=True, verbose_name=_("Active"))
|
active = models.BooleanField(default=True, verbose_name=_("Active"))
|
||||||
image = models.ImageField(
|
image = models.ImageField(
|
||||||
upload_to="customers/", blank=True, null=True, verbose_name=_("Image"),default="default-image/user-jpg"
|
upload_to="customers/",
|
||||||
|
blank=True,
|
||||||
|
null=True,
|
||||||
|
verbose_name=_("Image"),
|
||||||
|
default="default-image/user-jpg",
|
||||||
)
|
)
|
||||||
thumbnail = ImageSpecField(
|
thumbnail = ImageSpecField(
|
||||||
source="image",
|
source="image",
|
||||||
@ -1708,8 +1754,7 @@ class Customer(models.Model):
|
|||||||
class Meta:
|
class Meta:
|
||||||
constraints = [
|
constraints = [
|
||||||
models.UniqueConstraint(
|
models.UniqueConstraint(
|
||||||
fields=['dealer', 'email'],
|
fields=["dealer", "email"], name="unique_customer_email_per_dealer"
|
||||||
name='unique_customer_email_per_dealer'
|
|
||||||
)
|
)
|
||||||
]
|
]
|
||||||
verbose_name = _("Customer")
|
verbose_name = _("Customer")
|
||||||
@ -1779,13 +1824,13 @@ class Customer(models.Model):
|
|||||||
user, created = User.objects.get_or_create(
|
user, created = User.objects.get_or_create(
|
||||||
username=self.email,
|
username=self.email,
|
||||||
defaults={
|
defaults={
|
||||||
'email': self.email,
|
"email": self.email,
|
||||||
'first_name': self.first_name,
|
"first_name": self.first_name,
|
||||||
'last_name': self.last_name,
|
"last_name": self.last_name,
|
||||||
'password': make_random_password(),
|
"password": make_random_password(),
|
||||||
'is_staff': False,
|
"is_staff": False,
|
||||||
'is_superuser': False,
|
"is_superuser": False,
|
||||||
'is_active': False if for_lead else True,
|
"is_active": False if for_lead else True,
|
||||||
},
|
},
|
||||||
)
|
)
|
||||||
self.user = user
|
self.user = user
|
||||||
@ -1822,7 +1867,11 @@ class Organization(models.Model, LocalizedNameMixin):
|
|||||||
CustomerModel, on_delete=models.SET_NULL, null=True
|
CustomerModel, on_delete=models.SET_NULL, null=True
|
||||||
)
|
)
|
||||||
user = models.OneToOneField(
|
user = models.OneToOneField(
|
||||||
User, on_delete=models.CASCADE, related_name="organization_profile", null=True, blank=True
|
User,
|
||||||
|
on_delete=models.CASCADE,
|
||||||
|
related_name="organization_profile",
|
||||||
|
null=True,
|
||||||
|
blank=True,
|
||||||
)
|
)
|
||||||
name = models.CharField(max_length=255, verbose_name=_("Name"))
|
name = models.CharField(max_length=255, verbose_name=_("Name"))
|
||||||
arabic_name = models.CharField(max_length=255, verbose_name=_("Arabic Name"))
|
arabic_name = models.CharField(max_length=255, verbose_name=_("Arabic Name"))
|
||||||
@ -1831,12 +1880,20 @@ class Organization(models.Model, LocalizedNameMixin):
|
|||||||
)
|
)
|
||||||
vrn = models.CharField(max_length=15, verbose_name=_("VAT Registration Number"))
|
vrn = models.CharField(max_length=15, verbose_name=_("VAT Registration Number"))
|
||||||
email = models.EmailField(verbose_name=_("Email"))
|
email = models.EmailField(verbose_name=_("Email"))
|
||||||
phone_number = models.CharField(max_length=255, verbose_name=_("Phone Number"),validators=[SaudiPhoneNumberValidator()])
|
phone_number = models.CharField(
|
||||||
|
max_length=255,
|
||||||
|
verbose_name=_("Phone Number"),
|
||||||
|
validators=[SaudiPhoneNumberValidator()],
|
||||||
|
)
|
||||||
address = models.CharField(
|
address = models.CharField(
|
||||||
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"),default="default-image/user.jpg"
|
upload_to="logos",
|
||||||
|
blank=True,
|
||||||
|
null=True,
|
||||||
|
verbose_name=_("Logo"),
|
||||||
|
default="default-image/user.jpg",
|
||||||
)
|
)
|
||||||
thumbnail = ImageSpecField(
|
thumbnail = ImageSpecField(
|
||||||
source="logo",
|
source="logo",
|
||||||
@ -1965,7 +2022,11 @@ class Representative(models.Model, LocalizedNameMixin):
|
|||||||
id_number = models.CharField(
|
id_number = models.CharField(
|
||||||
max_length=10, unique=True, verbose_name=_("ID Number")
|
max_length=10, unique=True, verbose_name=_("ID Number")
|
||||||
)
|
)
|
||||||
phone_number = models.CharField(max_length=255, verbose_name=_("Phone Number"),validators=[SaudiPhoneNumberValidator()])
|
phone_number = models.CharField(
|
||||||
|
max_length=255,
|
||||||
|
verbose_name=_("Phone Number"),
|
||||||
|
validators=[SaudiPhoneNumberValidator()],
|
||||||
|
)
|
||||||
email = models.EmailField(max_length=255, verbose_name=_("Email Address"))
|
email = models.EmailField(max_length=255, verbose_name=_("Email Address"))
|
||||||
address = models.CharField(
|
address = models.CharField(
|
||||||
max_length=200, blank=True, null=True, verbose_name=_("Address")
|
max_length=200, blank=True, null=True, verbose_name=_("Address")
|
||||||
@ -1985,7 +2046,11 @@ class Lead(models.Model):
|
|||||||
first_name = models.CharField(max_length=50, verbose_name=_("First Name"))
|
first_name = models.CharField(max_length=50, verbose_name=_("First Name"))
|
||||||
last_name = models.CharField(max_length=50, verbose_name=_("Last Name"))
|
last_name = models.CharField(max_length=50, verbose_name=_("Last Name"))
|
||||||
email = models.EmailField(verbose_name=_("Email"))
|
email = models.EmailField(verbose_name=_("Email"))
|
||||||
phone_number = models.CharField(max_length=255, verbose_name=_("Phone Number"),validators=[SaudiPhoneNumberValidator()])
|
phone_number = models.CharField(
|
||||||
|
max_length=255,
|
||||||
|
verbose_name=_("Phone Number"),
|
||||||
|
validators=[SaudiPhoneNumberValidator()],
|
||||||
|
)
|
||||||
address = models.CharField(
|
address = models.CharField(
|
||||||
max_length=200, blank=True, null=True, verbose_name=_("Address")
|
max_length=200, blank=True, null=True, verbose_name=_("Address")
|
||||||
)
|
)
|
||||||
@ -2175,8 +2240,9 @@ class Lead(models.Model):
|
|||||||
.order_by("-updated")
|
.order_by("-updated")
|
||||||
.first()
|
.first()
|
||||||
)
|
)
|
||||||
|
|
||||||
def get_absolute_url(self):
|
def get_absolute_url(self):
|
||||||
return reverse("lead_detail", args=[self.dealer.slug,self.slug])
|
return reverse("lead_detail", args=[self.dealer.slug, self.slug])
|
||||||
|
|
||||||
def save(self, *args, **kwargs):
|
def save(self, *args, **kwargs):
|
||||||
if not self.slug:
|
if not self.slug:
|
||||||
@ -2246,6 +2312,7 @@ class Schedule(models.Model):
|
|||||||
@property
|
@property
|
||||||
def duration(self):
|
def duration(self):
|
||||||
return (self.end_time - self.start_time).seconds
|
return (self.end_time - self.start_time).seconds
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def schedule_past_date(self):
|
def schedule_past_date(self):
|
||||||
if self.scheduled_at < now():
|
if self.scheduled_at < now():
|
||||||
@ -2255,6 +2322,7 @@ class Schedule(models.Model):
|
|||||||
@property
|
@property
|
||||||
def get_purpose(self):
|
def get_purpose(self):
|
||||||
return self.purpose.replace("_", " ").title()
|
return self.purpose.replace("_", " ").title()
|
||||||
|
|
||||||
class Meta:
|
class Meta:
|
||||||
ordering = ["-scheduled_at"]
|
ordering = ["-scheduled_at"]
|
||||||
verbose_name = _("Schedule")
|
verbose_name = _("Schedule")
|
||||||
@ -2673,11 +2741,19 @@ class Vendor(models.Model, LocalizedNameMixin):
|
|||||||
arabic_name = models.CharField(max_length=255, verbose_name=_("Arabic Name"))
|
arabic_name = models.CharField(max_length=255, verbose_name=_("Arabic Name"))
|
||||||
name = models.CharField(max_length=255, verbose_name=_("English Name"))
|
name = models.CharField(max_length=255, verbose_name=_("English Name"))
|
||||||
contact_person = models.CharField(max_length=100, verbose_name=_("Contact Person"))
|
contact_person = models.CharField(max_length=100, verbose_name=_("Contact Person"))
|
||||||
phone_number = models.CharField(max_length=255, verbose_name=_("Phone Number"),validators=[SaudiPhoneNumberValidator()])
|
phone_number = models.CharField(
|
||||||
|
max_length=255,
|
||||||
|
verbose_name=_("Phone Number"),
|
||||||
|
validators=[SaudiPhoneNumberValidator()],
|
||||||
|
)
|
||||||
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"),default="default-image/user.jpg"
|
upload_to="logos/vendors",
|
||||||
|
blank=True,
|
||||||
|
null=True,
|
||||||
|
verbose_name=_("Logo"),
|
||||||
|
default="default-image/user.jpg",
|
||||||
)
|
)
|
||||||
thumbnail = ImageSpecField(
|
thumbnail = ImageSpecField(
|
||||||
source="logo",
|
source="logo",
|
||||||
@ -3225,7 +3301,6 @@ class CustomGroup(models.Model):
|
|||||||
"activity",
|
"activity",
|
||||||
"payment",
|
"payment",
|
||||||
"vendor",
|
"vendor",
|
||||||
|
|
||||||
],
|
],
|
||||||
other_perms=[
|
other_perms=[
|
||||||
"view_car",
|
"view_car",
|
||||||
@ -3236,8 +3311,7 @@ class CustomGroup(models.Model):
|
|||||||
"view_saleorder",
|
"view_saleorder",
|
||||||
"view_leads",
|
"view_leads",
|
||||||
"view_opportunity",
|
"view_opportunity",
|
||||||
'view_customer'
|
"view_customer",
|
||||||
|
|
||||||
],
|
],
|
||||||
)
|
)
|
||||||
self.set_permissions(
|
self.set_permissions(
|
||||||
@ -3533,7 +3607,7 @@ class ExtraInfo(models.Model):
|
|||||||
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,dealer=None):
|
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 []
|
||||||
|
|
||||||
@ -3546,11 +3620,13 @@ class ExtraInfo(models.Model):
|
|||||||
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(
|
).union(
|
||||||
|
cls.objects.filter(
|
||||||
dealer=dealer,
|
dealer=dealer,
|
||||||
content_type=ContentType.objects.get_for_model(EstimateModel),
|
content_type=ContentType.objects.get_for_model(EstimateModel),
|
||||||
related_content_type=ContentType.objects.get_for_model(User),
|
related_content_type=ContentType.objects.get_for_model(User),
|
||||||
))
|
)
|
||||||
|
)
|
||||||
else:
|
else:
|
||||||
qs = cls.objects.filter(
|
qs = cls.objects.filter(
|
||||||
dealer=dealer,
|
dealer=dealer,
|
||||||
@ -3559,7 +3635,17 @@ class ExtraInfo(models.Model):
|
|||||||
related_object_id=staff.pk,
|
related_object_id=staff.pk,
|
||||||
)
|
)
|
||||||
# qs = qs.select_related("customer","estimate","invoice")
|
# qs = qs.select_related("customer","estimate","invoice")
|
||||||
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()])
|
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 data
|
||||||
|
|
||||||
@ -3572,7 +3658,7 @@ class ExtraInfo(models.Model):
|
|||||||
# ]
|
# ]
|
||||||
|
|
||||||
@classmethod
|
@classmethod
|
||||||
def get_invoices(cls, staff=None, is_dealer=False,dealer=None):
|
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 []
|
||||||
|
|
||||||
@ -3585,11 +3671,13 @@ class ExtraInfo(models.Model):
|
|||||||
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(
|
).union(
|
||||||
|
cls.objects.filter(
|
||||||
dealer=dealer,
|
dealer=dealer,
|
||||||
content_type=content_type,
|
content_type=content_type,
|
||||||
related_content_type=ContentType.objects.get_for_model(User),
|
related_content_type=ContentType.objects.get_for_model(User),
|
||||||
))
|
)
|
||||||
|
)
|
||||||
else:
|
else:
|
||||||
qs = cls.objects.filter(
|
qs = cls.objects.filter(
|
||||||
dealer=dealer,
|
dealer=dealer,
|
||||||
@ -3608,32 +3696,16 @@ class Recall(models.Model):
|
|||||||
title = models.CharField(max_length=200, verbose_name=_("Recall Title"))
|
title = models.CharField(max_length=200, verbose_name=_("Recall Title"))
|
||||||
description = models.TextField(verbose_name=_("Description"))
|
description = models.TextField(verbose_name=_("Description"))
|
||||||
make = models.ForeignKey(
|
make = models.ForeignKey(
|
||||||
CarMake,
|
CarMake, models.DO_NOTHING, verbose_name=_("Make"), null=True, blank=True
|
||||||
models.DO_NOTHING,
|
|
||||||
verbose_name=_("Make"),
|
|
||||||
null=True,
|
|
||||||
blank=True
|
|
||||||
)
|
)
|
||||||
model = models.ForeignKey(
|
model = models.ForeignKey(
|
||||||
CarModel,
|
CarModel, models.DO_NOTHING, verbose_name=_("Model"), null=True, blank=True
|
||||||
models.DO_NOTHING,
|
|
||||||
verbose_name=_("Model"),
|
|
||||||
null=True,
|
|
||||||
blank=True
|
|
||||||
)
|
)
|
||||||
serie = models.ForeignKey(
|
serie = models.ForeignKey(
|
||||||
CarSerie,
|
CarSerie, models.DO_NOTHING, verbose_name=_("Series"), null=True, blank=True
|
||||||
models.DO_NOTHING,
|
|
||||||
verbose_name=_("Series"),
|
|
||||||
null=True,
|
|
||||||
blank=True
|
|
||||||
)
|
)
|
||||||
trim = models.ForeignKey(
|
trim = models.ForeignKey(
|
||||||
CarTrim,
|
CarTrim, models.DO_NOTHING, verbose_name=_("Trim"), null=True, blank=True
|
||||||
models.DO_NOTHING,
|
|
||||||
verbose_name=_("Trim"),
|
|
||||||
null=True,
|
|
||||||
blank=True
|
|
||||||
)
|
)
|
||||||
year_from = models.IntegerField(verbose_name=_("From Year"), null=True, blank=True)
|
year_from = models.IntegerField(verbose_name=_("From Year"), null=True, blank=True)
|
||||||
year_to = models.IntegerField(verbose_name=_("To Year"), null=True, blank=True)
|
year_to = models.IntegerField(verbose_name=_("To Year"), null=True, blank=True)
|
||||||
@ -3643,7 +3715,7 @@ class Recall(models.Model):
|
|||||||
on_delete=models.SET_NULL,
|
on_delete=models.SET_NULL,
|
||||||
null=True,
|
null=True,
|
||||||
blank=True,
|
blank=True,
|
||||||
verbose_name=_("Created By")
|
verbose_name=_("Created By"),
|
||||||
)
|
)
|
||||||
|
|
||||||
class Meta:
|
class Meta:
|
||||||
@ -3653,11 +3725,16 @@ class Recall(models.Model):
|
|||||||
def __str__(self):
|
def __str__(self):
|
||||||
return self.title
|
return self.title
|
||||||
|
|
||||||
|
|
||||||
class RecallNotification(models.Model):
|
class RecallNotification(models.Model):
|
||||||
recall = models.ForeignKey(Recall, on_delete=models.CASCADE, related_name='notifications')
|
recall = models.ForeignKey(
|
||||||
dealer = models.ForeignKey("Dealer", on_delete=models.CASCADE, related_name='recall_notifications')
|
Recall, on_delete=models.CASCADE, related_name="notifications"
|
||||||
|
)
|
||||||
|
dealer = models.ForeignKey(
|
||||||
|
"Dealer", on_delete=models.CASCADE, related_name="recall_notifications"
|
||||||
|
)
|
||||||
sent_at = models.DateTimeField(auto_now_add=True)
|
sent_at = models.DateTimeField(auto_now_add=True)
|
||||||
cars_affected = models.ManyToManyField(Car, related_name='recall_notifications')
|
cars_affected = models.ManyToManyField(Car, related_name="recall_notifications")
|
||||||
|
|
||||||
class Meta:
|
class Meta:
|
||||||
verbose_name = _("Recall Notification")
|
verbose_name = _("Recall Notification")
|
||||||
@ -3666,27 +3743,30 @@ class RecallNotification(models.Model):
|
|||||||
def __str__(self):
|
def __str__(self):
|
||||||
return f"Notification for {self.dealer} about {self.recall}"
|
return f"Notification for {self.dealer} about {self.recall}"
|
||||||
|
|
||||||
|
|
||||||
class Ticket(models.Model):
|
class Ticket(models.Model):
|
||||||
STATUS_CHOICES = [
|
STATUS_CHOICES = [
|
||||||
('open', 'Open'),
|
("open", "Open"),
|
||||||
('in_progress', 'In Progress'),
|
("in_progress", "In Progress"),
|
||||||
('resolved', 'Resolved'),
|
("resolved", "Resolved"),
|
||||||
('closed', 'Closed'),
|
("closed", "Closed"),
|
||||||
]
|
]
|
||||||
|
|
||||||
PRIORITY_CHOICES = [
|
PRIORITY_CHOICES = [
|
||||||
('low', 'Low'),
|
("low", "Low"),
|
||||||
('medium', 'Medium'),
|
("medium", "Medium"),
|
||||||
('high', 'High'),
|
("high", "High"),
|
||||||
('critical', 'Critical'),
|
("critical", "Critical"),
|
||||||
]
|
]
|
||||||
|
|
||||||
dealer = models.ForeignKey(Dealer, on_delete=models.CASCADE, related_name='tickets')
|
dealer = models.ForeignKey(Dealer, on_delete=models.CASCADE, related_name="tickets")
|
||||||
subject = models.CharField(max_length=200)
|
subject = models.CharField(max_length=200)
|
||||||
description = models.TextField()
|
description = models.TextField()
|
||||||
resolution_notes = models.TextField(blank=True, null=True)
|
resolution_notes = models.TextField(blank=True, null=True)
|
||||||
status = models.CharField(max_length=20, choices=STATUS_CHOICES, default='open')
|
status = models.CharField(max_length=20, choices=STATUS_CHOICES, default="open")
|
||||||
priority = models.CharField(max_length=20, choices=PRIORITY_CHOICES, default='medium')
|
priority = models.CharField(
|
||||||
|
max_length=20, choices=PRIORITY_CHOICES, default="medium"
|
||||||
|
)
|
||||||
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)
|
||||||
|
|
||||||
@ -3697,7 +3777,7 @@ class Ticket(models.Model):
|
|||||||
Returns None if ticket isn't resolved/closed.
|
Returns None if ticket isn't resolved/closed.
|
||||||
Returns timedelta if resolved/closed.
|
Returns timedelta if resolved/closed.
|
||||||
"""
|
"""
|
||||||
if self.status in ['resolved', 'closed'] and self.created_at:
|
if self.status in ["resolved", "closed"] and self.created_at:
|
||||||
return self.updated_at - self.created_at
|
return self.updated_at - self.created_at
|
||||||
return None
|
return None
|
||||||
|
|
||||||
@ -3729,9 +3809,11 @@ class Ticket(models.Model):
|
|||||||
|
|
||||||
|
|
||||||
class CarImage(models.Model):
|
class CarImage(models.Model):
|
||||||
car = models.OneToOneField('Car', on_delete=models.CASCADE, related_name='generated_image')
|
car = models.OneToOneField(
|
||||||
|
"Car", on_delete=models.CASCADE, related_name="generated_image"
|
||||||
|
)
|
||||||
image_hash = models.CharField(max_length=64, unique=True)
|
image_hash = models.CharField(max_length=64, unique=True)
|
||||||
image = models.ImageField(upload_to='car_images/', null=True, blank=True)
|
image = models.ImageField(upload_to="car_images/", null=True, blank=True)
|
||||||
is_generating = models.BooleanField(default=False)
|
is_generating = models.BooleanField(default=False)
|
||||||
created_at = models.DateTimeField(auto_now_add=True)
|
created_at = models.DateTimeField(auto_now_add=True)
|
||||||
|
|
||||||
@ -3752,5 +3834,5 @@ class CarImage(models.Model):
|
|||||||
async_task(
|
async_task(
|
||||||
generate_car_image_task,
|
generate_car_image_task,
|
||||||
self.id,
|
self.id,
|
||||||
task_name=f"generate_car_image_{self.car.vin}"
|
task_name=f"generate_car_image_{self.car.vin}",
|
||||||
)
|
)
|
||||||
@ -87,6 +87,7 @@ from inventory.models import Notification
|
|||||||
import asyncio
|
import asyncio
|
||||||
from datetime import datetime
|
from datetime import datetime
|
||||||
|
|
||||||
|
|
||||||
@database_sync_to_async
|
@database_sync_to_async
|
||||||
def get_user(user_id):
|
def get_user(user_id):
|
||||||
User = get_user_model()
|
User = get_user_model()
|
||||||
@ -95,24 +96,24 @@ def get_user(user_id):
|
|||||||
except User.DoesNotExist:
|
except User.DoesNotExist:
|
||||||
return AnonymousUser()
|
return AnonymousUser()
|
||||||
|
|
||||||
|
|
||||||
@database_sync_to_async
|
@database_sync_to_async
|
||||||
def get_notifications(user, last_id):
|
def get_notifications(user, last_id):
|
||||||
notifications = Notification.objects.filter(
|
notifications = Notification.objects.filter(
|
||||||
user=user,
|
user=user, id__gt=last_id, is_read=False
|
||||||
id__gt=last_id,
|
|
||||||
is_read=False
|
|
||||||
).order_by("created")
|
).order_by("created")
|
||||||
|
|
||||||
return [
|
return [
|
||||||
{
|
{
|
||||||
'id': n.id,
|
"id": n.id,
|
||||||
'message': n.message,
|
"message": n.message,
|
||||||
'created': n.created.isoformat(), # Convert datetime to string
|
"created": n.created.isoformat(), # Convert datetime to string
|
||||||
'is_read': n.is_read
|
"is_read": n.is_read,
|
||||||
}
|
}
|
||||||
for n in notifications
|
for n in notifications
|
||||||
]
|
]
|
||||||
|
|
||||||
|
|
||||||
class NotificationSSEApp:
|
class NotificationSSEApp:
|
||||||
async def __call__(self, scope, receive, send):
|
async def __call__(self, scope, receive, send):
|
||||||
if scope["type"] != "http":
|
if scope["type"] != "http":
|
||||||
@ -143,15 +144,17 @@ class NotificationSSEApp:
|
|||||||
|
|
||||||
for notification in notifications:
|
for notification in notifications:
|
||||||
await self._send_notification(send, notification)
|
await self._send_notification(send, notification)
|
||||||
if notification['id'] > last_id:
|
if notification["id"] > last_id:
|
||||||
last_id = notification['id']
|
last_id = notification["id"]
|
||||||
|
|
||||||
# Send keep-alive comment every 15 seconds
|
# Send keep-alive comment every 15 seconds
|
||||||
await send({
|
await send(
|
||||||
|
{
|
||||||
"type": "http.response.body",
|
"type": "http.response.body",
|
||||||
"body": b":keep-alive\n\n",
|
"body": b":keep-alive\n\n",
|
||||||
"more_body": True
|
"more_body": True,
|
||||||
})
|
}
|
||||||
|
)
|
||||||
|
|
||||||
# await asyncio.sleep(3)
|
# await asyncio.sleep(3)
|
||||||
|
|
||||||
@ -161,7 +164,8 @@ class NotificationSSEApp:
|
|||||||
await self._close_connection(send)
|
await self._close_connection(send)
|
||||||
|
|
||||||
async def _send_headers(self, send):
|
async def _send_headers(self, send):
|
||||||
await send({
|
await send(
|
||||||
|
{
|
||||||
"type": "http.response.start",
|
"type": "http.response.start",
|
||||||
"status": 200,
|
"status": 200,
|
||||||
"headers": [
|
"headers": [
|
||||||
@ -169,8 +173,9 @@ class NotificationSSEApp:
|
|||||||
(b"cache-control", b"no-cache"),
|
(b"cache-control", b"no-cache"),
|
||||||
(b"connection", b"keep-alive"),
|
(b"connection", b"keep-alive"),
|
||||||
(b"x-accel-buffering", b"no"),
|
(b"x-accel-buffering", b"no"),
|
||||||
]
|
],
|
||||||
})
|
}
|
||||||
|
)
|
||||||
|
|
||||||
async def _send_notification(self, send, notification):
|
async def _send_notification(self, send, notification):
|
||||||
try:
|
try:
|
||||||
@ -179,27 +184,25 @@ class NotificationSSEApp:
|
|||||||
f"event: notification\n"
|
f"event: notification\n"
|
||||||
f"data: {json.dumps(notification)}\n\n"
|
f"data: {json.dumps(notification)}\n\n"
|
||||||
)
|
)
|
||||||
await send({
|
await send(
|
||||||
|
{
|
||||||
"type": "http.response.body",
|
"type": "http.response.body",
|
||||||
"body": event_str.encode("utf-8"),
|
"body": event_str.encode("utf-8"),
|
||||||
"more_body": True
|
"more_body": True,
|
||||||
})
|
}
|
||||||
|
)
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
print(f"Error sending notification: {e}")
|
print(f"Error sending notification: {e}")
|
||||||
|
|
||||||
async def _send_response(self, send, status, body):
|
async def _send_response(self, send, status, body):
|
||||||
await send({
|
await send(
|
||||||
|
{
|
||||||
"type": "http.response.start",
|
"type": "http.response.start",
|
||||||
"status": status,
|
"status": status,
|
||||||
"headers": [(b"content-type", b"text/plain")]
|
"headers": [(b"content-type", b"text/plain")],
|
||||||
})
|
}
|
||||||
await send({
|
)
|
||||||
"type": "http.response.body",
|
await send({"type": "http.response.body", "body": body})
|
||||||
"body": body
|
|
||||||
})
|
|
||||||
|
|
||||||
async def _close_connection(self, send):
|
async def _close_connection(self, send):
|
||||||
await send({
|
await send({"type": "http.response.body", "body": b""})
|
||||||
"type": "http.response.body",
|
|
||||||
"body": b""
|
|
||||||
})
|
|
||||||
|
|||||||
@ -20,7 +20,12 @@ 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,InvoiceModel,LedgerModel,EntityModel
|
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.views.generic.edit import CreateView
|
from django.views.generic.edit import CreateView
|
||||||
from django_ledger.forms.chart_of_accounts import (
|
from django_ledger.forms.chart_of_accounts import (
|
||||||
@ -35,17 +40,28 @@ from django_ledger.forms.purchase_order import (
|
|||||||
get_po_itemtxs_formset_class,
|
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, ChartOfAccountModel
|
from django_ledger.models import (
|
||||||
|
PurchaseOrderModel,
|
||||||
|
EstimateModel,
|
||||||
|
BillModel,
|
||||||
|
ChartOfAccountModel,
|
||||||
|
)
|
||||||
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,
|
from django_ledger.forms.invoice import (
|
||||||
|
BaseInvoiceModelUpdateForm,
|
||||||
|
InvoiceModelCreateForEstimateForm,
|
||||||
get_invoice_itemtxs_formset_class,
|
get_invoice_itemtxs_formset_class,
|
||||||
DraftInvoiceModelUpdateForm, InReviewInvoiceModelUpdateForm,
|
DraftInvoiceModelUpdateForm,
|
||||||
ApprovedInvoiceModelUpdateForm, PaidInvoiceModelUpdateForm,
|
InReviewInvoiceModelUpdateForm,
|
||||||
AccruedAndApprovedInvoiceModelUpdateForm, InvoiceModelCreateForm)
|
ApprovedInvoiceModelUpdateForm,
|
||||||
|
PaidInvoiceModelUpdateForm,
|
||||||
|
AccruedAndApprovedInvoiceModelUpdateForm,
|
||||||
|
InvoiceModelCreateForm,
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
logger = logging.getLogger(__name__)
|
logger = logging.getLogger(__name__)
|
||||||
@ -71,7 +87,11 @@ class PurchaseOrderModelUpdateView(
|
|||||||
|
|
||||||
context = super().get_context_data(**kwargs)
|
context = super().get_context_data(**kwargs)
|
||||||
context["entity_slug"] = dealer.entity.slug
|
context["entity_slug"] = dealer.entity.slug
|
||||||
context["po_ready_to_fulfill"] = [item for item in po_model.get_itemtxs_data()[0] if item.po_item_status == 'received']
|
context["po_ready_to_fulfill"] = [
|
||||||
|
item
|
||||||
|
for item in po_model.get_itemtxs_data()[0]
|
||||||
|
if item.po_item_status == "received"
|
||||||
|
]
|
||||||
|
|
||||||
if not itemtxs_formset:
|
if not itemtxs_formset:
|
||||||
itemtxs_qs = self.get_po_itemtxs_qs(po_model)
|
itemtxs_qs = self.get_po_itemtxs_qs(po_model)
|
||||||
@ -776,12 +796,12 @@ class InventoryListView(LoginRequiredMixin, PermissionRequiredMixin, ListView):
|
|||||||
|
|
||||||
|
|
||||||
class InvoiceModelUpdateView(LoginRequiredMixin, PermissionRequiredMixin, UpdateView):
|
class InvoiceModelUpdateView(LoginRequiredMixin, PermissionRequiredMixin, UpdateView):
|
||||||
slug_url_kwarg = 'invoice_pk'
|
slug_url_kwarg = "invoice_pk"
|
||||||
slug_field = 'uuid'
|
slug_field = "uuid"
|
||||||
context_object_name = 'invoice'
|
context_object_name = "invoice"
|
||||||
# template_name = 'inventory/sales/invoices/invoice_update.html'
|
# template_name = 'inventory/sales/invoices/invoice_update.html'
|
||||||
form_class = BaseInvoiceModelUpdateForm
|
form_class = BaseInvoiceModelUpdateForm
|
||||||
http_method_names = ['get', 'post']
|
http_method_names = ["get", "post"]
|
||||||
|
|
||||||
action_update_items = False
|
action_update_items = False
|
||||||
|
|
||||||
@ -802,115 +822,137 @@ class InvoiceModelUpdateView(LoginRequiredMixin, PermissionRequiredMixin, Update
|
|||||||
|
|
||||||
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()
|
||||||
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_slug=self.kwargs['entity_slug'],
|
entity_slug=self.kwargs["entity_slug"],
|
||||||
user_model=self.request.dealer.user,
|
user_model=self.request.dealer.user,
|
||||||
instance=self.object
|
instance=self.object,
|
||||||
)
|
)
|
||||||
return form_class(
|
return form_class(
|
||||||
entity_slug=self.kwargs['entity_slug'],
|
entity_slug=self.kwargs["entity_slug"],
|
||||||
user_model=self.request.dealer.user,
|
user_model=self.request.dealer.user,
|
||||||
**self.get_form_kwargs()
|
**self.get_form_kwargs(),
|
||||||
)
|
)
|
||||||
|
|
||||||
def get_context_data(self, itemtxs_formset=None, **kwargs):
|
def get_context_data(self, itemtxs_formset=None, **kwargs):
|
||||||
context = super().get_context_data(**kwargs)
|
context = super().get_context_data(**kwargs)
|
||||||
invoice_model: InvoiceModel = self.object
|
invoice_model: InvoiceModel = self.object
|
||||||
title = f'Invoice {invoice_model.invoice_number}'
|
title = f"Invoice {invoice_model.invoice_number}"
|
||||||
context['page_title'] = title
|
context["page_title"] = title
|
||||||
context['header_title'] = title
|
context["header_title"] = title
|
||||||
|
|
||||||
ledger_model: LedgerModel = self.object.ledger
|
ledger_model: LedgerModel = self.object.ledger
|
||||||
|
|
||||||
if not invoice_model.is_configured():
|
if not invoice_model.is_configured():
|
||||||
messages.add_message(
|
messages.add_message(
|
||||||
request=self.request,
|
request=self.request,
|
||||||
message=f'Invoice {invoice_model.invoice_number} must have all accounts configured.',
|
message=f"Invoice {invoice_model.invoice_number} must have all accounts configured.",
|
||||||
level=messages.ERROR,
|
level=messages.ERROR,
|
||||||
extra_tags='is-danger'
|
extra_tags="is-danger",
|
||||||
)
|
)
|
||||||
|
|
||||||
if not invoice_model.is_paid():
|
if not invoice_model.is_paid():
|
||||||
if ledger_model.locked:
|
if ledger_model.locked:
|
||||||
messages.add_message(self.request,
|
messages.add_message(
|
||||||
|
self.request,
|
||||||
messages.ERROR,
|
messages.ERROR,
|
||||||
f'Warning! This invoice is locked. Must unlock before making any changes.',
|
f"Warning! This invoice is locked. Must unlock before making any changes.",
|
||||||
extra_tags='is-danger')
|
extra_tags="is-danger",
|
||||||
|
)
|
||||||
|
|
||||||
if ledger_model.locked:
|
if ledger_model.locked:
|
||||||
messages.add_message(self.request,
|
messages.add_message(
|
||||||
|
self.request,
|
||||||
messages.ERROR,
|
messages.ERROR,
|
||||||
f'Warning! This Invoice is Locked. Must unlock before making any changes.',
|
f"Warning! This Invoice is Locked. Must unlock before making any changes.",
|
||||||
extra_tags='is-danger')
|
extra_tags="is-danger",
|
||||||
|
)
|
||||||
|
|
||||||
if not ledger_model.is_posted():
|
if not ledger_model.is_posted():
|
||||||
messages.add_message(self.request,
|
messages.add_message(
|
||||||
|
self.request,
|
||||||
messages.INFO,
|
messages.INFO,
|
||||||
f'This Invoice has not been posted. Must post to see ledger changes.',
|
f"This Invoice has not been posted. Must post to see ledger changes.",
|
||||||
extra_tags='is-info')
|
extra_tags="is-info",
|
||||||
|
)
|
||||||
|
|
||||||
if not itemtxs_formset:
|
if not itemtxs_formset:
|
||||||
itemtxs_qs = invoice_model.itemtransactionmodel_set.all().select_related('item_model')
|
itemtxs_qs = invoice_model.itemtransactionmodel_set.all().select_related(
|
||||||
itemtxs_qs, itemtxs_agg = invoice_model.get_itemtxs_data(queryset=itemtxs_qs)
|
"item_model"
|
||||||
invoice_itemtxs_formset_class = get_invoice_itemtxs_formset_class(invoice_model)
|
)
|
||||||
itemtxs_formset = invoice_itemtxs_formset_class(
|
itemtxs_qs, itemtxs_agg = invoice_model.get_itemtxs_data(
|
||||||
entity_slug=self.kwargs['entity_slug'],
|
|
||||||
user_model=self.request.dealer.user,
|
|
||||||
invoice_model=invoice_model,
|
|
||||||
queryset=itemtxs_qs
|
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:
|
else:
|
||||||
itemtxs_qs, itemtxs_agg = invoice_model.get_itemtxs_data(queryset=itemtxs_formset.queryset)
|
itemtxs_qs, itemtxs_agg = invoice_model.get_itemtxs_data(
|
||||||
|
queryset=itemtxs_formset.queryset
|
||||||
|
)
|
||||||
|
|
||||||
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"]
|
||||||
return context
|
return context
|
||||||
|
|
||||||
def get_success_url(self):
|
def get_success_url(self):
|
||||||
entity_slug = self.kwargs['entity_slug']
|
entity_slug = self.kwargs["entity_slug"]
|
||||||
invoice_pk = self.kwargs['invoice_pk']
|
invoice_pk = self.kwargs["invoice_pk"]
|
||||||
return reverse('invoice_detail',
|
return reverse(
|
||||||
|
"invoice_detail",
|
||||||
kwargs={
|
kwargs={
|
||||||
'dealer_slug': self.request.dealer.slug,
|
"dealer_slug": self.request.dealer.slug,
|
||||||
'entity_slug': entity_slug,
|
"entity_slug": entity_slug,
|
||||||
'pk': invoice_pk
|
"pk": invoice_pk,
|
||||||
})
|
},
|
||||||
|
)
|
||||||
|
|
||||||
# def get_queryset(self):
|
# def get_queryset(self):
|
||||||
# qs = super().get_queryset()
|
# qs = super().get_queryset()
|
||||||
# return qs.prefetch_related('itemtransactionmodel_set')
|
# return qs.prefetch_related('itemtransactionmodel_set')
|
||||||
def get_queryset(self):
|
def get_queryset(self):
|
||||||
if self.queryset is None:
|
if self.queryset is None:
|
||||||
self.queryset = InvoiceModel.objects.for_entity(
|
self.queryset = (
|
||||||
entity_slug=self.kwargs['entity_slug'],
|
InvoiceModel.objects.for_entity(
|
||||||
user_model=self.request.user
|
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')
|
.select_related("customer", "ledger")
|
||||||
|
.order_by("-created")
|
||||||
|
)
|
||||||
|
return super().get_queryset().prefetch_related("itemtransactionmodel_set")
|
||||||
|
|
||||||
def form_valid(self, form):
|
def form_valid(self, form):
|
||||||
invoice_model: InvoiceModel = form.save(commit=False)
|
invoice_model: InvoiceModel = form.save(commit=False)
|
||||||
if invoice_model.can_migrate():
|
if invoice_model.can_migrate():
|
||||||
invoice_model.migrate_state(
|
invoice_model.migrate_state(
|
||||||
user_model=self.request.dealer.user,
|
user_model=self.request.dealer.user,
|
||||||
entity_slug=self.kwargs['entity_slug']
|
entity_slug=self.kwargs["entity_slug"],
|
||||||
)
|
)
|
||||||
messages.add_message(self.request,
|
messages.add_message(
|
||||||
|
self.request,
|
||||||
messages.SUCCESS,
|
messages.SUCCESS,
|
||||||
f'Invoice {self.object.invoice_number} successfully updated.',
|
f"Invoice {self.object.invoice_number} successfully updated.",
|
||||||
extra_tags='is-success')
|
extra_tags="is-success",
|
||||||
|
)
|
||||||
return super().form_valid(form)
|
return super().form_valid(form)
|
||||||
|
|
||||||
def get(self, request, entity_slug, invoice_pk, *args, **kwargs):
|
def get(self, request, entity_slug, invoice_pk, *args, **kwargs):
|
||||||
if self.action_update_items:
|
if self.action_update_items:
|
||||||
return HttpResponseRedirect(
|
return HttpResponseRedirect(
|
||||||
redirect_to=reverse('invoice_update',
|
redirect_to=reverse(
|
||||||
|
"invoice_update",
|
||||||
kwargs={
|
kwargs={
|
||||||
'dealer_slug': request.dealer.slug,
|
"dealer_slug": request.dealer.slug,
|
||||||
'entity_slug': entity_slug,
|
"entity_slug": entity_slug,
|
||||||
'pk': invoice_pk
|
"pk": invoice_pk,
|
||||||
})
|
},
|
||||||
|
)
|
||||||
)
|
)
|
||||||
return super(InvoiceModelUpdateView, self).get(request, *args, **kwargs)
|
return super(InvoiceModelUpdateView, self).get(request, *args, **kwargs)
|
||||||
|
|
||||||
@ -922,18 +964,22 @@ class InvoiceModelUpdateView(LoginRequiredMixin, PermissionRequiredMixin, Update
|
|||||||
queryset = self.get_queryset()
|
queryset = self.get_queryset()
|
||||||
invoice_model = self.get_object(queryset=queryset)
|
invoice_model = self.get_object(queryset=queryset)
|
||||||
self.object = invoice_model
|
self.object = invoice_model
|
||||||
invoice_itemtxs_formset_class = get_invoice_itemtxs_formset_class(invoice_model)
|
invoice_itemtxs_formset_class = get_invoice_itemtxs_formset_class(
|
||||||
itemtxs_formset = invoice_itemtxs_formset_class(request.POST,
|
invoice_model
|
||||||
|
)
|
||||||
|
itemtxs_formset = invoice_itemtxs_formset_class(
|
||||||
|
request.POST,
|
||||||
user_model=self.request.dealer.user,
|
user_model=self.request.dealer.user,
|
||||||
invoice_model=invoice_model,
|
invoice_model=invoice_model,
|
||||||
entity_slug=entity_slug)
|
entity_slug=entity_slug,
|
||||||
|
)
|
||||||
|
|
||||||
if not invoice_model.can_edit_items():
|
if not invoice_model.can_edit_items():
|
||||||
messages.add_message(
|
messages.add_message(
|
||||||
request,
|
request,
|
||||||
message=f'Cannot update items once Invoice is {invoice_model.get_invoice_status_display()}',
|
message=f"Cannot update items once Invoice is {invoice_model.get_invoice_status_display()}",
|
||||||
level=messages.ERROR,
|
level=messages.ERROR,
|
||||||
extra_tags='is-danger'
|
extra_tags="is-danger",
|
||||||
)
|
)
|
||||||
context = self.get_context_data(itemtxs_formset=itemtxs_formset)
|
context = self.get_context_data(itemtxs_formset=itemtxs_formset)
|
||||||
return self.render_to_response(context=context)
|
return self.render_to_response(context=context)
|
||||||
@ -941,8 +987,12 @@ class InvoiceModelUpdateView(LoginRequiredMixin, PermissionRequiredMixin, Update
|
|||||||
if itemtxs_formset.has_changed():
|
if itemtxs_formset.has_changed():
|
||||||
if itemtxs_formset.is_valid():
|
if itemtxs_formset.is_valid():
|
||||||
itemtxs_list = itemtxs_formset.save(commit=False)
|
itemtxs_list = itemtxs_formset.save(commit=False)
|
||||||
entity_qs = EntityModel.objects.for_user(user_model=self.request.dealer.user)
|
entity_qs = EntityModel.objects.for_user(
|
||||||
entity_model: EntityModel = get_object_or_404(entity_qs, slug__exact=entity_slug)
|
user_model=self.request.dealer.user
|
||||||
|
)
|
||||||
|
entity_model: EntityModel = get_object_or_404(
|
||||||
|
entity_qs, slug__exact=entity_slug
|
||||||
|
)
|
||||||
|
|
||||||
for itemtxs in itemtxs_list:
|
for itemtxs in itemtxs_list:
|
||||||
itemtxs.invoice_model_id = invoice_model.uuid
|
itemtxs.invoice_model_id = invoice_model.uuid
|
||||||
@ -953,53 +1003,72 @@ class InvoiceModelUpdateView(LoginRequiredMixin, PermissionRequiredMixin, Update
|
|||||||
invoice_model.get_state(commit=True)
|
invoice_model.get_state(commit=True)
|
||||||
invoice_model.clean()
|
invoice_model.clean()
|
||||||
invoice_model.save(
|
invoice_model.save(
|
||||||
update_fields=['amount_due',
|
update_fields=[
|
||||||
'amount_receivable',
|
"amount_due",
|
||||||
'amount_unearned',
|
"amount_receivable",
|
||||||
'amount_earned',
|
"amount_unearned",
|
||||||
'updated']
|
"amount_earned",
|
||||||
|
"updated",
|
||||||
|
]
|
||||||
)
|
)
|
||||||
|
|
||||||
invoice_model.migrate_state(
|
invoice_model.migrate_state(
|
||||||
entity_slug=entity_slug,
|
entity_slug=entity_slug,
|
||||||
user_model=self.request.user,
|
user_model=self.request.user,
|
||||||
raise_exception=False,
|
raise_exception=False,
|
||||||
itemtxs_qs=itemtxs_qs
|
itemtxs_qs=itemtxs_qs,
|
||||||
)
|
)
|
||||||
|
|
||||||
messages.add_message(request,
|
messages.add_message(
|
||||||
message=f'Items for Invoice {invoice_model.invoice_number} saved.',
|
request,
|
||||||
|
message=f"Items for Invoice {invoice_model.invoice_number} saved.",
|
||||||
level=messages.SUCCESS,
|
level=messages.SUCCESS,
|
||||||
extra_tags='is-success')
|
extra_tags="is-success",
|
||||||
|
)
|
||||||
return HttpResponseRedirect(
|
return HttpResponseRedirect(
|
||||||
redirect_to=reverse('django_ledger:invoice-update',
|
redirect_to=reverse(
|
||||||
|
"django_ledger:invoice-update",
|
||||||
kwargs={
|
kwargs={
|
||||||
'entity_slug': entity_slug,
|
"entity_slug": entity_slug,
|
||||||
'invoice_pk': invoice_pk
|
"invoice_pk": invoice_pk,
|
||||||
})
|
},
|
||||||
|
)
|
||||||
)
|
)
|
||||||
|
|
||||||
# if not valid, return formset with errors...
|
# if not valid, return formset with errors...
|
||||||
return self.render_to_response(context=self.get_context_data(itemtxs_formset=itemtxs_formset))
|
return self.render_to_response(
|
||||||
|
context=self.get_context_data(itemtxs_formset=itemtxs_formset)
|
||||||
|
)
|
||||||
return super(InvoiceModelUpdateView, self).post(request, **kwargs)
|
return super(InvoiceModelUpdateView, self).post(request, **kwargs)
|
||||||
|
|
||||||
|
|
||||||
|
class ChartOfAccountModelModelBaseViewMixIn(
|
||||||
class ChartOfAccountModelModelBaseViewMixIn(LoginRequiredMixin, PermissionRequiredMixin):
|
LoginRequiredMixin, PermissionRequiredMixin
|
||||||
|
):
|
||||||
queryset = None
|
queryset = None
|
||||||
permission_required = []
|
permission_required = []
|
||||||
|
|
||||||
def get_queryset(self):
|
def get_queryset(self):
|
||||||
if self.queryset is None:
|
if self.queryset is None:
|
||||||
entity_model = self.request.dealer.entity
|
entity_model = self.request.dealer.entity
|
||||||
self.queryset = entity_model.chartofaccountmodel_set.all().order_by('-updated')
|
self.queryset = entity_model.chartofaccountmodel_set.all().order_by(
|
||||||
|
"-updated"
|
||||||
|
)
|
||||||
return super().get_queryset()
|
return super().get_queryset()
|
||||||
|
|
||||||
def get_redirect_url(self, *args, **kwargs):
|
def get_redirect_url(self, *args, **kwargs):
|
||||||
return reverse('coa-list', kwargs={'dealer_slug': self.request.dealer.slug,
|
return reverse(
|
||||||
'entity_slug': self.request.entity.slug})
|
"coa-list",
|
||||||
|
kwargs={
|
||||||
|
"dealer_slug": self.request.dealer.slug,
|
||||||
|
"entity_slug": self.request.entity.slug,
|
||||||
|
},
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
class ChartOfAccountModelListView(ChartOfAccountModelModelBaseViewMixIn, ListView):
|
class ChartOfAccountModelListView(ChartOfAccountModelModelBaseViewMixIn, ListView):
|
||||||
template_name = 'chart_of_accounts/coa_list.html'
|
template_name = "chart_of_accounts/coa_list.html"
|
||||||
context_object_name = 'coa_list'
|
context_object_name = "coa_list"
|
||||||
inactive = False
|
inactive = False
|
||||||
|
|
||||||
def get_queryset(self):
|
def get_queryset(self):
|
||||||
@ -1010,84 +1079,116 @@ class ChartOfAccountModelListView(ChartOfAccountModelModelBaseViewMixIn, ListVie
|
|||||||
|
|
||||||
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=None, **kwargs)
|
context = super().get_context_data(object_list=None, **kwargs)
|
||||||
context['inactive'] = self.inactive
|
context["inactive"] = self.inactive
|
||||||
context['header_subtitle'] = self.request.entity.name
|
context["header_subtitle"] = self.request.entity.name
|
||||||
context['header_subtitle_icon'] = 'gravity-ui:hierarchy'
|
context["header_subtitle_icon"] = "gravity-ui:hierarchy"
|
||||||
context['page_title'] = 'Inactive Chart of Account List' if self.inactive else 'Chart of Accounts List'
|
context["page_title"] = (
|
||||||
context['header_title'] = 'Inactive Chart of Account List' if self.inactive else 'Chart of Accounts List'
|
"Inactive Chart of Account List"
|
||||||
|
if self.inactive
|
||||||
|
else "Chart of Accounts List"
|
||||||
|
)
|
||||||
|
context["header_title"] = (
|
||||||
|
"Inactive Chart of Account List"
|
||||||
|
if self.inactive
|
||||||
|
else "Chart of Accounts List"
|
||||||
|
)
|
||||||
return context
|
return context
|
||||||
|
|
||||||
|
|
||||||
class ChartOfAccountModelCreateView(ChartOfAccountModelModelBaseViewMixIn, CreateView):
|
class ChartOfAccountModelCreateView(ChartOfAccountModelModelBaseViewMixIn, CreateView):
|
||||||
template_name = 'chart_of_accounts/coa_create.html'
|
template_name = "chart_of_accounts/coa_create.html"
|
||||||
extra_context = {
|
extra_context = {
|
||||||
'header_title': _('Create Chart of Accounts'),
|
"header_title": _("Create Chart of Accounts"),
|
||||||
'page_title': _('Create Chart of Account'),
|
"page_title": _("Create Chart of Account"),
|
||||||
}
|
}
|
||||||
|
|
||||||
def get_initial(self):
|
def get_initial(self):
|
||||||
return {
|
return {
|
||||||
'entity': self.request.entity,
|
"entity": self.request.entity,
|
||||||
}
|
}
|
||||||
|
|
||||||
def get_form(self, form_class=None):
|
def get_form(self, form_class=None):
|
||||||
return ChartOfAccountsModelCreateForm(
|
return ChartOfAccountsModelCreateForm(
|
||||||
entity_model=self.request.entity,
|
entity_model=self.request.entity, **self.get_form_kwargs()
|
||||||
**self.get_form_kwargs()
|
|
||||||
)
|
)
|
||||||
|
|
||||||
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=None, **kwargs)
|
context = super().get_context_data(object_list=None, **kwargs)
|
||||||
context['header_subtitle'] = f'New Chart of Accounts: {self.request.entity.name}'
|
context["header_subtitle"] = (
|
||||||
context['header_subtitle_icon'] = 'gravity-ui:hierarchy'
|
f"New Chart of Accounts: {self.request.entity.name}"
|
||||||
|
)
|
||||||
|
context["header_subtitle_icon"] = "gravity-ui:hierarchy"
|
||||||
return context
|
return context
|
||||||
|
|
||||||
def get_success_url(self):
|
def get_success_url(self):
|
||||||
return reverse('coa-list', kwargs={'dealer_slug': self.request.dealer.slug,
|
return reverse(
|
||||||
'entity_slug': self.request.entity.slug})
|
"coa-list",
|
||||||
|
kwargs={
|
||||||
|
"dealer_slug": self.request.dealer.slug,
|
||||||
|
"entity_slug": self.request.entity.slug,
|
||||||
|
},
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
class ChartOfAccountModelUpdateView(ChartOfAccountModelModelBaseViewMixIn, UpdateView):
|
class ChartOfAccountModelUpdateView(ChartOfAccountModelModelBaseViewMixIn, UpdateView):
|
||||||
context_object_name = 'coa_model'
|
context_object_name = "coa_model"
|
||||||
slug_url_kwarg = 'coa_slug'
|
slug_url_kwarg = "coa_slug"
|
||||||
template_name = 'chart_of_accounts/coa_update.html'
|
template_name = "chart_of_accounts/coa_update.html"
|
||||||
form_class = ChartOfAccountsModelUpdateForm
|
form_class = ChartOfAccountsModelUpdateForm
|
||||||
|
|
||||||
def get_context_data(self, **kwargs):
|
def get_context_data(self, **kwargs):
|
||||||
context = super().get_context_data(**kwargs)
|
context = super().get_context_data(**kwargs)
|
||||||
chart_of_accounts_model: ChartOfAccountModel = self.object
|
chart_of_accounts_model: ChartOfAccountModel = self.object
|
||||||
context['page_title'] = f'Update Chart of Account {chart_of_accounts_model.name}'
|
context["page_title"] = (
|
||||||
context['header_title'] = f'Update Chart of Account {chart_of_accounts_model.name}'
|
f"Update Chart of Account {chart_of_accounts_model.name}"
|
||||||
|
)
|
||||||
|
context["header_title"] = (
|
||||||
|
f"Update Chart of Account {chart_of_accounts_model.name}"
|
||||||
|
)
|
||||||
return context
|
return context
|
||||||
|
|
||||||
def get_success_url(self):
|
def get_success_url(self):
|
||||||
return reverse('coa-list', kwargs={'dealer_slug': self.request.dealer.slug,
|
return reverse(
|
||||||
'entity_slug': self.request.entity.slug})
|
"coa-list",
|
||||||
|
kwargs={
|
||||||
|
"dealer_slug": self.request.dealer.slug,
|
||||||
|
"entity_slug": self.request.entity.slug,
|
||||||
|
},
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
class CharOfAccountModelActionView(ChartOfAccountModelModelBaseViewMixIn,
|
class CharOfAccountModelActionView(
|
||||||
RedirectView,
|
ChartOfAccountModelModelBaseViewMixIn, RedirectView, SingleObjectMixin
|
||||||
SingleObjectMixin):
|
):
|
||||||
http_method_names = ['get']
|
http_method_names = ["get"]
|
||||||
slug_url_kwarg = 'coa_slug'
|
slug_url_kwarg = "coa_slug"
|
||||||
action_name = None
|
action_name = None
|
||||||
commit = True
|
commit = True
|
||||||
|
|
||||||
def get(self, request, *args, **kwargs):
|
def get(self, request, *args, **kwargs):
|
||||||
kwargs['user_model'] = self.request.user
|
kwargs["user_model"] = self.request.user
|
||||||
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(CharOfAccountModelActionView, self).get(request, *args, **kwargs)
|
response = super(CharOfAccountModelActionView, self).get(
|
||||||
|
request, *args, **kwargs
|
||||||
|
)
|
||||||
coa_model: ChartOfAccountModel = self.get_object()
|
coa_model: ChartOfAccountModel = self.get_object()
|
||||||
|
|
||||||
try:
|
try:
|
||||||
getattr(coa_model, self.action_name)(commit=self.commit, **kwargs)
|
getattr(coa_model, self.action_name)(commit=self.commit, **kwargs)
|
||||||
messages.add_message(request, level=messages.SUCCESS, extra_tags='is-success',
|
messages.add_message(
|
||||||
message=_('Successfully updated {} Default Chart of Account to '.format(
|
request,
|
||||||
request.entity.name) +
|
level=messages.SUCCESS,
|
||||||
'{}'.format(coa_model.name)))
|
extra_tags="is-success",
|
||||||
|
message=_(
|
||||||
|
"Successfully updated {} Default Chart of Account to ".format(
|
||||||
|
request.entity.name
|
||||||
|
)
|
||||||
|
+ "{}".format(coa_model.name)
|
||||||
|
),
|
||||||
|
)
|
||||||
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
|
||||||
|
|||||||
@ -28,6 +28,7 @@ from plans.models import UserPlan
|
|||||||
from plans.signals import order_completed, activate_user_plan
|
from plans.signals import order_completed, activate_user_plan
|
||||||
from inventory.tasks import send_email
|
from inventory.tasks import send_email
|
||||||
from django.conf import settings
|
from django.conf import settings
|
||||||
|
|
||||||
# logging
|
# logging
|
||||||
import logging
|
import logging
|
||||||
|
|
||||||
@ -177,7 +178,11 @@ 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(func="inventory.tasks.create_coa_accounts",dealer=instance,hook="inventory.hooks.check_create_coa_accounts")
|
async_task(
|
||||||
|
func="inventory.tasks.create_coa_accounts",
|
||||||
|
dealer=instance,
|
||||||
|
hook="inventory.hooks.check_create_coa_accounts",
|
||||||
|
)
|
||||||
# async_task('inventory.tasks.check_create_coa_accounts', instance, schedule_type='O', schedule_time=timedelta(seconds=20))
|
# async_task('inventory.tasks.check_create_coa_accounts', instance, schedule_type='O', schedule_time=timedelta(seconds=20))
|
||||||
|
|
||||||
# create_settings(instance.pk)
|
# create_settings(instance.pk)
|
||||||
@ -1039,7 +1044,11 @@ def po_fullfilled_notification(sender, instance, created, **kwargs):
|
|||||||
po_number=instance.po_number,
|
po_number=instance.po_number,
|
||||||
url=reverse(
|
url=reverse(
|
||||||
"purchase_order_detail",
|
"purchase_order_detail",
|
||||||
kwargs={"dealer_slug": dealer.slug,"entity_slug":instance.entity.slug, "pk": instance.pk},
|
kwargs={
|
||||||
|
"dealer_slug": dealer.slug,
|
||||||
|
"entity_slug": instance.entity.slug,
|
||||||
|
"pk": instance.pk,
|
||||||
|
},
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
)
|
)
|
||||||
@ -1086,7 +1095,10 @@ def sale_order_created_notification(sender, instance, created, **kwargs):
|
|||||||
estimate_number=instance.estimate.estimate_number,
|
estimate_number=instance.estimate.estimate_number,
|
||||||
url=reverse(
|
url=reverse(
|
||||||
"estimate_detail",
|
"estimate_detail",
|
||||||
kwargs={"dealer_slug": instance.dealer.slug, "pk": instance.estimate.pk},
|
kwargs={
|
||||||
|
"dealer_slug": instance.dealer.slug,
|
||||||
|
"pk": instance.estimate.pk,
|
||||||
|
},
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
)
|
)
|
||||||
@ -1131,7 +1143,7 @@ def estimate_in_review_notification(sender, instance, created, **kwargs):
|
|||||||
url=reverse(
|
url=reverse(
|
||||||
"estimate_detail",
|
"estimate_detail",
|
||||||
kwargs={"dealer_slug": dealer.slug, "pk": instance.pk},
|
kwargs={"dealer_slug": dealer.slug, "pk": instance.pk},
|
||||||
)
|
),
|
||||||
),
|
),
|
||||||
)
|
)
|
||||||
|
|
||||||
@ -1188,7 +1200,11 @@ def bill_model_in_approve_notification(sender, instance, created, **kwargs):
|
|||||||
bill_number=instance.bill_number,
|
bill_number=instance.bill_number,
|
||||||
url=reverse(
|
url=reverse(
|
||||||
"bill-update",
|
"bill-update",
|
||||||
kwargs={"dealer_slug": dealer.slug, "entity_slug": dealer.entity.slug, "bill_pk": instance.pk},
|
kwargs={
|
||||||
|
"dealer_slug": dealer.slug,
|
||||||
|
"entity_slug": dealer.entity.slug,
|
||||||
|
"bill_pk": instance.pk,
|
||||||
|
},
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
)
|
)
|
||||||
@ -1224,7 +1240,6 @@ def bill_model_in_approve_notification(sender, instance, created, **kwargs):
|
|||||||
# )
|
# )
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
@receiver(post_save, sender=models.Ticket)
|
@receiver(post_save, sender=models.Ticket)
|
||||||
def send_ticket_notification(sender, instance, created, **kwargs):
|
def send_ticket_notification(sender, instance, created, **kwargs):
|
||||||
if created:
|
if created:
|
||||||
@ -1259,7 +1274,10 @@ def send_ticket_notification(sender, instance, created, **kwargs):
|
|||||||
ticket_number=instance.pk,
|
ticket_number=instance.pk,
|
||||||
url=reverse(
|
url=reverse(
|
||||||
"ticket_detail",
|
"ticket_detail",
|
||||||
kwargs={"dealer_slug": instance.dealer.slug, "ticket_id": instance.pk},
|
kwargs={
|
||||||
|
"dealer_slug": instance.dealer.slug,
|
||||||
|
"ticket_id": instance.pk,
|
||||||
|
},
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
)
|
)
|
||||||
@ -1273,28 +1291,29 @@ def handle_car_image(sender, instance, created, **kwargs):
|
|||||||
try:
|
try:
|
||||||
# Create or get car image record
|
# Create or get car image record
|
||||||
car = instance.car
|
car = instance.car
|
||||||
car_image, created = models.CarImage.objects.get_or_create(car=car, defaults={'image_hash': car.get_hash})
|
car_image, created = models.CarImage.objects.get_or_create(
|
||||||
|
car=car, defaults={"image_hash": car.get_hash}
|
||||||
|
)
|
||||||
|
|
||||||
# Check for existing image with same hash
|
# Check for existing image with same hash
|
||||||
existing = models.CarImage.objects.filter(
|
existing = (
|
||||||
image_hash=car_image.image_hash,
|
models.CarImage.objects.filter(
|
||||||
image__isnull=False
|
image_hash=car_image.image_hash, image__isnull=False
|
||||||
).exclude(car=car).first()
|
)
|
||||||
|
.exclude(car=car)
|
||||||
|
.first()
|
||||||
|
)
|
||||||
|
|
||||||
if existing:
|
if existing:
|
||||||
# Copy existing image
|
# Copy existing image
|
||||||
car_image.image.save(
|
car_image.image.save(existing.image.name, existing.image.file, save=True)
|
||||||
existing.image.name,
|
|
||||||
existing.image.file,
|
|
||||||
save=True
|
|
||||||
)
|
|
||||||
logger.info(f"Reused image for car {car.vin}")
|
logger.info(f"Reused image for car {car.vin}")
|
||||||
else:
|
else:
|
||||||
# Schedule async generation
|
# Schedule async generation
|
||||||
async_task(
|
async_task(
|
||||||
'inventory.tasks.generate_car_image_task',
|
"inventory.tasks.generate_car_image_task",
|
||||||
car_image.id,
|
car_image.id,
|
||||||
task_name=f"generate_car_image_{car.vin}"
|
task_name=f"generate_car_image_{car.vin}",
|
||||||
)
|
)
|
||||||
logger.info(f"Scheduled image generation for car {car.vin}")
|
logger.info(f"Scheduled image generation for car {car.vin}")
|
||||||
|
|
||||||
|
|||||||
@ -17,11 +17,19 @@ from django.core.files.base import ContentFile
|
|||||||
from django.contrib.auth import get_user_model
|
from django.contrib.auth import get_user_model
|
||||||
from allauth.account.models import EmailAddress
|
from allauth.account.models import EmailAddress
|
||||||
from django.core.mail import EmailMultiAlternatives
|
from django.core.mail import EmailMultiAlternatives
|
||||||
from .utils import get_accounts_data,create_account
|
from .utils import get_accounts_data, create_account
|
||||||
from django.template.loader import render_to_string
|
from django.template.loader import render_to_string
|
||||||
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
|
from django.contrib.auth.models import User, Group, Permission
|
||||||
from inventory.models import DealerSettings, Dealer,Schedule,Notification,CarReservation,CarStatusChoices,CarImage
|
from inventory.models import (
|
||||||
|
DealerSettings,
|
||||||
|
Dealer,
|
||||||
|
Schedule,
|
||||||
|
Notification,
|
||||||
|
CarReservation,
|
||||||
|
CarStatusChoices,
|
||||||
|
CarImage,
|
||||||
|
)
|
||||||
|
|
||||||
logger = logging.getLogger(__name__)
|
logger = logging.getLogger(__name__)
|
||||||
logging.basicConfig(level=logging.INFO)
|
logging.basicConfig(level=logging.INFO)
|
||||||
@ -52,6 +60,7 @@ def create_settings(pk):
|
|||||||
.first(),
|
.first(),
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
def create_coa_accounts(**kwargs):
|
def create_coa_accounts(**kwargs):
|
||||||
logger.info("creating all accounts are created")
|
logger.info("creating all accounts are created")
|
||||||
instance = kwargs.get("dealer")
|
instance = kwargs.get("dealer")
|
||||||
@ -62,9 +71,6 @@ def create_coa_accounts(**kwargs):
|
|||||||
create_account(entity, coa, account_data)
|
create_account(entity, coa, account_data)
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
# def create_coa_accounts1(pk):
|
# def create_coa_accounts1(pk):
|
||||||
# with transaction.atomic():
|
# with transaction.atomic():
|
||||||
# instance = Dealer.objects.select_for_update().get(pk=pk)
|
# instance = Dealer.objects.select_for_update().get(pk=pk)
|
||||||
@ -800,8 +806,6 @@ def create_user_dealer(email, password, name, arabic_name, phone, crn, vrn, addr
|
|||||||
# transaction.on_commit(run)
|
# transaction.on_commit(run)
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
def send_bilingual_reminder(user_id, plan_id, expiration_date, days_until_expire):
|
def send_bilingual_reminder(user_id, plan_id, expiration_date, days_until_expire):
|
||||||
"""Send bilingual email reminder using Django-Q"""
|
"""Send bilingual email reminder using Django-Q"""
|
||||||
try:
|
try:
|
||||||
@ -809,41 +813,49 @@ def send_bilingual_reminder(user_id, plan_id, expiration_date, days_until_expire
|
|||||||
plan = Plan.objects.get(id=plan_id)
|
plan = Plan.objects.get(id=plan_id)
|
||||||
|
|
||||||
# Determine user language preference
|
# Determine user language preference
|
||||||
user_language = getattr(user, 'language', settings.LANGUAGE_CODE)
|
user_language = getattr(user, "language", settings.LANGUAGE_CODE)
|
||||||
activate(user_language)
|
activate(user_language)
|
||||||
|
|
||||||
# Context data
|
# Context data
|
||||||
context = {
|
context = {
|
||||||
'user': user,
|
"user": user,
|
||||||
'plan': plan,
|
"plan": plan,
|
||||||
'expiration_date': expiration_date,
|
"expiration_date": expiration_date,
|
||||||
'days_until_expire': days_until_expire,
|
"days_until_expire": days_until_expire,
|
||||||
'SITE_NAME': settings.SITE_NAME,
|
"SITE_NAME": settings.SITE_NAME,
|
||||||
'RENEWAL_URL': "url" ,#settings.RENEWAL_URL,
|
"RENEWAL_URL": "url", # settings.RENEWAL_URL,
|
||||||
'direction': 'rtl' if user_language.startswith('ar') else 'ltr'
|
"direction": "rtl" if user_language.startswith("ar") else "ltr",
|
||||||
}
|
}
|
||||||
|
|
||||||
# Subject with translation
|
# Subject with translation
|
||||||
subject_en = f"Your {plan.name} subscription expires in {days_until_expire} days"
|
subject_en = (
|
||||||
|
f"Your {plan.name} subscription expires in {days_until_expire} days"
|
||||||
|
)
|
||||||
subject_ar = f"اشتراكك في {plan.name} ينتهي خلال {days_until_expire} أيام"
|
subject_ar = f"اشتراكك في {plan.name} ينتهي خلال {days_until_expire} أيام"
|
||||||
|
|
||||||
# Render templates
|
# Render templates
|
||||||
text_content = render_to_string([
|
text_content = render_to_string(
|
||||||
f'emails/expiration_reminder_{user_language}.txt',
|
[
|
||||||
'emails/expiration_reminder.txt'
|
f"emails/expiration_reminder_{user_language}.txt",
|
||||||
], context)
|
"emails/expiration_reminder.txt",
|
||||||
|
],
|
||||||
|
context,
|
||||||
|
)
|
||||||
|
|
||||||
html_content = render_to_string([
|
html_content = render_to_string(
|
||||||
f'emails/expiration_reminder_{user_language}.html',
|
[
|
||||||
'emails/expiration_reminder.html'
|
f"emails/expiration_reminder_{user_language}.html",
|
||||||
], context)
|
"emails/expiration_reminder.html",
|
||||||
|
],
|
||||||
|
context,
|
||||||
|
)
|
||||||
|
|
||||||
# Create email
|
# Create email
|
||||||
email = EmailMultiAlternatives(
|
email = EmailMultiAlternatives(
|
||||||
subject=subject_ar if user_language.startswith('ar') else subject_en,
|
subject=subject_ar if user_language.startswith("ar") else subject_en,
|
||||||
body=text_content,
|
body=text_content,
|
||||||
from_email=settings.DEFAULT_FROM_EMAIL,
|
from_email=settings.DEFAULT_FROM_EMAIL,
|
||||||
to=[user.email]
|
to=[user.email],
|
||||||
)
|
)
|
||||||
email.attach_alternative(html_content, "text/html")
|
email.attach_alternative(html_content, "text/html")
|
||||||
email.send()
|
email.send()
|
||||||
@ -853,6 +865,7 @@ def send_bilingual_reminder(user_id, plan_id, expiration_date, days_until_expire
|
|||||||
logger.error(f"Email failed: {str(e)}")
|
logger.error(f"Email failed: {str(e)}")
|
||||||
raise
|
raise
|
||||||
|
|
||||||
|
|
||||||
def handle_email_result(task):
|
def handle_email_result(task):
|
||||||
"""Callback for email results"""
|
"""Callback for email results"""
|
||||||
if task.success:
|
if task.success:
|
||||||
@ -861,7 +874,6 @@ def handle_email_result(task):
|
|||||||
logger.error(f"Email task failed: {task.result}")
|
logger.error(f"Email task failed: {task.result}")
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
def send_schedule_reminder_email(schedule_id):
|
def send_schedule_reminder_email(schedule_id):
|
||||||
"""
|
"""
|
||||||
Sends an email reminder for a specific schedule.
|
Sends an email reminder for a specific schedule.
|
||||||
@ -871,8 +883,13 @@ def send_schedule_reminder_email(schedule_id):
|
|||||||
schedule = Schedule.objects.get(pk=schedule_id)
|
schedule = Schedule.objects.get(pk=schedule_id)
|
||||||
|
|
||||||
# Ensure the user has an email and the schedule is not completed/canceled
|
# Ensure the user has an email and the schedule is not completed/canceled
|
||||||
if not schedule.scheduled_by.email or schedule.status in ["completed", "canceled"]:
|
if not schedule.scheduled_by.email or schedule.status in [
|
||||||
logger.error(f"Skipping email for Schedule ID {schedule_id}: No email or schedule status is {schedule.status}.")
|
"completed",
|
||||||
|
"canceled",
|
||||||
|
]:
|
||||||
|
logger.error(
|
||||||
|
f"Skipping email for Schedule ID {schedule_id}: No email or schedule status is {schedule.status}."
|
||||||
|
)
|
||||||
return
|
return
|
||||||
|
|
||||||
user_email = schedule.scheduled_by.email
|
user_email = schedule.scheduled_by.email
|
||||||
@ -882,35 +899,51 @@ def send_schedule_reminder_email(schedule_id):
|
|||||||
"""
|
"""
|
||||||
Reminder: You have an appointment scheduled for {scheduled_type} After 15 minutes <a href="{url}" target="_blank">View</a>.
|
Reminder: You have an appointment scheduled for {scheduled_type} After 15 minutes <a href="{url}" target="_blank">View</a>.
|
||||||
"""
|
"""
|
||||||
).format(scheduled_type=schedule.scheduled_type, url=reverse("schedule_calendar", kwargs={"dealer_slug": schedule.dealer.slug})),)
|
).format(
|
||||||
|
scheduled_type=schedule.scheduled_type,
|
||||||
|
url=reverse(
|
||||||
|
"schedule_calendar", kwargs={"dealer_slug": schedule.dealer.slug}
|
||||||
|
),
|
||||||
|
),
|
||||||
|
)
|
||||||
# Prepare context for email templates
|
# Prepare context for email templates
|
||||||
context = {
|
context = {
|
||||||
'schedule_purpose': schedule.purpose,
|
"schedule_purpose": schedule.purpose,
|
||||||
'scheduled_at': schedule.scheduled_at.astimezone(timezone.get_current_timezone()).strftime('%Y-%m-%d %H:%M %Z'), # Format with timezone
|
"scheduled_at": schedule.scheduled_at.astimezone(
|
||||||
'schedule_type': schedule.scheduled_type,
|
timezone.get_current_timezone()
|
||||||
'customer_name': schedule.customer.customer_name if schedule.customer else 'N/A',
|
).strftime("%Y-%m-%d %H:%M %Z"), # Format with timezone
|
||||||
'notes': schedule.notes,
|
"schedule_type": schedule.scheduled_type,
|
||||||
'user_name': schedule.scheduled_by.get_full_name() or schedule.scheduled_by.email,
|
"customer_name": schedule.customer.customer_name
|
||||||
|
if schedule.customer
|
||||||
|
else "N/A",
|
||||||
|
"notes": schedule.notes,
|
||||||
|
"user_name": schedule.scheduled_by.get_full_name()
|
||||||
|
or schedule.scheduled_by.email,
|
||||||
}
|
}
|
||||||
|
|
||||||
# Render email content from templates
|
# Render email content from templates
|
||||||
html_message = render_to_string('emails/schedule_reminder.html', context)
|
html_message = render_to_string("emails/schedule_reminder.html", context)
|
||||||
plain_message = render_to_string('emails/schedule_reminder.txt', context)
|
plain_message = render_to_string("emails/schedule_reminder.txt", context)
|
||||||
|
|
||||||
send_mail(
|
send_mail(
|
||||||
f'Reminder: Your Upcoming Schedule - {schedule.purpose}',
|
f"Reminder: Your Upcoming Schedule - {schedule.purpose}",
|
||||||
plain_message,
|
plain_message,
|
||||||
settings.DEFAULT_FROM_EMAIL,
|
settings.DEFAULT_FROM_EMAIL,
|
||||||
[user_email],
|
[user_email],
|
||||||
html_message=html_message,
|
html_message=html_message,
|
||||||
)
|
)
|
||||||
logger.info(f"Successfully sent reminder email for Schedule ID: {schedule_id} to {user_email}")
|
logger.info(
|
||||||
|
f"Successfully sent reminder email for Schedule ID: {schedule_id} to {user_email}"
|
||||||
|
)
|
||||||
|
|
||||||
except Schedule.DoesNotExist:
|
except Schedule.DoesNotExist:
|
||||||
logger.info(f"Schedule with ID {schedule_id} does not exist. Cannot send reminder.")
|
logger.info(
|
||||||
|
f"Schedule with ID {schedule_id} does not exist. Cannot send reminder."
|
||||||
|
)
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
logger.info(f"Error sending reminder email for Schedule ID {schedule_id}: {e}")
|
logger.info(f"Error sending reminder email for Schedule ID {schedule_id}: {e}")
|
||||||
|
|
||||||
|
|
||||||
# Optional: A hook function to log the status of the email task (add to your_app/tasks.py)
|
# Optional: A hook function to log the status of the email task (add to your_app/tasks.py)
|
||||||
def log_email_status(task):
|
def log_email_status(task):
|
||||||
"""
|
"""
|
||||||
@ -918,9 +951,14 @@ def log_email_status(task):
|
|||||||
It logs whether the task was successful or not.
|
It logs whether the task was successful or not.
|
||||||
"""
|
"""
|
||||||
if task.success:
|
if task.success:
|
||||||
logger.info(f"Email task for Schedule ID {task.args[0]} completed successfully. Result: {task.result}")
|
logger.info(
|
||||||
|
f"Email task for Schedule ID {task.args[0]} completed successfully. Result: {task.result}"
|
||||||
|
)
|
||||||
else:
|
else:
|
||||||
logger.error(f"Email task for Schedule ID {task.args[0]} failed. Error: {task.result}")
|
logger.error(
|
||||||
|
f"Email task for Schedule ID {task.args[0]} failed. Error: {task.result}"
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
def remove_reservation_by_id(reservation_id):
|
def remove_reservation_by_id(reservation_id):
|
||||||
try:
|
try:
|
||||||
@ -931,8 +969,9 @@ def remove_reservation_by_id(reservation_id):
|
|||||||
except Exception as e:
|
except Exception as e:
|
||||||
logger.error(f"Error removing reservation with ID {reservation_id}: {e}")
|
logger.error(f"Error removing reservation with ID {reservation_id}: {e}")
|
||||||
|
|
||||||
|
|
||||||
def test_task(**kwargs):
|
def test_task(**kwargs):
|
||||||
print("TASK : ",kwargs.get("dealer"))
|
print("TASK : ", kwargs.get("dealer"))
|
||||||
|
|
||||||
|
|
||||||
def generate_car_image_task(car_image_id):
|
def generate_car_image_task(car_image_id):
|
||||||
@ -940,22 +979,25 @@ def generate_car_image_task(car_image_id):
|
|||||||
Simple async task to generate car image
|
Simple async task to generate car image
|
||||||
"""
|
"""
|
||||||
from inventory.utils import generate_car_image_simple
|
from inventory.utils import generate_car_image_simple
|
||||||
|
|
||||||
try:
|
try:
|
||||||
car_image = CarImage.objects.get(id=car_image_id)
|
car_image = CarImage.objects.get(id=car_image_id)
|
||||||
result = generate_car_image_simple(car_image)
|
result = generate_car_image_simple(car_image)
|
||||||
|
|
||||||
return {
|
return {
|
||||||
'success': result.get('success', False),
|
"success": result.get("success", False),
|
||||||
'car_image_id': car_image_id,
|
"car_image_id": car_image_id,
|
||||||
'error': result.get('error'),
|
"error": result.get("error"),
|
||||||
'message': 'Image generated' if result.get('success') else 'Generation failed'
|
"message": "Image generated"
|
||||||
|
if result.get("success")
|
||||||
|
else "Generation failed",
|
||||||
}
|
}
|
||||||
|
|
||||||
except CarImage.DoesNotExist:
|
except CarImage.DoesNotExist:
|
||||||
error_msg = f"CarImage with id {car_image_id} not found"
|
error_msg = f"CarImage with id {car_image_id} not found"
|
||||||
logger.error(error_msg)
|
logger.error(error_msg)
|
||||||
return {'success': False, 'error': error_msg}
|
return {"success": False, "error": error_msg}
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
error_msg = f"Unexpected error: {e}"
|
error_msg = f"Unexpected error: {e}"
|
||||||
logger.error(error_msg)
|
logger.error(error_msg)
|
||||||
return {'success': False, 'error': error_msg}
|
return {"success": False, "error": error_msg}
|
||||||
|
|||||||
@ -13,9 +13,9 @@ from django.db.models import Case, Value, When, IntegerField
|
|||||||
|
|
||||||
register = template.Library()
|
register = template.Library()
|
||||||
|
|
||||||
|
|
||||||
@register.filter
|
@register.filter
|
||||||
def get_percentage(value, total):
|
def get_percentage(value, total):
|
||||||
|
|
||||||
try:
|
try:
|
||||||
value = int(value)
|
value = int(value)
|
||||||
total = int(total)
|
total = int(total)
|
||||||
@ -25,6 +25,7 @@ def get_percentage(value, total):
|
|||||||
except (ValueError, TypeError):
|
except (ValueError, TypeError):
|
||||||
return 0
|
return 0
|
||||||
|
|
||||||
|
|
||||||
@register.filter(name="percentage")
|
@register.filter(name="percentage")
|
||||||
def percentage(value):
|
def percentage(value):
|
||||||
if value is not None:
|
if value is not None:
|
||||||
@ -686,11 +687,12 @@ 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)
|
|
||||||
|
@register.inclusion_tag("sales/tags/invoice_item_formset.html", takes_context=True)
|
||||||
def invoice_item_formset_table(context, itemtxs_formset):
|
def invoice_item_formset_table(context, itemtxs_formset):
|
||||||
return {
|
return {
|
||||||
'entity_slug': context['view'].kwargs['entity_slug'],
|
"entity_slug": context["view"].kwargs["entity_slug"],
|
||||||
'invoice_model': context['invoice'],
|
"invoice_model": context["invoice"],
|
||||||
'total_amount__sum': context['total_amount__sum'],
|
"total_amount__sum": context["total_amount__sum"],
|
||||||
'itemtxs_formset': itemtxs_formset,
|
"itemtxs_formset": itemtxs_formset,
|
||||||
}
|
}
|
||||||
|
|||||||
@ -40,13 +40,18 @@ urlpatterns = [
|
|||||||
views.assign_car_makes,
|
views.assign_car_makes,
|
||||||
name="assign_car_makes",
|
name="assign_car_makes",
|
||||||
),
|
),
|
||||||
|
# dashboards for manager, dealer, inventory and accounatant
|
||||||
|
path(
|
||||||
#dashboards for manager, dealer, inventory and accounatant
|
"dashboards/<slug:dealer_slug>/general/",
|
||||||
path("dashboards/<slug:dealer_slug>/general/", views.general_dashboard,name="general_dashboard"),
|
views.general_dashboard,
|
||||||
#dashboard for sales
|
name="general_dashboard",
|
||||||
path("dashboards/<slug:dealer_slug>/sales/", views.sales_dashboard, name="sales_dashboard"),
|
),
|
||||||
|
# dashboard for sales
|
||||||
|
path(
|
||||||
|
"dashboards/<slug:dealer_slug>/sales/",
|
||||||
|
views.sales_dashboard,
|
||||||
|
name="sales_dashboard",
|
||||||
|
),
|
||||||
path(
|
path(
|
||||||
"<slug:dealer_slug>/cars/aging-inventory/list",
|
"<slug:dealer_slug>/cars/aging-inventory/list",
|
||||||
views.aging_inventory_list_view,
|
views.aging_inventory_list_view,
|
||||||
@ -777,7 +782,11 @@ urlpatterns = [
|
|||||||
views.EstimateDetailView.as_view(),
|
views.EstimateDetailView.as_view(),
|
||||||
name="estimate_detail",
|
name="estimate_detail",
|
||||||
),
|
),
|
||||||
path('<slug:dealer_slug>/sales/estimates/print/<uuid:pk>/', views.EstimatePrintView.as_view(), name='estimate_print'),
|
path(
|
||||||
|
"<slug:dealer_slug>/sales/estimates/print/<uuid:pk>/",
|
||||||
|
views.EstimatePrintView.as_view(),
|
||||||
|
name="estimate_print",
|
||||||
|
),
|
||||||
path(
|
path(
|
||||||
"<slug:dealer_slug>/sales/estimates/create/",
|
"<slug:dealer_slug>/sales/estimates/create/",
|
||||||
views.create_estimate,
|
views.create_estimate,
|
||||||
@ -934,7 +943,6 @@ urlpatterns = [
|
|||||||
views.ItemServiceUpdateView.as_view(),
|
views.ItemServiceUpdateView.as_view(),
|
||||||
name="item_service_update",
|
name="item_service_update",
|
||||||
),
|
),
|
||||||
|
|
||||||
# Expanese
|
# Expanese
|
||||||
path(
|
path(
|
||||||
"<slug:dealer_slug>/items/expeneses/",
|
"<slug:dealer_slug>/items/expeneses/",
|
||||||
@ -1093,32 +1101,47 @@ urlpatterns = [
|
|||||||
name="entity-ic-date",
|
name="entity-ic-date",
|
||||||
),
|
),
|
||||||
# Chart of Accounts...
|
# Chart of Accounts...
|
||||||
path('<slug:dealer_slug>/chart-of-accounts/<slug:entity_slug>/list/',
|
path(
|
||||||
|
"<slug:dealer_slug>/chart-of-accounts/<slug:entity_slug>/list/",
|
||||||
views.ChartOfAccountModelListView.as_view(),
|
views.ChartOfAccountModelListView.as_view(),
|
||||||
name='coa-list'),
|
name="coa-list",
|
||||||
path('<slug:dealer_slug>/chart-of-accounts/<slug:entity_slug>/list/inactive/',
|
),
|
||||||
|
path(
|
||||||
|
"<slug:dealer_slug>/chart-of-accounts/<slug:entity_slug>/list/inactive/",
|
||||||
views.ChartOfAccountModelListView.as_view(inactive=True),
|
views.ChartOfAccountModelListView.as_view(inactive=True),
|
||||||
name='coa-list-inactive'),
|
name="coa-list-inactive",
|
||||||
path('<slug:dealer_slug>/<slug:entity_slug>/create/',
|
),
|
||||||
|
path(
|
||||||
|
"<slug:dealer_slug>/<slug:entity_slug>/create/",
|
||||||
views.ChartOfAccountModelCreateView.as_view(),
|
views.ChartOfAccountModelCreateView.as_view(),
|
||||||
name='coa-create'),
|
name="coa-create",
|
||||||
path('<slug:dealer_slug>/<slug:entity_slug>/detail/<slug:coa_slug>/',
|
),
|
||||||
|
path(
|
||||||
|
"<slug:dealer_slug>/<slug:entity_slug>/detail/<slug:coa_slug>/",
|
||||||
views.ChartOfAccountModelListView.as_view(),
|
views.ChartOfAccountModelListView.as_view(),
|
||||||
name='coa-detail'),
|
name="coa-detail",
|
||||||
path('<slug:dealer_slug>/<slug:entity_slug>/update/<slug:coa_slug>/',
|
),
|
||||||
|
path(
|
||||||
|
"<slug:dealer_slug>/<slug:entity_slug>/update/<slug:coa_slug>/",
|
||||||
views.ChartOfAccountModelUpdateView.as_view(),
|
views.ChartOfAccountModelUpdateView.as_view(),
|
||||||
name='coa-update'),
|
name="coa-update",
|
||||||
|
),
|
||||||
# ACTIONS....
|
# ACTIONS....
|
||||||
path('<slug:dealer_slug>/<slug:entity_slug>/action/<slug:coa_slug>/mark-as-default/',
|
path(
|
||||||
views.CharOfAccountModelActionView.as_view(action_name='mark_as_default'),
|
"<slug:dealer_slug>/<slug:entity_slug>/action/<slug:coa_slug>/mark-as-default/",
|
||||||
name='coa-action-mark-as-default'),
|
views.CharOfAccountModelActionView.as_view(action_name="mark_as_default"),
|
||||||
path('<slug:dealer_slug>/<slug:entity_slug>/action/<slug:coa_slug>/mark-as-active/',
|
name="coa-action-mark-as-default",
|
||||||
views.CharOfAccountModelActionView.as_view(action_name='mark_as_active'),
|
),
|
||||||
name='coa-action-mark-as-active'),
|
path(
|
||||||
path('<slug:dealer_slug>/<slug:entity_slug>/action/<slug:coa_slug>/mark-as-inactive/',
|
"<slug:dealer_slug>/<slug:entity_slug>/action/<slug:coa_slug>/mark-as-active/",
|
||||||
views.CharOfAccountModelActionView.as_view(action_name='mark_as_inactive'),
|
views.CharOfAccountModelActionView.as_view(action_name="mark_as_active"),
|
||||||
name='coa-action-mark-as-inactive'),
|
name="coa-action-mark-as-active",
|
||||||
|
),
|
||||||
|
path(
|
||||||
|
"<slug:dealer_slug>/<slug:entity_slug>/action/<slug:coa_slug>/mark-as-inactive/",
|
||||||
|
views.CharOfAccountModelActionView.as_view(action_name="mark_as_inactive"),
|
||||||
|
name="coa-action-mark-as-inactive",
|
||||||
|
),
|
||||||
# CASH FLOW STATEMENTS...
|
# CASH FLOW STATEMENTS...
|
||||||
# Entities...
|
# Entities...
|
||||||
path(
|
path(
|
||||||
@ -1294,40 +1317,74 @@ urlpatterns = [
|
|||||||
views.PurchaseOrderMarkAsVoidView.as_view(),
|
views.PurchaseOrderMarkAsVoidView.as_view(),
|
||||||
name="po-action-mark-as-void",
|
name="po-action-mark-as-void",
|
||||||
),
|
),
|
||||||
|
|
||||||
# reports
|
# reports
|
||||||
path(
|
path(
|
||||||
"<slug:dealer_slug>/purchase-report/",
|
"<slug:dealer_slug>/purchase-report/",
|
||||||
views.purchase_report_view,
|
views.purchase_report_view,
|
||||||
name="po-report",
|
name="po-report",
|
||||||
),
|
),
|
||||||
path('purchase-report/<slug:dealer_slug>/csv/', views.purchase_report_csv_export, name='purchase-report-csv-export'),
|
path(
|
||||||
|
"purchase-report/<slug:dealer_slug>/csv/",
|
||||||
|
views.purchase_report_csv_export,
|
||||||
|
name="purchase-report-csv-export",
|
||||||
|
),
|
||||||
path(
|
path(
|
||||||
"<slug:dealer_slug>/car-sale-report/",
|
"<slug:dealer_slug>/car-sale-report/",
|
||||||
views.car_sale_report_view,
|
views.car_sale_report_view,
|
||||||
name="car-sale-report",
|
name="car-sale-report",
|
||||||
),
|
),
|
||||||
path('car-sale-report/<slug:dealer_slug>/csv/', views.car_sale_report_csv_export, name='car-sale-report-csv-export'),
|
path(
|
||||||
|
"car-sale-report/<slug:dealer_slug>/csv/",
|
||||||
path('feature/recall/', views.RecallListView.as_view(), name='recall_list'),
|
views.car_sale_report_csv_export,
|
||||||
path('feature/recall/filter/', views.RecallFilterView, name='recall_filter'),
|
name="car-sale-report-csv-export",
|
||||||
path('feature/recall/<int:pk>/view/', views.RecallDetailView.as_view(), name='recall_detail'),
|
),
|
||||||
path('feature/recall/create/', views.RecallCreateView.as_view(), name='recall_create'),
|
path("feature/recall/", views.RecallListView.as_view(), name="recall_list"),
|
||||||
path('feature/recall/success/', views.RecallSuccessView.as_view(), name='recall_success'),
|
path("feature/recall/filter/", views.RecallFilterView, name="recall_filter"),
|
||||||
|
path(
|
||||||
path('<slug:dealer_slug>/schedules/calendar/', views.schedule_calendar, name='schedule_calendar'),
|
"feature/recall/<int:pk>/view/",
|
||||||
|
views.RecallDetailView.as_view(),
|
||||||
|
name="recall_detail",
|
||||||
|
),
|
||||||
|
path(
|
||||||
|
"feature/recall/create/", views.RecallCreateView.as_view(), name="recall_create"
|
||||||
|
),
|
||||||
|
path(
|
||||||
|
"feature/recall/success/",
|
||||||
|
views.RecallSuccessView.as_view(),
|
||||||
|
name="recall_success",
|
||||||
|
),
|
||||||
|
path(
|
||||||
|
"<slug:dealer_slug>/schedules/calendar/",
|
||||||
|
views.schedule_calendar,
|
||||||
|
name="schedule_calendar",
|
||||||
|
),
|
||||||
# staff profile
|
# staff profile
|
||||||
path('<slug:dealer_slug>/staff/<slug:slug>detail/', views.StaffDetailView.as_view(), name='staff_detail'),
|
path(
|
||||||
|
"<slug:dealer_slug>/staff/<slug:slug>detail/",
|
||||||
|
views.StaffDetailView.as_view(),
|
||||||
|
name="staff_detail",
|
||||||
|
),
|
||||||
# tickets
|
# tickets
|
||||||
path('help_center/view/', views.help_center, name='help_center'),
|
path("help_center/view/", views.help_center, name="help_center"),
|
||||||
path('<slug:dealer_slug>/help_center/tickets/', views.ticket_list, name='ticket_list'),
|
path(
|
||||||
path('help_center/tickets/<slug:dealer_slug>/create/', views.create_ticket, name='create_ticket'),
|
"<slug:dealer_slug>/help_center/tickets/", views.ticket_list, name="ticket_list"
|
||||||
path('<slug:dealer_slug>/help_center/tickets/<int:ticket_id>/', views.ticket_detail, name='ticket_detail'),
|
),
|
||||||
path('help_center/tickets/<int:ticket_id>/update/', views.ticket_update, name='ticket_update'),
|
path(
|
||||||
|
"help_center/tickets/<slug:dealer_slug>/create/",
|
||||||
|
views.create_ticket,
|
||||||
|
name="create_ticket",
|
||||||
|
),
|
||||||
|
path(
|
||||||
|
"<slug:dealer_slug>/help_center/tickets/<int:ticket_id>/",
|
||||||
|
views.ticket_detail,
|
||||||
|
name="ticket_detail",
|
||||||
|
),
|
||||||
|
path(
|
||||||
|
"help_center/tickets/<int:ticket_id>/update/",
|
||||||
|
views.ticket_update,
|
||||||
|
name="ticket_update",
|
||||||
|
),
|
||||||
# path('help_center/tickets/<int:ticket_id>/ticket_mark_resolved/', views.ticket_mark_resolved, name='ticket_mark_resolved'),
|
# path('help_center/tickets/<int:ticket_id>/ticket_mark_resolved/', views.ticket_mark_resolved, name='ticket_mark_resolved'),
|
||||||
|
|
||||||
]
|
]
|
||||||
|
|
||||||
handler404 = "inventory.views.custom_page_not_found_view"
|
handler404 = "inventory.views.custom_page_not_found_view"
|
||||||
|
|||||||
@ -73,15 +73,12 @@ def get_jwt_token():
|
|||||||
try:
|
try:
|
||||||
response = requests.post(url, headers=headers, json=data)
|
response = requests.post(url, headers=headers, json=data)
|
||||||
response.raise_for_status()
|
response.raise_for_status()
|
||||||
#logging for success
|
# logging for success
|
||||||
logger.info("Successfully fetched JWT token.")
|
logger.info("Successfully fetched JWT token.")
|
||||||
return response.text
|
return response.text
|
||||||
except requests.exceptions.RequestException as e:
|
except requests.exceptions.RequestException as e:
|
||||||
#logging for error
|
# logging for error
|
||||||
logger.error(
|
logger.error(f"HTTP error fetching JWT token from {url}: ", exc_info=True)
|
||||||
f"HTTP error fetching JWT token from {url}: ",
|
|
||||||
exc_info=True
|
|
||||||
)
|
|
||||||
print(f"Error obtaining JWT token: {e}")
|
print(f"Error obtaining JWT token: {e}")
|
||||||
return None
|
return None
|
||||||
|
|
||||||
@ -169,7 +166,7 @@ def send_email(from_, to_, subject, message):
|
|||||||
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 get_user_type(request):
|
def get_user_type(request):
|
||||||
@ -239,7 +236,7 @@ def reserve_car(car, request):
|
|||||||
# --- Logging for Success ---
|
# --- Logging for Success ---
|
||||||
DjangoQSchedule.objects.create(
|
DjangoQSchedule.objects.create(
|
||||||
name=f"remove_reservation_for_car_with_vin_{car.vin}",
|
name=f"remove_reservation_for_car_with_vin_{car.vin}",
|
||||||
func='inventory.tasks.remove_reservation_by_id',
|
func="inventory.tasks.remove_reservation_by_id",
|
||||||
args=reservation.pk,
|
args=reservation.pk,
|
||||||
schedule_type=DjangoQSchedule.ONCE,
|
schedule_type=DjangoQSchedule.ONCE,
|
||||||
next_run=reserved_until,
|
next_run=reserved_until,
|
||||||
@ -257,7 +254,7 @@ def reserve_car(car, request):
|
|||||||
f"Error reserving car {car.pk} ('{car.id_car_make} {car.id_car_model}') "
|
f"Error reserving car {car.pk} ('{car.id_car_make} {car.id_car_model}') "
|
||||||
f"for user {request.user} . "
|
f"for user {request.user} . "
|
||||||
f"Error: {e}",
|
f"Error: {e}",
|
||||||
exc_info=True
|
exc_info=True,
|
||||||
)
|
)
|
||||||
messages.error(request, f"Error reserving car: {e}")
|
messages.error(request, f"Error reserving car: {e}")
|
||||||
|
|
||||||
@ -1038,22 +1035,25 @@ class CarFinanceCalculator1:
|
|||||||
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()
|
||||||
|
|
||||||
|
|
||||||
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()
|
||||||
if not vat:
|
if not vat:
|
||||||
raise ObjectDoesNotExist("No active VAT rate found")
|
raise ObjectDoesNotExist("No active VAT rate found")
|
||||||
return vat.rate
|
return vat.rate
|
||||||
|
|
||||||
def _get_additional_services(self):
|
def _get_additional_services(self):
|
||||||
return [x for item in self.item_transactions
|
return [
|
||||||
|
x
|
||||||
|
for item in self.item_transactions
|
||||||
for x in item.item_model.car.additional_services
|
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):
|
def get_items(self):
|
||||||
return self._get_item_transactions()
|
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
|
||||||
@ -1068,17 +1068,17 @@ class CarFinanceCalculator1:
|
|||||||
quantity = self._get_quantity(item)
|
quantity = self._get_quantity(item)
|
||||||
car = item.item_model.car
|
car = item.item_model.car
|
||||||
unit_price = Decimal(car.marked_price)
|
unit_price = Decimal(car.marked_price)
|
||||||
discount = self.extra_info.data.get("discount",0)
|
discount = self.extra_info.data.get("discount", 0)
|
||||||
sell_price = unit_price - Decimal(discount)
|
sell_price = unit_price - Decimal(discount)
|
||||||
return {
|
return {
|
||||||
"item_number": item.item_model.item_number,
|
"item_number": item.item_model.item_number,
|
||||||
"vin": car.vin, #car_info.get("vin"),
|
"vin": car.vin, # car_info.get("vin"),
|
||||||
"make": car.id_car_make ,#car_info.get("make"),
|
"make": car.id_car_make, # car_info.get("make"),
|
||||||
"model": car.id_car_model ,#car_info.get("model"),
|
"model": car.id_car_model, # car_info.get("model"),
|
||||||
"year": car.year ,# car_info.get("year"),
|
"year": car.year, # car_info.get("year"),
|
||||||
"logo": car.logo, # getattr(car.id_car_make, "logo", ""),
|
"logo": car.logo, # getattr(car.id_car_make, "logo", ""),
|
||||||
"trim": car.id_car_trim ,# car_info.get("trim"),
|
"trim": car.id_car_trim, # car_info.get("trim"),
|
||||||
"mileage": car.mileage ,# car_info.get("mileage"),
|
"mileage": car.mileage, # car_info.get("mileage"),
|
||||||
"cost_price": car.cost_price,
|
"cost_price": car.cost_price,
|
||||||
"selling_price": car.selling_price,
|
"selling_price": car.selling_price,
|
||||||
"marked_price": car.marked_price,
|
"marked_price": car.marked_price,
|
||||||
@ -1091,21 +1091,23 @@ class CarFinanceCalculator1:
|
|||||||
"total_discount": discount,
|
"total_discount": discount,
|
||||||
"final_price": sell_price + (sell_price * self.vat_rate),
|
"final_price": sell_price + (sell_price * self.vat_rate),
|
||||||
"total_additionals": car.total_additional_services,
|
"total_additionals": car.total_additional_services,
|
||||||
"grand_total": sell_price + (sell_price * self.vat_rate) + car.total_additional_services,
|
"grand_total": sell_price
|
||||||
"additional_services": car.additional_services,# self._get_nested_value(
|
+ (sell_price * self.vat_rate)
|
||||||
#item, self.ADDITIONAL_SERVICES_KEY
|
+ car.total_additional_services,
|
||||||
#),
|
"additional_services": car.additional_services, # self._get_nested_value(
|
||||||
|
# item, self.ADDITIONAL_SERVICES_KEY
|
||||||
|
# ),
|
||||||
}
|
}
|
||||||
|
|
||||||
def calculate_totals(self):
|
def calculate_totals(self):
|
||||||
total_price = sum(
|
total_price = sum(
|
||||||
Decimal(item.item_model.car.marked_price)
|
Decimal(item.item_model.car.marked_price) for item in self.item_transactions
|
||||||
for item in self.item_transactions
|
|
||||||
)
|
)
|
||||||
total_additionals = sum(
|
total_additionals = sum(
|
||||||
Decimal(item.price_) for item 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_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)
|
||||||
@ -1113,13 +1115,15 @@ class CarFinanceCalculator1:
|
|||||||
total_vat_amount = total_price_discounted * self.vat_rate
|
total_vat_amount = total_price_discounted * self.vat_rate
|
||||||
|
|
||||||
return {
|
return {
|
||||||
"total_price_discounted":total_price_discounted,
|
"total_price_discounted": total_price_discounted,
|
||||||
"total_price_before_discount":total_price,
|
"total_price_before_discount": total_price,
|
||||||
"total_price": total_price_discounted,
|
"total_price": total_price_discounted,
|
||||||
"total_vat_amount": total_vat_amount,
|
"total_vat_amount": total_vat_amount,
|
||||||
"total_discount": Decimal(total_discount),
|
"total_discount": Decimal(total_discount),
|
||||||
"total_additionals": total_additionals,
|
"total_additionals": total_additionals,
|
||||||
"grand_total":total_price_discounted + total_vat_amount + total_additionals,
|
"grand_total": total_price_discounted
|
||||||
|
+ total_vat_amount
|
||||||
|
+ total_additionals,
|
||||||
}
|
}
|
||||||
|
|
||||||
def get_finance_data(self):
|
def get_finance_data(self):
|
||||||
@ -1131,7 +1135,9 @@ class CarFinanceCalculator1:
|
|||||||
),
|
),
|
||||||
"total_price": round(totals["total_price"], 2),
|
"total_price": round(totals["total_price"], 2),
|
||||||
"total_price_discounted": round(totals["total_price_discounted"], 2),
|
"total_price_discounted": round(totals["total_price_discounted"], 2),
|
||||||
"total_price_before_discount": round(totals["total_price_before_discount"], 2),
|
"total_price_before_discount": round(
|
||||||
|
totals["total_price_before_discount"], 2
|
||||||
|
),
|
||||||
"total_vat": round(totals["total_vat_amount"] + totals["total_price"], 2),
|
"total_vat": round(totals["total_vat_amount"] + totals["total_price"], 2),
|
||||||
"total_vat_amount": round(totals["total_vat_amount"], 2),
|
"total_vat_amount": round(totals["total_vat_amount"], 2),
|
||||||
"total_discount": round(totals["total_discount"], 2),
|
"total_discount": round(totals["total_discount"], 2),
|
||||||
@ -1140,6 +1146,8 @@ class CarFinanceCalculator1:
|
|||||||
"additionals": self._get_additional_services(),
|
"additionals": self._get_additional_services(),
|
||||||
"vat": round(self.vat_rate, 2),
|
"vat": round(self.vat_rate, 2),
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
class CarFinanceCalculator:
|
class CarFinanceCalculator:
|
||||||
"""
|
"""
|
||||||
Class responsible for calculating car financing details.
|
Class responsible for calculating car financing details.
|
||||||
@ -1185,22 +1193,25 @@ class CarFinanceCalculator:
|
|||||||
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()
|
||||||
|
|
||||||
|
|
||||||
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()
|
||||||
if not vat:
|
if not vat:
|
||||||
raise ObjectDoesNotExist("No active VAT rate found")
|
raise ObjectDoesNotExist("No active VAT rate found")
|
||||||
return vat.rate
|
return vat.rate
|
||||||
|
|
||||||
def _get_additional_services(self):
|
def _get_additional_services(self):
|
||||||
return [x for item in self.item_transactions
|
return [
|
||||||
|
x
|
||||||
|
for item in self.item_transactions
|
||||||
for x in item.item_model.car.additional_services
|
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):
|
def get_items(self):
|
||||||
return self._get_item_transactions()
|
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
|
||||||
@ -1215,17 +1226,17 @@ class CarFinanceCalculator:
|
|||||||
quantity = self._get_quantity(item)
|
quantity = self._get_quantity(item)
|
||||||
car = item.item_model.car
|
car = item.item_model.car
|
||||||
unit_price = Decimal(car.marked_price)
|
unit_price = Decimal(car.marked_price)
|
||||||
discount = self.extra_info.data.get("discount",0)
|
discount = self.extra_info.data.get("discount", 0)
|
||||||
sell_price = unit_price - Decimal(discount)
|
sell_price = unit_price - Decimal(discount)
|
||||||
return {
|
return {
|
||||||
"item_number": item.item_model.item_number,
|
"item_number": item.item_model.item_number,
|
||||||
"vin": car.vin, #car_info.get("vin"),
|
"vin": car.vin, # car_info.get("vin"),
|
||||||
"make": car.id_car_make ,#car_info.get("make"),
|
"make": car.id_car_make, # car_info.get("make"),
|
||||||
"model": car.id_car_model ,#car_info.get("model"),
|
"model": car.id_car_model, # car_info.get("model"),
|
||||||
"year": car.year ,# car_info.get("year"),
|
"year": car.year, # car_info.get("year"),
|
||||||
"logo": car.logo, # getattr(car.id_car_make, "logo", ""),
|
"logo": car.logo, # getattr(car.id_car_make, "logo", ""),
|
||||||
"trim": car.id_car_trim ,# car_info.get("trim"),
|
"trim": car.id_car_trim, # car_info.get("trim"),
|
||||||
"mileage": car.mileage ,# car_info.get("mileage"),
|
"mileage": car.mileage, # car_info.get("mileage"),
|
||||||
"cost_price": car.cost_price,
|
"cost_price": car.cost_price,
|
||||||
"selling_price": car.selling_price,
|
"selling_price": car.selling_price,
|
||||||
"marked_price": car.marked_price,
|
"marked_price": car.marked_price,
|
||||||
@ -1238,21 +1249,23 @@ class CarFinanceCalculator:
|
|||||||
"total_discount": discount,
|
"total_discount": discount,
|
||||||
"final_price": sell_price + (sell_price * self.vat_rate),
|
"final_price": sell_price + (sell_price * self.vat_rate),
|
||||||
"total_additionals": car.total_additional_services,
|
"total_additionals": car.total_additional_services,
|
||||||
"grand_total": sell_price + (sell_price * self.vat_rate) + car.total_additional_services,
|
"grand_total": sell_price
|
||||||
"additional_services": car.additional_services,# self._get_nested_value(
|
+ (sell_price * self.vat_rate)
|
||||||
#item, self.ADDITIONAL_SERVICES_KEY
|
+ car.total_additional_services,
|
||||||
#),
|
"additional_services": car.additional_services, # self._get_nested_value(
|
||||||
|
# item, self.ADDITIONAL_SERVICES_KEY
|
||||||
|
# ),
|
||||||
}
|
}
|
||||||
|
|
||||||
def calculate_totals(self):
|
def calculate_totals(self):
|
||||||
total_price = sum(
|
total_price = sum(
|
||||||
Decimal(item.item_model.car.marked_price)
|
Decimal(item.item_model.car.marked_price) for item in self.item_transactions
|
||||||
for item in self.item_transactions
|
|
||||||
)
|
)
|
||||||
total_additionals = sum(
|
total_additionals = sum(
|
||||||
Decimal(item.price_) for item 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_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)
|
||||||
@ -1260,13 +1273,15 @@ class CarFinanceCalculator:
|
|||||||
total_vat_amount = total_price_discounted * self.vat_rate
|
total_vat_amount = total_price_discounted * self.vat_rate
|
||||||
|
|
||||||
return {
|
return {
|
||||||
"total_price_discounted":total_price_discounted,
|
"total_price_discounted": total_price_discounted,
|
||||||
"total_price_before_discount":total_price,
|
"total_price_before_discount": total_price,
|
||||||
"total_price": total_price_discounted,
|
"total_price": total_price_discounted,
|
||||||
"total_vat_amount": total_vat_amount,
|
"total_vat_amount": total_vat_amount,
|
||||||
"total_discount": Decimal(total_discount),
|
"total_discount": Decimal(total_discount),
|
||||||
"total_additionals": total_additionals,
|
"total_additionals": total_additionals,
|
||||||
"grand_total":total_price_discounted + total_vat_amount + total_additionals,
|
"grand_total": total_price_discounted
|
||||||
|
+ total_vat_amount
|
||||||
|
+ total_additionals,
|
||||||
}
|
}
|
||||||
|
|
||||||
def get_finance_data(self):
|
def get_finance_data(self):
|
||||||
@ -1278,7 +1293,9 @@ class CarFinanceCalculator:
|
|||||||
),
|
),
|
||||||
"total_price": round(totals["total_price"], 2),
|
"total_price": round(totals["total_price"], 2),
|
||||||
"total_price_discounted": round(totals["total_price_discounted"], 2),
|
"total_price_discounted": round(totals["total_price_discounted"], 2),
|
||||||
"total_price_before_discount": round(totals["total_price_before_discount"], 2),
|
"total_price_before_discount": round(
|
||||||
|
totals["total_price_before_discount"], 2
|
||||||
|
),
|
||||||
"total_vat": round(totals["total_vat_amount"] + totals["total_price"], 2),
|
"total_vat": round(totals["total_vat_amount"] + totals["total_price"], 2),
|
||||||
"total_vat_amount": round(totals["total_vat_amount"], 2),
|
"total_vat_amount": round(totals["total_vat_amount"], 2),
|
||||||
"total_discount": round(totals["total_discount"], 2),
|
"total_discount": round(totals["total_discount"], 2),
|
||||||
@ -1288,11 +1305,12 @@ class CarFinanceCalculator:
|
|||||||
"vat": round(self.vat_rate, 2),
|
"vat": round(self.vat_rate, 2),
|
||||||
}
|
}
|
||||||
|
|
||||||
def get_finance_data(estimate,dealer):
|
|
||||||
vat = models.VatRate.objects.filter(dealer=dealer,is_active=True).first()
|
def get_finance_data(estimate, dealer):
|
||||||
|
vat = models.VatRate.objects.filter(dealer=dealer, is_active=True).first()
|
||||||
item = estimate.get_itemtxs_data()[0].first()
|
item = estimate.get_itemtxs_data()[0].first()
|
||||||
car = item.item_model.car
|
car = item.item_model.car
|
||||||
if isinstance(estimate,InvoiceModel) and hasattr(estimate, "ce_model"):
|
if isinstance(estimate, InvoiceModel) and hasattr(estimate, "ce_model"):
|
||||||
estimate = estimate.ce_model
|
estimate = estimate.ce_model
|
||||||
|
|
||||||
extra_info = models.ExtraInfo.objects.get(
|
extra_info = models.ExtraInfo.objects.get(
|
||||||
@ -1304,10 +1322,10 @@ def get_finance_data(estimate,dealer):
|
|||||||
discount = Decimal(discount)
|
discount = Decimal(discount)
|
||||||
|
|
||||||
additional_services = car.get_additional_services()
|
additional_services = car.get_additional_services()
|
||||||
discounted_price=(Decimal(car.marked_price) - discount)
|
discounted_price = Decimal(car.marked_price) - discount
|
||||||
vat_amount = discounted_price * vat.rate
|
vat_amount = discounted_price * vat.rate
|
||||||
total_services_vat=sum([x[1] for x in additional_services.get("services")])
|
total_services_vat = sum([x[1] for x in additional_services.get("services")])
|
||||||
total_vat=vat_amount+total_services_vat
|
total_vat = vat_amount + total_services_vat
|
||||||
return {
|
return {
|
||||||
"car": car,
|
"car": car,
|
||||||
"discounted_price": discounted_price or 0,
|
"discounted_price": discounted_price or 0,
|
||||||
@ -1317,12 +1335,11 @@ def get_finance_data(estimate,dealer):
|
|||||||
"discount_amount": discount,
|
"discount_amount": discount,
|
||||||
"additional_services": additional_services,
|
"additional_services": additional_services,
|
||||||
"final_price": discounted_price + vat_amount,
|
"final_price": discounted_price + vat_amount,
|
||||||
"total_services_vat":total_services_vat,
|
"total_services_vat": total_services_vat,
|
||||||
"total_vat":total_vat,
|
"total_vat": total_vat,
|
||||||
"grand_total": discounted_price + total_vat + additional_services.get("total")
|
"grand_total": discounted_price + total_vat + additional_services.get("total"),
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
# totals = self.calculate_totals()
|
# totals = self.calculate_totals()
|
||||||
# return {
|
# return {
|
||||||
# "car": [self._get_car_data(item) for item in self.item_transactions],
|
# "car": [self._get_car_data(item) for item in self.item_transactions],
|
||||||
@ -1340,6 +1357,8 @@ def get_finance_data(estimate,dealer):
|
|||||||
# "additionals": self._get_additional_services(),
|
# "additionals": self._get_additional_services(),
|
||||||
# "vat": round(self.vat_rate, 2),
|
# "vat": round(self.vat_rate, 2),
|
||||||
# }
|
# }
|
||||||
|
|
||||||
|
|
||||||
# class CarFinanceCalculator:
|
# class CarFinanceCalculator:
|
||||||
# """
|
# """
|
||||||
# Class responsible for calculating car financing details.
|
# Class responsible for calculating car financing details.
|
||||||
@ -1554,7 +1573,6 @@ def get_local_name(self):
|
|||||||
return getattr(self, "name", None)
|
return getattr(self, "name", None)
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
@transaction.atomic
|
@transaction.atomic
|
||||||
def set_invoice_payment(dealer, entity, invoice, amount, payment_method):
|
def set_invoice_payment(dealer, entity, invoice, amount, payment_method):
|
||||||
"""
|
"""
|
||||||
@ -1566,6 +1584,7 @@ def set_invoice_payment(dealer, entity, invoice, amount, payment_method):
|
|||||||
|
|
||||||
_post_sale_and_cogs(invoice, dealer)
|
_post_sale_and_cogs(invoice, dealer)
|
||||||
|
|
||||||
|
|
||||||
def _post_sale_and_cogs(invoice, dealer):
|
def _post_sale_and_cogs(invoice, dealer):
|
||||||
"""
|
"""
|
||||||
For every car line on the invoice:
|
For every car line on the invoice:
|
||||||
@ -1574,15 +1593,39 @@ def _post_sale_and_cogs(invoice, dealer):
|
|||||||
"""
|
"""
|
||||||
entity = invoice.ledger.entity
|
entity = invoice.ledger.entity
|
||||||
# calc = CarFinanceCalculator(invoice)
|
# calc = CarFinanceCalculator(invoice)
|
||||||
data = get_finance_data(invoice,dealer)
|
data = get_finance_data(invoice, dealer)
|
||||||
car = data.get("car")
|
car = data.get("car")
|
||||||
cash_acc = entity.get_default_coa_accounts().filter(role_default=True, role=roles.ASSET_CA_CASH).first()
|
cash_acc = (
|
||||||
ar_acc = entity.get_default_coa_accounts().filter(role_default=True, role=roles.ASSET_CA_RECEIVABLES).first()
|
entity.get_default_coa_accounts()
|
||||||
vat_acc = entity.get_default_coa_accounts().filter(role_default=True, role=roles.LIABILITY_CL_TAXES_PAYABLE).first()
|
.filter(role_default=True, role=roles.ASSET_CA_CASH)
|
||||||
car_rev = entity.get_default_coa_accounts().filter(role_default=True, role=roles.INCOME_OPERATIONAL).first()
|
.first()
|
||||||
|
)
|
||||||
|
ar_acc = (
|
||||||
|
entity.get_default_coa_accounts()
|
||||||
|
.filter(role_default=True, role=roles.ASSET_CA_RECEIVABLES)
|
||||||
|
.first()
|
||||||
|
)
|
||||||
|
vat_acc = (
|
||||||
|
entity.get_default_coa_accounts()
|
||||||
|
.filter(role_default=True, role=roles.LIABILITY_CL_TAXES_PAYABLE)
|
||||||
|
.first()
|
||||||
|
)
|
||||||
|
car_rev = (
|
||||||
|
entity.get_default_coa_accounts()
|
||||||
|
.filter(role_default=True, role=roles.INCOME_OPERATIONAL)
|
||||||
|
.first()
|
||||||
|
)
|
||||||
add_rev = entity.get_default_coa_accounts().filter(code="4020").first()
|
add_rev = entity.get_default_coa_accounts().filter(code="4020").first()
|
||||||
cogs_acc = entity.get_default_coa_accounts().filter(role_default=True, role=roles.COGS).first()
|
cogs_acc = (
|
||||||
inv_acc = entity.get_default_coa_accounts().filter(role_default=True, role=roles.ASSET_CA_INVENTORY).first()
|
entity.get_default_coa_accounts()
|
||||||
|
.filter(role_default=True, role=roles.COGS)
|
||||||
|
.first()
|
||||||
|
)
|
||||||
|
inv_acc = (
|
||||||
|
entity.get_default_coa_accounts()
|
||||||
|
.filter(role_default=True, role=roles.ASSET_CA_INVENTORY)
|
||||||
|
.first()
|
||||||
|
)
|
||||||
|
|
||||||
# for car_data in data['cars']:
|
# for car_data in data['cars']:
|
||||||
# car = invoice.get_itemtxs_data()[0].filter(
|
# car = invoice.get_itemtxs_data()[0].filter(
|
||||||
@ -1590,12 +1633,12 @@ def _post_sale_and_cogs(invoice, dealer):
|
|||||||
# ).first().item_model.car
|
# ).first().item_model.car
|
||||||
# qty = Decimal(car_data['quantity'])
|
# qty = Decimal(car_data['quantity'])
|
||||||
|
|
||||||
net_car_price = Decimal(data['discounted_price'])
|
net_car_price = Decimal(data["discounted_price"])
|
||||||
net_additionals_price = Decimal(data['additional_services']['total'])
|
net_additionals_price = Decimal(data["additional_services"]["total"])
|
||||||
vat_amount = Decimal(data['vat_amount'])
|
vat_amount = Decimal(data["vat_amount"])
|
||||||
grand_total = net_car_price + car.get_additional_services_amount_ + vat_amount
|
grand_total = net_car_price + car.get_additional_services_amount_ + vat_amount
|
||||||
cost_total = Decimal(car.cost_price)
|
cost_total = Decimal(car.cost_price)
|
||||||
discount_amount =Decimal(data['discount_amount'])
|
discount_amount = Decimal(data["discount_amount"])
|
||||||
|
|
||||||
# ------------------------------------------------------------------
|
# ------------------------------------------------------------------
|
||||||
# 2A. Journal: Cash / A-R / VAT / Sales
|
# 2A. Journal: Cash / A-R / VAT / Sales
|
||||||
@ -1606,15 +1649,15 @@ def _post_sale_and_cogs(invoice, dealer):
|
|||||||
description=f"Sale {car.vin}",
|
description=f"Sale {car.vin}",
|
||||||
origin=f"Invoice {invoice.invoice_number}",
|
origin=f"Invoice {invoice.invoice_number}",
|
||||||
locked=False,
|
locked=False,
|
||||||
posted=False
|
posted=False,
|
||||||
)
|
)
|
||||||
# Dr Cash (what the customer paid)
|
# Dr Cash (what the customer paid)
|
||||||
TransactionModel.objects.create(
|
TransactionModel.objects.create(
|
||||||
journal_entry=je_sale,
|
journal_entry=je_sale,
|
||||||
account=cash_acc,
|
account=cash_acc,
|
||||||
amount=grand_total,
|
amount=grand_total,
|
||||||
tx_type='debit',
|
tx_type="debit",
|
||||||
description='Debit to Cash on Hand'
|
description="Debit to Cash on Hand",
|
||||||
)
|
)
|
||||||
|
|
||||||
# # Cr A/R (clear the receivable)
|
# # Cr A/R (clear the receivable)
|
||||||
@ -1630,8 +1673,8 @@ def _post_sale_and_cogs(invoice, dealer):
|
|||||||
journal_entry=je_sale,
|
journal_entry=je_sale,
|
||||||
account=vat_acc,
|
account=vat_acc,
|
||||||
amount=vat_amount,
|
amount=vat_amount,
|
||||||
tx_type='credit',
|
tx_type="credit",
|
||||||
description="Credit to Tax Payable"
|
description="Credit to Tax Payable",
|
||||||
)
|
)
|
||||||
|
|
||||||
# Cr Sales – Car
|
# Cr Sales – Car
|
||||||
@ -1639,8 +1682,8 @@ def _post_sale_and_cogs(invoice, dealer):
|
|||||||
journal_entry=je_sale,
|
journal_entry=je_sale,
|
||||||
account=car_rev,
|
account=car_rev,
|
||||||
amount=net_car_price,
|
amount=net_car_price,
|
||||||
tx_type='credit',
|
tx_type="credit",
|
||||||
description=" Credit to Car Sales"
|
description=" Credit to Car Sales",
|
||||||
)
|
)
|
||||||
|
|
||||||
if car.get_additional_services_amount > 0:
|
if car.get_additional_services_amount > 0:
|
||||||
@ -1649,16 +1692,15 @@ def _post_sale_and_cogs(invoice, dealer):
|
|||||||
journal_entry=je_sale,
|
journal_entry=je_sale,
|
||||||
account=add_rev,
|
account=add_rev,
|
||||||
amount=car.get_additional_services_amount,
|
amount=car.get_additional_services_amount,
|
||||||
tx_type='credit',
|
tx_type="credit",
|
||||||
description="Credit to After-Sales Services"
|
description="Credit to After-Sales Services",
|
||||||
)
|
)
|
||||||
TransactionModel.objects.create(
|
TransactionModel.objects.create(
|
||||||
journal_entry=je_sale,
|
journal_entry=je_sale,
|
||||||
|
|
||||||
account=vat_acc,
|
account=vat_acc,
|
||||||
amount=car.get_additional_services_vat,
|
amount=car.get_additional_services_vat,
|
||||||
tx_type='credit',
|
tx_type="credit",
|
||||||
description="Credit to Tax Payable (Additional Services)"
|
description="Credit to Tax Payable (Additional Services)",
|
||||||
)
|
)
|
||||||
|
|
||||||
# ------------------------------------------------------------------
|
# ------------------------------------------------------------------
|
||||||
@ -1669,7 +1711,7 @@ def _post_sale_and_cogs(invoice, dealer):
|
|||||||
description=f"COGS {car.vin}",
|
description=f"COGS {car.vin}",
|
||||||
origin=f"Invoice {invoice.invoice_number}",
|
origin=f"Invoice {invoice.invoice_number}",
|
||||||
locked=False,
|
locked=False,
|
||||||
posted=False
|
posted=False,
|
||||||
)
|
)
|
||||||
|
|
||||||
# Dr COGS
|
# Dr COGS
|
||||||
@ -1677,15 +1719,12 @@ def _post_sale_and_cogs(invoice, dealer):
|
|||||||
journal_entry=je_cogs,
|
journal_entry=je_cogs,
|
||||||
account=cogs_acc,
|
account=cogs_acc,
|
||||||
amount=cost_total,
|
amount=cost_total,
|
||||||
tx_type='debit',
|
tx_type="debit",
|
||||||
)
|
)
|
||||||
|
|
||||||
# Cr Inventory
|
# Cr Inventory
|
||||||
TransactionModel.objects.create(
|
TransactionModel.objects.create(
|
||||||
journal_entry=je_cogs,
|
journal_entry=je_cogs, account=inv_acc, amount=cost_total, tx_type="credit"
|
||||||
account=inv_acc,
|
|
||||||
amount=cost_total,
|
|
||||||
tx_type='credit'
|
|
||||||
)
|
)
|
||||||
# ------------------------------------------------------------------
|
# ------------------------------------------------------------------
|
||||||
# 2C. Update car state flags inside the same transaction
|
# 2C. Update car state flags inside the same transaction
|
||||||
@ -1693,10 +1732,12 @@ def _post_sale_and_cogs(invoice, dealer):
|
|||||||
entity.get_items_inventory().filter(name=car.vin).update(for_inventory=False)
|
entity.get_items_inventory().filter(name=car.vin).update(for_inventory=False)
|
||||||
# car.item_model.for_inventory = False
|
# car.item_model.for_inventory = False
|
||||||
# car.item_model.save(update_fields=['for_inventory'])
|
# car.item_model.save(update_fields=['for_inventory'])
|
||||||
car.discount_amount=discount_amount
|
car.discount_amount = discount_amount
|
||||||
car.selling_price = grand_total
|
car.selling_price = grand_total
|
||||||
# car.is_sold = True
|
# car.is_sold = True
|
||||||
car.save()
|
car.save()
|
||||||
|
|
||||||
|
|
||||||
# def handle_account_process(invoice, amount, finance_data):
|
# def handle_account_process(invoice, amount, finance_data):
|
||||||
# """
|
# """
|
||||||
# Processes accounting transactions based on an invoice, financial data,
|
# Processes accounting transactions based on an invoice, financial data,
|
||||||
@ -1787,29 +1828,29 @@ def _post_sale_and_cogs(invoice, dealer):
|
|||||||
# car.finances.save()
|
# car.finances.save()
|
||||||
# car.item_model.save()
|
# car.item_model.save()
|
||||||
|
|
||||||
# TransactionModel.objects.create(
|
# TransactionModel.objects.create(
|
||||||
# journal_entry=journal,
|
# journal_entry=journal,
|
||||||
# account=additional_services_account, # Debit Additional Services
|
# account=additional_services_account, # Debit Additional Services
|
||||||
# amount=Decimal(car.finances.total_additionals),
|
# amount=Decimal(car.finances.total_additionals),
|
||||||
# tx_type="debit",
|
# tx_type="debit",
|
||||||
# description="Additional Services",
|
# description="Additional Services",
|
||||||
# )
|
# )
|
||||||
|
|
||||||
# TransactionModel.objects.create(
|
# TransactionModel.objects.create(
|
||||||
# journal_entry=journal,
|
# journal_entry=journal,
|
||||||
# account=inventory_account, # Credit Inventory account
|
# account=inventory_account, # Credit Inventory account
|
||||||
# amount=Decimal(finance_data.get("grand_total")),
|
# amount=Decimal(finance_data.get("grand_total")),
|
||||||
# tx_type="credit",
|
# tx_type="credit",
|
||||||
# description="Account Adjustment",
|
# description="Account Adjustment",
|
||||||
# )
|
# )
|
||||||
|
|
||||||
# TransactionModel.objects.create(
|
# TransactionModel.objects.create(
|
||||||
# journal_entry=journal,
|
# journal_entry=journal,
|
||||||
# account=vat_payable_account, # Credit VAT Payable
|
# account=vat_payable_account, # Credit VAT Payable
|
||||||
# amount=finance_data.get("total_vat_amount"),
|
# amount=finance_data.get("total_vat_amount"),
|
||||||
# tx_type="credit",
|
# tx_type="credit",
|
||||||
# description="VAT Payable on Invoice",
|
# description="VAT Payable on Invoice",
|
||||||
# )
|
# )
|
||||||
|
|
||||||
|
|
||||||
def create_make_accounts(dealer):
|
def create_make_accounts(dealer):
|
||||||
@ -1857,6 +1898,7 @@ def create_make_accounts(dealer):
|
|||||||
active=True,
|
active=True,
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
def handle_payment(request, order):
|
def handle_payment(request, order):
|
||||||
url = "https://api.moyasar.com/v1/payments"
|
url = "https://api.moyasar.com/v1/payments"
|
||||||
callback_url = request.build_absolute_uri(
|
callback_url = request.build_absolute_uri(
|
||||||
@ -1943,7 +1985,6 @@ def handle_payment(request, order):
|
|||||||
# return user.dealer.quota
|
# return user.dealer.quota
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
def get_accounts_data():
|
def get_accounts_data():
|
||||||
return [
|
return [
|
||||||
# Current Assets (must start with 1)
|
# Current Assets (must start with 1)
|
||||||
@ -2339,6 +2380,7 @@ def get_accounts_data():
|
|||||||
},
|
},
|
||||||
]
|
]
|
||||||
|
|
||||||
|
|
||||||
def create_account(entity, coa, account_data):
|
def create_account(entity, coa, account_data):
|
||||||
try:
|
try:
|
||||||
account = entity.create_account(
|
account = entity.create_account(
|
||||||
@ -2371,17 +2413,16 @@ def get_or_generate_car_image(car):
|
|||||||
return car_image.image.url
|
return car_image.image.url
|
||||||
|
|
||||||
# Check for existing image with same hash
|
# Check for existing image with same hash
|
||||||
existing = models.CarImage.objects.filter(
|
existing = (
|
||||||
image_hash=car_image.image_hash,
|
models.CarImage.objects.filter(
|
||||||
image__isnull=False
|
image_hash=car_image.image_hash, image__isnull=False
|
||||||
).exclude(car=car).first()
|
)
|
||||||
|
.exclude(car=car)
|
||||||
|
.first()
|
||||||
|
)
|
||||||
|
|
||||||
if existing:
|
if existing:
|
||||||
car_image.image.save(
|
car_image.image.save(existing.image.name, existing.image.file, save=True)
|
||||||
existing.image.name,
|
|
||||||
existing.image.file,
|
|
||||||
save=True
|
|
||||||
)
|
|
||||||
return car_image.image.url
|
return car_image.image.url
|
||||||
|
|
||||||
# If no image exists and not already generating, schedule generation
|
# If no image exists and not already generating, schedule generation
|
||||||
@ -2394,6 +2435,7 @@ def get_or_generate_car_image(car):
|
|||||||
logger.error(f"Error getting/generating car image: {e}")
|
logger.error(f"Error getting/generating car image: {e}")
|
||||||
return None
|
return None
|
||||||
|
|
||||||
|
|
||||||
def force_regenerate_car_image(car):
|
def force_regenerate_car_image(car):
|
||||||
"""
|
"""
|
||||||
Force regeneration of car image (useful for admin actions)
|
Force regeneration of car image (useful for admin actions)
|
||||||
@ -2414,6 +2456,7 @@ def force_regenerate_car_image(car):
|
|||||||
logger.error(f"Error forcing image regeneration: {e}")
|
logger.error(f"Error forcing image regeneration: {e}")
|
||||||
return False
|
return False
|
||||||
|
|
||||||
|
|
||||||
class CarImageAPIClient:
|
class CarImageAPIClient:
|
||||||
"""Simple client to handle authenticated requests to the car image API"""
|
"""Simple client to handle authenticated requests to the car image API"""
|
||||||
|
|
||||||
@ -2436,7 +2479,7 @@ class CarImageAPIClient:
|
|||||||
response.raise_for_status()
|
response.raise_for_status()
|
||||||
|
|
||||||
# Get CSRF token from cookies
|
# Get CSRF token from cookies
|
||||||
self.csrf_token = self.session.cookies.get('csrftoken')
|
self.csrf_token = self.session.cookies.get("csrftoken")
|
||||||
if not self.csrf_token:
|
if not self.csrf_token:
|
||||||
raise Exception("CSRF token not found in cookies")
|
raise Exception("CSRF token not found in cookies")
|
||||||
|
|
||||||
@ -2444,16 +2487,17 @@ class CarImageAPIClient:
|
|||||||
login_data = {
|
login_data = {
|
||||||
"username": self.USERNAME,
|
"username": self.USERNAME,
|
||||||
"password": self.PASSWORD,
|
"password": self.PASSWORD,
|
||||||
"csrfmiddlewaretoken": self.csrf_token
|
"csrfmiddlewaretoken": self.csrf_token,
|
||||||
}
|
}
|
||||||
|
|
||||||
login_response = self.session.post(
|
login_response = self.session.post(
|
||||||
f"{self.BASE_URL}/login",
|
f"{self.BASE_URL}/login", data=login_data
|
||||||
data=login_data
|
|
||||||
)
|
)
|
||||||
|
|
||||||
if login_response.status_code != 200:
|
if login_response.status_code != 200:
|
||||||
raise Exception(f"Login failed with status {login_response.status_code}")
|
raise Exception(
|
||||||
|
f"Login failed with status {login_response.status_code}"
|
||||||
|
)
|
||||||
|
|
||||||
logger.info("Successfully logged in to car image API")
|
logger.info("Successfully logged in to car image API")
|
||||||
return True
|
return True
|
||||||
@ -2472,39 +2516,38 @@ class CarImageAPIClient:
|
|||||||
|
|
||||||
try:
|
try:
|
||||||
headers = {
|
headers = {
|
||||||
'X-CSRFToken': self.csrf_token,
|
"X-CSRFToken": self.csrf_token,
|
||||||
'Referer': self.BASE_URL,
|
"Referer": self.BASE_URL,
|
||||||
}
|
}
|
||||||
print(payload)
|
print(payload)
|
||||||
generate_data = {
|
generate_data = {
|
||||||
"year": payload['year'],
|
"year": payload["year"],
|
||||||
"make": payload['make'],
|
"make": payload["make"],
|
||||||
"model": payload['model'],
|
"model": payload["model"],
|
||||||
"exterior_color": payload['color'],
|
"exterior_color": payload["color"],
|
||||||
"angle": "3/4 rear",
|
"angle": "3/4 rear",
|
||||||
"reference_image": ""
|
"reference_image": "",
|
||||||
}
|
}
|
||||||
|
|
||||||
response = self.session.post(
|
response = self.session.post(
|
||||||
f"{self.BASE_URL}/generate",
|
f"{self.BASE_URL}/generate",
|
||||||
json=generate_data,
|
json=generate_data,
|
||||||
headers=headers,
|
headers=headers,
|
||||||
timeout=160
|
timeout=160,
|
||||||
)
|
)
|
||||||
|
|
||||||
response.raise_for_status()
|
response.raise_for_status()
|
||||||
|
|
||||||
# Parse response
|
# Parse response
|
||||||
result = response.json()
|
result = response.json()
|
||||||
image_url = result.get('url')
|
image_url = result.get("url")
|
||||||
|
|
||||||
if not image_url:
|
if not image_url:
|
||||||
raise Exception("No image URL in response")
|
raise Exception("No image URL in response")
|
||||||
|
|
||||||
# Download the actual image
|
# Download the actual image
|
||||||
image_response = self.session.get(
|
image_response = self.session.get(
|
||||||
f"{self.BASE_URL}{image_url}",
|
f"{self.BASE_URL}{image_url}", timeout=160
|
||||||
timeout=160
|
|
||||||
)
|
)
|
||||||
|
|
||||||
image_response.raise_for_status()
|
image_response.raise_for_status()
|
||||||
@ -2520,9 +2563,11 @@ class CarImageAPIClient:
|
|||||||
logger.error(error_msg)
|
logger.error(error_msg)
|
||||||
return None, error_msg
|
return None, error_msg
|
||||||
|
|
||||||
|
|
||||||
# Global client instance
|
# Global client instance
|
||||||
api_client = CarImageAPIClient()
|
api_client = CarImageAPIClient()
|
||||||
|
|
||||||
|
|
||||||
def resize_image(image_data, max_size=(800, 600)):
|
def resize_image(image_data, max_size=(800, 600)):
|
||||||
"""
|
"""
|
||||||
Resize image to make it smaller while maintaining aspect ratio
|
Resize image to make it smaller while maintaining aspect ratio
|
||||||
@ -2539,29 +2584,31 @@ def resize_image(image_data, max_size=(800, 600)):
|
|||||||
# Save back to bytes in original format
|
# Save back to bytes in original format
|
||||||
output_buffer = BytesIO()
|
output_buffer = BytesIO()
|
||||||
|
|
||||||
if original_format and original_format.upper() in ['JPEG', 'JPG']:
|
if original_format and original_format.upper() in ["JPEG", "JPG"]:
|
||||||
img.save(output_buffer, format='JPEG', quality=95, optimize=True)
|
img.save(output_buffer, format="JPEG", quality=95, optimize=True)
|
||||||
elif original_format and original_format.upper() == 'PNG':
|
elif original_format and original_format.upper() == "PNG":
|
||||||
# Preserve transparency for PNG
|
# Preserve transparency for PNG
|
||||||
if original_mode == 'RGBA':
|
if original_mode == "RGBA":
|
||||||
img.save(output_buffer, format='PNG', optimize=True)
|
img.save(output_buffer, format="PNG", optimize=True)
|
||||||
else:
|
else:
|
||||||
img.save(output_buffer, format='PNG', optimize=True)
|
img.save(output_buffer, format="PNG", optimize=True)
|
||||||
else:
|
else:
|
||||||
# Default to JPEG for other formats
|
# Default to JPEG for other formats
|
||||||
if img.mode in ('RGBA', 'LA', 'P'):
|
if img.mode in ("RGBA", "LA", "P"):
|
||||||
# Convert to RGB if image has transparency
|
# Convert to RGB if image has transparency
|
||||||
background = Image.new('RGB', img.size, (255, 255, 255))
|
background = Image.new("RGB", img.size, (255, 255, 255))
|
||||||
if img.mode == 'RGBA':
|
if img.mode == "RGBA":
|
||||||
background.paste(img, mask=img.split()[3])
|
background.paste(img, mask=img.split()[3])
|
||||||
else:
|
else:
|
||||||
background.paste(img, (0, 0))
|
background.paste(img, (0, 0))
|
||||||
img = background
|
img = background
|
||||||
img.save(output_buffer, format='JPEG', quality=95, optimize=True)
|
img.save(output_buffer, format="JPEG", quality=95, optimize=True)
|
||||||
|
|
||||||
resized_data = output_buffer.getvalue()
|
resized_data = output_buffer.getvalue()
|
||||||
|
|
||||||
logger.info(f"Resized image from {len(image_data)} to {len(resized_data)} bytes")
|
logger.info(
|
||||||
|
f"Resized image from {len(image_data)} to {len(resized_data)} bytes"
|
||||||
|
)
|
||||||
return resized_data, None
|
return resized_data, None
|
||||||
|
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
@ -2569,6 +2616,7 @@ def resize_image(image_data, max_size=(800, 600)):
|
|||||||
logger.error(error_msg)
|
logger.error(error_msg)
|
||||||
return None, error_msg
|
return None, error_msg
|
||||||
|
|
||||||
|
|
||||||
def generate_car_image_simple(car_image):
|
def generate_car_image_simple(car_image):
|
||||||
"""
|
"""
|
||||||
Simple function to generate car image with authentication and resizing
|
Simple function to generate car image with authentication and resizing
|
||||||
@ -2577,10 +2625,10 @@ def generate_car_image_simple(car_image):
|
|||||||
|
|
||||||
# Prepare payload
|
# Prepare payload
|
||||||
payload = {
|
payload = {
|
||||||
'make': car.id_car_make.name if car.id_car_make else '',
|
"make": car.id_car_make.name if car.id_car_make else "",
|
||||||
'model': car.id_car_model.name if car.id_car_model else '',
|
"model": car.id_car_model.name if car.id_car_model else "",
|
||||||
'year': car.year,
|
"year": car.year,
|
||||||
'color': car.colors.exterior.name
|
"color": car.colors.exterior.name,
|
||||||
}
|
}
|
||||||
|
|
||||||
logger.info(f"Generating image for car {car.vin}")
|
logger.info(f"Generating image for car {car.vin}")
|
||||||
@ -2589,10 +2637,10 @@ def generate_car_image_simple(car_image):
|
|||||||
image_data, error = api_client.generate_image(payload)
|
image_data, error = api_client.generate_image(payload)
|
||||||
|
|
||||||
if error:
|
if error:
|
||||||
return {'success': False, 'error': error}
|
return {"success": False, "error": error}
|
||||||
|
|
||||||
if not image_data:
|
if not image_data:
|
||||||
return {'success': False, 'error': 'No image data received'}
|
return {"success": False, "error": "No image data received"}
|
||||||
|
|
||||||
try:
|
try:
|
||||||
# Resize the image to make it smaller
|
# Resize the image to make it smaller
|
||||||
@ -2606,21 +2654,21 @@ def generate_car_image_simple(car_image):
|
|||||||
# Determine file extension based on content
|
# Determine file extension based on content
|
||||||
try:
|
try:
|
||||||
img = Image.open(BytesIO(resized_data))
|
img = Image.open(BytesIO(resized_data))
|
||||||
file_extension = img.format.lower() if img.format else 'jpg'
|
file_extension = img.format.lower() if img.format else "jpg"
|
||||||
except:
|
except:
|
||||||
file_extension = 'jpg'
|
file_extension = "jpg"
|
||||||
|
|
||||||
# Save the resized image
|
# Save the resized image
|
||||||
car_image.image.save(
|
car_image.image.save(
|
||||||
f"{car_image.image_hash}.{file_extension}",
|
f"{car_image.image_hash}.{file_extension}",
|
||||||
ContentFile(resized_data),
|
ContentFile(resized_data),
|
||||||
save=False
|
save=False,
|
||||||
)
|
)
|
||||||
|
|
||||||
logger.info(f"Successfully generated and resized image for car {car.vin}")
|
logger.info(f"Successfully generated and resized image for car {car.vin}")
|
||||||
return {'success': True}
|
return {"success": True}
|
||||||
|
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
error_msg = f"Image processing failed: {e}"
|
error_msg = f"Image processing failed: {e}"
|
||||||
logger.error(error_msg)
|
logger.error(error_msg)
|
||||||
return {'success': False, 'error': error_msg}
|
return {"success": False, "error": error_msg}
|
||||||
|
|||||||
@ -2,13 +2,15 @@ from django.core.validators import RegexValidator
|
|||||||
from django.utils.translation import gettext_lazy as _
|
from django.utils.translation import gettext_lazy as _
|
||||||
import re
|
import re
|
||||||
|
|
||||||
|
|
||||||
class SaudiPhoneNumberValidator(RegexValidator):
|
class SaudiPhoneNumberValidator(RegexValidator):
|
||||||
def __init__(self, *args, **kwargs):
|
def __init__(self, *args, **kwargs):
|
||||||
super().__init__(
|
super().__init__(
|
||||||
regex=r"^(\+9665|05)[0-9]{8}$",
|
regex=r"^(\+9665|05)[0-9]{8}$",
|
||||||
message=_("Enter a valid Saudi phone number (05XXXXXXXX or +9665XXXXXXXX)"),
|
message=_("Enter a valid Saudi phone number (05XXXXXXXX or +9665XXXXXXXX)"),
|
||||||
)
|
)
|
||||||
|
|
||||||
def __call__(self, value):
|
def __call__(self, value):
|
||||||
# Remove any whitespace, dashes, or other separators
|
# Remove any whitespace, dashes, or other separators
|
||||||
cleaned_value = re.sub(r'[\s\-\(\)\.]', '', str(value))
|
cleaned_value = re.sub(r"[\s\-\(\)\.]", "", str(value))
|
||||||
super().__call__(cleaned_value)
|
super().__call__(cleaned_value)
|
||||||
1536
inventory/views.py
1536
inventory/views.py
File diff suppressed because it is too large
Load Diff
@ -46,7 +46,7 @@ def run():
|
|||||||
# arabic_name=item.get("arabic_name", ""),
|
# arabic_name=item.get("arabic_name", ""),
|
||||||
# logo=item.get("Logo", ""),
|
# logo=item.get("Logo", ""),
|
||||||
# is_sa_import=item.get("is_sa_import", False),
|
# is_sa_import=item.get("is_sa_import", False),
|
||||||
slug=unique_slug
|
slug=unique_slug,
|
||||||
)
|
)
|
||||||
|
|
||||||
# Step 2: Insert CarModel
|
# Step 2: Insert CarModel
|
||||||
@ -60,7 +60,7 @@ def run():
|
|||||||
id_car_make_id=item["id_car_make"],
|
id_car_make_id=item["id_car_make"],
|
||||||
name=item["name"],
|
name=item["name"],
|
||||||
# arabic_name=item.get("arabic_name", ""),
|
# arabic_name=item.get("arabic_name", ""),
|
||||||
slug=unique_slug
|
slug=unique_slug,
|
||||||
)
|
)
|
||||||
|
|
||||||
# Step 3: Insert CarSerie
|
# Step 3: Insert CarSerie
|
||||||
@ -77,7 +77,7 @@ def run():
|
|||||||
year_begin=item.get("year_begin"),
|
year_begin=item.get("year_begin"),
|
||||||
year_end=item.get("year_end"),
|
year_end=item.get("year_end"),
|
||||||
generation_name=item.get("generation_name", ""),
|
generation_name=item.get("generation_name", ""),
|
||||||
slug=unique_slug
|
slug=unique_slug,
|
||||||
)
|
)
|
||||||
|
|
||||||
# Step 4: Insert CarTrim
|
# Step 4: Insert CarTrim
|
||||||
@ -98,9 +98,10 @@ def run():
|
|||||||
|
|
||||||
# Step 5: Insert CarEquipment
|
# Step 5: Insert CarEquipment
|
||||||
|
|
||||||
|
|
||||||
for item in tqdm(data["car_equipment"], desc="Inserting CarEquipment"):
|
for item in tqdm(data["car_equipment"], desc="Inserting CarEquipment"):
|
||||||
if not CarEquipment.objects.filter(id_car_equipment=item["id_car_equipment"]).exists():
|
if not CarEquipment.objects.filter(
|
||||||
|
id_car_equipment=item["id_car_equipment"]
|
||||||
|
).exists():
|
||||||
if CarTrim.objects.filter(id_car_trim=item["id_car_trim"]).exists():
|
if CarTrim.objects.filter(id_car_trim=item["id_car_trim"]).exists():
|
||||||
unique_slug = generate_unique_slug(CarEquipment, item["name"])
|
unique_slug = generate_unique_slug(CarEquipment, item["name"])
|
||||||
CarEquipment.objects.create(
|
CarEquipment.objects.create(
|
||||||
@ -108,7 +109,7 @@ def run():
|
|||||||
id_car_trim_id=item["id_car_trim"],
|
id_car_trim_id=item["id_car_trim"],
|
||||||
name=item["name"],
|
name=item["name"],
|
||||||
year_begin=item.get("year"),
|
year_begin=item.get("year"),
|
||||||
slug=unique_slug
|
slug=unique_slug,
|
||||||
)
|
)
|
||||||
|
|
||||||
# Step 6: Insert CarSpecification (Parent specifications first)
|
# Step 6: Insert CarSpecification (Parent specifications first)
|
||||||
|
|||||||
@ -58,7 +58,7 @@ def run():
|
|||||||
visible=True,
|
visible=True,
|
||||||
order=1,
|
order=1,
|
||||||
)
|
)
|
||||||
basic_plan.quotas.add(basic_quota,free_quota)
|
basic_plan.quotas.add(basic_quota, free_quota)
|
||||||
|
|
||||||
pro_plan = Plan.objects.create(
|
pro_plan = Plan.objects.create(
|
||||||
name="Professional",
|
name="Professional",
|
||||||
@ -69,7 +69,7 @@ def run():
|
|||||||
visible=True,
|
visible=True,
|
||||||
# order=2
|
# order=2
|
||||||
)
|
)
|
||||||
pro_plan.quotas.add(free_quota,basic_quota, pro_quota)
|
pro_plan.quotas.add(free_quota, basic_quota, pro_quota)
|
||||||
|
|
||||||
premium_plan = Plan.objects.create(
|
premium_plan = Plan.objects.create(
|
||||||
name="Premium",
|
name="Premium",
|
||||||
@ -80,4 +80,4 @@ def run():
|
|||||||
visible=True,
|
visible=True,
|
||||||
order=3,
|
order=3,
|
||||||
)
|
)
|
||||||
premium_plan.quotas.add(free_quota,basic_quota, pro_quota, premium_quota)
|
premium_plan.quotas.add(free_quota, basic_quota, pro_quota, premium_quota)
|
||||||
|
|||||||
@ -1,11 +1,12 @@
|
|||||||
{% load i18n %}
|
{% load i18n %}
|
||||||
<!DOCTYPE html>
|
<!DOCTYPE html>
|
||||||
<html lang="en">
|
<html lang="en">
|
||||||
<head>
|
<head>
|
||||||
<meta charset="UTF-8">
|
<meta charset="UTF-8">
|
||||||
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||||
<title>403 - Access Forbidden</title>
|
<title>403 - Access Forbidden</title>
|
||||||
<link href="https://cdn.jsdelivr.net/npm/bootstrap@5.3.3/dist/css/bootstrap.min.css" rel="stylesheet">
|
<link href="https://cdn.jsdelivr.net/npm/bootstrap@5.3.3/dist/css/bootstrap.min.css"
|
||||||
|
rel="stylesheet">
|
||||||
<style>
|
<style>
|
||||||
:root {
|
:root {
|
||||||
--dark-bg: #121212;
|
--dark-bg: #121212;
|
||||||
@ -82,19 +83,16 @@
|
|||||||
z-index: 1;
|
z-index: 1;
|
||||||
}
|
}
|
||||||
</style>
|
</style>
|
||||||
</head>
|
</head>
|
||||||
<body>
|
<body>
|
||||||
|
|
||||||
<div id="particles-js"></div>
|
<div id="particles-js"></div>
|
||||||
|
|
||||||
<div class="center-content container-fluid">
|
<div class="center-content container-fluid">
|
||||||
<h1 class="glitch">403</h1>
|
<h1 class="glitch">403</h1>
|
||||||
<h2 class="main-message">{% trans "Access Forbidden" %}</h2>
|
<h2 class="main-message">{% trans "Access Forbidden" %}</h2>
|
||||||
<p class="sub-message">{% trans "You do not have permission to view this page."%}</p>
|
<p class="sub-message">{% trans "You do not have permission to view this page." %}</p>
|
||||||
<p class="sub-message fs-2">{% trans "Powered By Tenhal, Riyadh Saudi Arabia"%}</p>
|
<p class="sub-message fs-2">{% trans "Powered By Tenhal, Riyadh Saudi Arabia" %}</p>
|
||||||
<a href="{% url 'home' %}" class="home-button">{% trans "Go Home" %}</a>
|
<a href="{% url 'home' %}" class="home-button">{% trans "Go Home" %}</a>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<script src="https://cdn.jsdelivr.net/npm/bootstrap@5.3.3/dist/js/bootstrap.bundle.min.js"></script>
|
<script src="https://cdn.jsdelivr.net/npm/bootstrap@5.3.3/dist/js/bootstrap.bundle.min.js"></script>
|
||||||
<script src="https://cdn.jsdelivr.net/npm/particles.js@2.0.0/particles.min.js"></script>
|
<script src="https://cdn.jsdelivr.net/npm/particles.js@2.0.0/particles.min.js"></script>
|
||||||
<script>
|
<script>
|
||||||
@ -173,5 +171,5 @@
|
|||||||
"retina_detect": true
|
"retina_detect": true
|
||||||
});
|
});
|
||||||
</script>
|
</script>
|
||||||
</body>
|
</body>
|
||||||
</html>
|
</html>
|
||||||
@ -6,7 +6,6 @@
|
|||||||
{% trans "Sign In" %}
|
{% trans "Sign In" %}
|
||||||
{% endblock head_title %}
|
{% endblock head_title %}
|
||||||
{% block content %}
|
{% block content %}
|
||||||
|
|
||||||
<section class="main my-2">
|
<section class="main my-2">
|
||||||
<div class="container" style="max-width:40rem;">
|
<div class="container" style="max-width:40rem;">
|
||||||
<div class="class="row form-container" id="form-container"">
|
<div class="class="row form-container" id="form-container"">
|
||||||
@ -78,9 +77,7 @@
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</section>
|
</section>
|
||||||
|
|
||||||
{% include 'footer.html' %}
|
{% include 'footer.html' %}
|
||||||
|
|
||||||
{% 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 %}
|
||||||
|
|||||||
@ -282,9 +282,7 @@
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</section>
|
</section>
|
||||||
|
|
||||||
{% include 'footer.html' %}
|
{% include 'footer.html' %}
|
||||||
|
|
||||||
<script src="{% static 'js/phoenix.js' %}"></script>
|
<script src="{% static 'js/phoenix.js' %}"></script>
|
||||||
{% endblock content %}
|
{% endblock content %}
|
||||||
{% block customJS %}
|
{% block customJS %}
|
||||||
|
|||||||
@ -6,7 +6,7 @@
|
|||||||
{% trans 'Dealer Settings' %}
|
{% trans 'Dealer Settings' %}
|
||||||
{% endblock %}
|
{% endblock %}
|
||||||
{% block content %}
|
{% block content %}
|
||||||
<main class="d-flex align-items-center justify-content-center min-vh-80 py-5">
|
<main class="d-flex align-items-center justify-content-center min-vh-80 py-5">
|
||||||
<div class="col-md-6">
|
<div class="col-md-6">
|
||||||
<div class="card shadow-lg border-0 rounded-4 overflow-hidden animate__animated animate__fadeInUp">
|
<div class="card shadow-lg border-0 rounded-4 overflow-hidden animate__animated animate__fadeInUp">
|
||||||
<div class="card-header bg-gradient py-4 border-0 rounded-top-4">
|
<div class="card-header bg-gradient py-4 border-0 rounded-top-4">
|
||||||
@ -43,5 +43,5 @@
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</main>
|
</main>
|
||||||
{% endblock %}
|
{% endblock %}
|
||||||
@ -23,7 +23,7 @@
|
|||||||
<h3 class="mb-0 fs-4 text-center text-white">{% trans 'Activate Account' %}</h3>
|
<h3 class="mb-0 fs-4 text-center text-white">{% trans 'Activate Account' %}</h3>
|
||||||
</div>
|
</div>
|
||||||
<div class="card-body bg-light-subtle">
|
<div class="card-body bg-light-subtle">
|
||||||
<p class="text-center">{{ _("Are you sure you want to activate this account")}} "{{ obj.email }}"?</p>
|
<p class="text-center">{{ _("Are you sure you want to activate this account") }} "{{ obj.email }}"?</p>
|
||||||
<form method="post">
|
<form method="post">
|
||||||
{% csrf_token %}
|
{% csrf_token %}
|
||||||
<hr class="my-2">
|
<hr class="my-2">
|
||||||
|
|||||||
@ -1,9 +1,13 @@
|
|||||||
{% extends "base.html" %}
|
{% extends "base.html" %}
|
||||||
{% load i18n %}
|
{% load i18n %}
|
||||||
{% block title %}
|
{% block title %}
|
||||||
{% trans 'Admin Management' %} {% endblock %}
|
{% trans 'Admin Management' %}
|
||||||
{% block content %}
|
{% endblock %}
|
||||||
<h3 class="my-4">{% trans "Admin Management" %}<li class="fa fa-user-cog ms-2 text-primary"></li></h3>
|
{% block content %}
|
||||||
|
<h3 class="my-4">
|
||||||
|
{% trans "Admin Management" %}
|
||||||
|
<li class="fa fa-user-cog ms-2 text-primary"></li>
|
||||||
|
</h3>
|
||||||
<div class="row row-cols-1 row-cols-sm-2 row-cols-md-4 g-4 mt-10">
|
<div class="row row-cols-1 row-cols-sm-2 row-cols-md-4 g-4 mt-10">
|
||||||
<div class="col">
|
<div class="col">
|
||||||
<a href="{% url 'user_management' request.dealer.slug %}">
|
<a href="{% url 'user_management' request.dealer.slug %}">
|
||||||
@ -26,4 +30,4 @@
|
|||||||
</a>
|
</a>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
{% endblock content %}
|
{% endblock content %}
|
||||||
|
|||||||
@ -32,15 +32,7 @@
|
|||||||
<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
|
<div class="alert alert-dismissible {% if message.tags %} alert- {% if message.level == DEFAULT_MESSAGE_LEVELS.ERROR %} danger {% else %} {{ message.tags }} {% endif %} {% endif %}"
|
||||||
{% 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 %}
|
||||||
|
|||||||
@ -78,15 +78,7 @@
|
|||||||
<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
|
<div class="alert alert-dismissible {% if message.tags %} alert- {% if message.level == DEFAULT_MESSAGE_LEVELS.ERROR %} danger {% else %} {{ message.tags }} {% endif %} {% endif %}"
|
||||||
{% 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 %}
|
||||||
|
|||||||
@ -65,15 +65,7 @@
|
|||||||
<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
|
<div class="alert alert-dismissible {% if message.tags %} alert- {% if message.level == DEFAULT_MESSAGE_LEVELS.ERROR %} danger {% else %} {{ message.tags }} {% endif %} {% endif %}"
|
||||||
{% 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 %}
|
||||||
|
|||||||
@ -25,15 +25,7 @@
|
|||||||
<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
|
<div class="alert alert-dismissible {% if message.tags %} alert- {% if message.level == DEFAULT_MESSAGE_LEVELS.ERROR %} danger {% else %} {{ message.tags }} {% endif %} {% endif %}"
|
||||||
{% 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 %}
|
||||||
|
|||||||
@ -11,22 +11,9 @@
|
|||||||
method="post"
|
method="post"
|
||||||
action=""
|
action=""
|
||||||
id="workingHoursForm"
|
id="workingHoursForm"
|
||||||
data-action="{% if working_hours_instance %}
|
data-action="{% if working_hours_instance %} update {% else %} create {% endif %}"
|
||||||
update
|
data-working-hours-id=" {% if working_hours_instance %} {{ working_hours_instance.id }} {% else %} 0 {% endif %}"
|
||||||
{% else %}
|
data-staff-user-id="{% if staff_user_id %} {{ staff_user_id }} {% else %} 0 {% endif %}">
|
||||||
create
|
|
||||||
{% endif %}"
|
|
||||||
data-working-hours-id="
|
|
||||||
{% if working_hours_instance %}
|
|
||||||
{{ 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">
|
||||||
@ -94,15 +81,7 @@
|
|||||||
<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
|
<div class="alert alert-dismissible {% if message.tags %} alert- {% if message.level == DEFAULT_MESSAGE_LEVELS.ERROR %} danger {% else %} {{ message.tags }} {% endif %} {% endif %}"
|
||||||
{% 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 %}
|
||||||
|
|||||||
@ -23,15 +23,7 @@
|
|||||||
<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
|
<div class="alert alert-dismissible {% if message.tags %} alert- {% if message.level == DEFAULT_MESSAGE_LEVELS.ERROR %} danger {% else %} {{ message.tags }} {% endif %} {% endif %}"
|
||||||
{% 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 %}
|
||||||
|
|||||||
@ -68,7 +68,9 @@
|
|||||||
{% endif %}
|
{% endif %}
|
||||||
{% for sf in all_staff_members %}
|
{% for sf in all_staff_members %}
|
||||||
<option value="{{ sf.id }}"
|
<option value="{{ sf.id }}"
|
||||||
{% if staff_member and staff_member.id == sf.id %}selected{% endif %}>{{ sf.get_staff_member_name }}</option>
|
{% if staff_member and staff_member.id == sf.id %}selected{% endif %}>
|
||||||
|
{{ sf.get_staff_member_name }}
|
||||||
|
</option>
|
||||||
{% endfor %}
|
{% endfor %}
|
||||||
</select>
|
</select>
|
||||||
</div>
|
</div>
|
||||||
@ -88,15 +90,7 @@
|
|||||||
</div>
|
</div>
|
||||||
{% if messages %}
|
{% if messages %}
|
||||||
{% for message in messages %}
|
{% for message in messages %}
|
||||||
<div class="alert alert-dismissible
|
<div class="alert alert-dismissible {% if message.tags %} alert- {% if message.level == DEFAULT_MESSAGE_LEVELS.ERROR %} danger {% else %} {{ message.tags }} {% endif %} {% endif %}"
|
||||||
{% 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 %}
|
||||||
|
|||||||
@ -24,15 +24,7 @@
|
|||||||
</ul>
|
</ul>
|
||||||
{% if messages %}
|
{% if messages %}
|
||||||
{% for message in messages %}
|
{% for message in messages %}
|
||||||
<div class="alert alert-dismissible
|
<div class="alert alert-dismissible {% if message.tags %} alert- {% if message.level == DEFAULT_MESSAGE_LEVELS.ERROR %} danger {% else %} {{ message.tags }} {% endif %} {% endif %}"
|
||||||
{% 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 %}
|
||||||
|
|||||||
@ -29,8 +29,7 @@
|
|||||||
</form>
|
</form>
|
||||||
{% if messages %}
|
{% if messages %}
|
||||||
{% for message in messages %}
|
{% for message in messages %}
|
||||||
<div class="vcode-alert vcode-alert-
|
<div class="vcode-alert vcode-alert- {% if message.tags %}{{ message.tags }}{% endif %}">{{ message }}</div>
|
||||||
{% if message.tags %}{{ message.tags }}{% endif %}">{{ message }}</div>
|
|
||||||
{% endfor %}
|
{% endfor %}
|
||||||
{% endif %}
|
{% endif %}
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@ -69,15 +69,7 @@
|
|||||||
</div>
|
</div>
|
||||||
{% if messages %}
|
{% if messages %}
|
||||||
{% for message in messages %}
|
{% for message in messages %}
|
||||||
<div class="alert alert-dismissible
|
<div class="alert alert-dismissible {% if message.tags %} alert- {% if message.level == DEFAULT_MESSAGE_LEVELS.ERROR %} danger {% else %} {{ message.tags }} {% endif %} {% endif %}"
|
||||||
{% 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 %}
|
||||||
|
|||||||
@ -40,15 +40,7 @@
|
|||||||
<p class="message">{{ page_message }}</p>
|
<p class="message">{{ page_message }}</p>
|
||||||
{% if messages %}
|
{% if messages %}
|
||||||
{% for message in messages %}
|
{% for message in messages %}
|
||||||
<div class="alert alert-dismissible
|
<div class="alert alert-dismissible {% if message.tags %} alert- {% if message.level == DEFAULT_MESSAGE_LEVELS.ERROR %} danger {% else %} {{ message.tags }} {% endif %} {% endif %}"
|
||||||
{% 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 %}
|
||||||
|
|||||||
@ -3,11 +3,7 @@
|
|||||||
<!DOCTYPE html>
|
<!DOCTYPE html>
|
||||||
{% get_current_language as LANGUAGE_CODE %}
|
{% get_current_language as LANGUAGE_CODE %}
|
||||||
<html lang="{{ LANGUAGE_CODE }}"
|
<html lang="{{ LANGUAGE_CODE }}"
|
||||||
dir="{% if LANGUAGE_CODE == 'ar' %}
|
dir="{% if LANGUAGE_CODE == 'ar' %} rtl {% else %} ltr {% endif %}"
|
||||||
rtl
|
|
||||||
{% else %}
|
|
||||||
ltr
|
|
||||||
{% endif %}"
|
|
||||||
data-bs-theme=""
|
data-bs-theme=""
|
||||||
data-navigation-type="default"
|
data-navigation-type="default"
|
||||||
data-navbar-horizontal-shape="default">
|
data-navbar-horizontal-shape="default">
|
||||||
|
|||||||
@ -2,11 +2,7 @@
|
|||||||
<!DOCTYPE html>
|
<!DOCTYPE html>
|
||||||
{% get_current_language as LANGUAGE_CODE %}
|
{% get_current_language as LANGUAGE_CODE %}
|
||||||
<html lang="{{ LANGUAGE_CODE }}"
|
<html lang="{{ LANGUAGE_CODE }}"
|
||||||
{% if LANGUAGE_CODE == 'ar' %}
|
{% if LANGUAGE_CODE == 'ar' %} dir="rtl" {% else %} dir="ltr" {% endif %}
|
||||||
dir="rtl"
|
|
||||||
{% else %}
|
|
||||||
dir="ltr"
|
|
||||||
{% endif %}
|
|
||||||
data-bs-theme=""
|
data-bs-theme=""
|
||||||
data-navigation-type="default"
|
data-navigation-type="default"
|
||||||
data-navbar-horizontal-shape="default">
|
data-navbar-horizontal-shape="default">
|
||||||
@ -54,8 +50,14 @@
|
|||||||
<link href="{% static 'css/custom.css' %}" rel="stylesheet">
|
<link href="{% static 'css/custom.css' %}" rel="stylesheet">
|
||||||
{% comment %} <link rel="stylesheet" href="https://unicons.iconscout.com/release/v4.0.8/css/line.css"> {% endcomment %}
|
{% comment %} <link rel="stylesheet" href="https://unicons.iconscout.com/release/v4.0.8/css/line.css"> {% endcomment %}
|
||||||
{% if LANGUAGE_CODE == 'ar' %}
|
{% if LANGUAGE_CODE == 'ar' %}
|
||||||
<link href="{% static 'css/theme-rtl.min.css' %}" type="text/css" rel="stylesheet" id="style-rtl">
|
<link href="{% static 'css/theme-rtl.min.css' %}"
|
||||||
<link href="{% static 'css/user-rtl.min.css' %}" type="text/css" rel="stylesheet" id="user-style-rtl">
|
type="text/css"
|
||||||
|
rel="stylesheet"
|
||||||
|
id="style-rtl">
|
||||||
|
<link href="{% static 'css/user-rtl.min.css' %}"
|
||||||
|
type="text/css"
|
||||||
|
rel="stylesheet"
|
||||||
|
id="user-style-rtl">
|
||||||
{% else %}
|
{% else %}
|
||||||
<link href="{% static 'css/theme.min.css' %}"
|
<link href="{% static 'css/theme.min.css' %}"
|
||||||
type="text/css"
|
type="text/css"
|
||||||
@ -66,11 +68,8 @@
|
|||||||
rel="stylesheet"
|
rel="stylesheet"
|
||||||
id="user-style-default">
|
id="user-style-default">
|
||||||
{% endif %}
|
{% endif %}
|
||||||
|
|
||||||
|
|
||||||
<script src="{% static 'js/main.js' %}"></script>
|
<script src="{% static 'js/main.js' %}"></script>
|
||||||
<script src="{% static 'js/jquery.min.js' %}"></script>
|
<script src="{% static 'js/jquery.min.js' %}"></script>
|
||||||
|
|
||||||
{% comment %} <script src="{% static 'js/echarts.js' %}"></script> {% endcomment %}
|
{% comment %} <script src="{% static 'js/echarts.js' %}"></script> {% endcomment %}
|
||||||
{% comment %} {% block customCSS %}{% endblock %} {% endcomment %}
|
{% comment %} {% block customCSS %}{% endblock %} {% endcomment %}
|
||||||
</head>
|
</head>
|
||||||
@ -87,17 +86,23 @@
|
|||||||
<div id="spinner" class="htmx-indicator spinner-bg">
|
<div id="spinner" class="htmx-indicator spinner-bg">
|
||||||
<img src="{% static 'spinner.svg' %}" width="100" height="100" alt="">
|
<img src="{% static 'spinner.svg' %}" width="100" height="100" alt="">
|
||||||
</div>
|
</div>
|
||||||
<div id="main_content" class="fade-me-in" hx-boost="false" hx-target="#main_content" hx-select="#main_content" hx-swap="outerHTML transition:true" hx-select-oob="#toast-container" hx-history-elt>
|
<div id="main_content"
|
||||||
|
class="fade-me-in"
|
||||||
|
hx-boost="false"
|
||||||
|
hx-target="#main_content"
|
||||||
|
hx-select="#main_content"
|
||||||
|
hx-swap="outerHTML transition:true"
|
||||||
|
hx-select-oob="#toast-container"
|
||||||
|
hx-history-elt>
|
||||||
{% block customCSS %}{% endblock %}
|
{% block customCSS %}{% endblock %}
|
||||||
{% block content %}{% endblock content %}
|
{% block content %}
|
||||||
|
{% endblock content %}
|
||||||
{% block customJS %}{% endblock %}
|
{% block customJS %}{% endblock %}
|
||||||
|
|
||||||
{% comment %} <script src="{% static 'vendors/feather-icons/feather.min.js' %}"></script>
|
{% comment %} <script src="{% static 'vendors/feather-icons/feather.min.js' %}"></script>
|
||||||
<script src="{% static 'vendors/fontawesome/all.min.js' %}"></script>
|
<script src="{% static 'vendors/fontawesome/all.min.js' %}"></script>
|
||||||
<script src="{% static 'vendors/popper/popper.min.js' %}"></script>
|
<script src="{% static 'vendors/popper/popper.min.js' %}"></script>
|
||||||
<script src="{% static 'vendors/bootstrap/bootstrap.min.js' %}"></script> {% endcomment %}
|
<script src="{% static 'vendors/bootstrap/bootstrap.min.js' %}"></script> {% endcomment %}
|
||||||
<script src="{% static 'js/phoenix.js' %}"></script>
|
<script src="{% static 'js/phoenix.js' %}"></script>
|
||||||
|
|
||||||
</div>
|
</div>
|
||||||
{% block body %}
|
{% block body %}
|
||||||
{% endblock body %}
|
{% endblock body %}
|
||||||
@ -173,7 +178,7 @@
|
|||||||
titleText: msg
|
titleText: msg
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
document.addEventListener('htmx:afterRequest', function(evt) {
|
document.addEventListener('htmx:afterRequest', function(evt) {
|
||||||
if(evt.detail.xhr.status == 403){
|
if(evt.detail.xhr.status == 403){
|
||||||
/* Notify the user of a 404 Not Found response */
|
/* Notify the user of a 404 Not Found response */
|
||||||
notify("error", "You do not have permission to view this page");
|
notify("error", "You do not have permission to view this page");
|
||||||
@ -187,8 +192,8 @@ document.addEventListener('htmx:afterRequest', function(evt) {
|
|||||||
/* Notify of an unexpected error, & print error to console */
|
/* Notify of an unexpected error, & print error to console */
|
||||||
notify("error", `Unexpected Error ,${evt.detail.xhr.statusText}`);
|
notify("error", `Unexpected Error ,${evt.detail.xhr.statusText}`);
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
document.body.addEventListener('htmx:beforeSwap', function(evt) {
|
document.body.addEventListener('htmx:beforeSwap', function(evt) {
|
||||||
if (evt.detail.target.id === 'main_content') {
|
if (evt.detail.target.id === 'main_content') {
|
||||||
var backdrops = document.querySelectorAll('.modal-backdrop');
|
var backdrops = document.querySelectorAll('.modal-backdrop');
|
||||||
backdrops.forEach(function(backdrop) {
|
backdrops.forEach(function(backdrop) {
|
||||||
|
|||||||
@ -6,9 +6,8 @@
|
|||||||
{% block title %}
|
{% block title %}
|
||||||
{{ _("Create Bill") |capfirst }}
|
{{ _("Create Bill") |capfirst }}
|
||||||
{% endblock title %}
|
{% endblock title %}
|
||||||
|
|
||||||
{% block content %}
|
{% block content %}
|
||||||
<main class="d-flex align-items-center justify-content-center min-vh-80 py-5 ">
|
<main class="d-flex align-items-center justify-content-center min-vh-80 py-5 ">
|
||||||
<div class="col-md-6">
|
<div class="col-md-6">
|
||||||
<div class="card shadow-lg border-0 rounded-4 overflow-hidden animate__animated animate__fadeInUp">
|
<div class="card shadow-lg border-0 rounded-4 overflow-hidden animate__animated animate__fadeInUp">
|
||||||
<div class="card-header bg-gradient py-4 border-0 rounded-top-4">
|
<div class="card-header bg-gradient py-4 border-0 rounded-top-4">
|
||||||
@ -18,29 +17,30 @@
|
|||||||
</h3>
|
</h3>
|
||||||
</div>
|
</div>
|
||||||
<div class="card-body p-4 p-md-5">
|
<div class="card-body p-4 p-md-5">
|
||||||
<form action="{{ form_action_url }}" method="post" id="djl-bill-model-create-form-id" class="needs-validation" novalidate>
|
<form action="{{ form_action_url }}"
|
||||||
|
method="post"
|
||||||
|
id="djl-bill-model-create-form-id"
|
||||||
|
class="needs-validation"
|
||||||
|
novalidate>
|
||||||
{% csrf_token %}
|
{% csrf_token %}
|
||||||
{% if po_model %}
|
{% if po_model %}
|
||||||
<div class="text-center mb-4">
|
<div class="text-center mb-4">
|
||||||
<h3 class="h5">{% trans 'Bill for' %} {{ po_model.po_number }}</h3>
|
<h3 class="h5">{% trans 'Bill for' %} {{ po_model.po_number }}</h3>
|
||||||
<p class="text-muted mb-3">{% trans 'Bill for' %} {{ po_model.po_title }}</p>
|
<p class="text-muted mb-3">{% trans 'Bill for' %} {{ po_model.po_title }}</p>
|
||||||
<div class="d-flex flex-column gap-2">
|
<div class="d-flex flex-column gap-2">
|
||||||
{% for itemtxs in po_itemtxs_qs %}
|
{% for itemtxs in po_itemtxs_qs %}<span class="badge bg-secondary">{{ itemtxs }}</span>{% endfor %}
|
||||||
<span class="badge bg-secondary">{{ itemtxs }}</span>
|
|
||||||
{% endfor %}
|
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
{% endif %}
|
{% endif %}
|
||||||
<div class="mb-4">
|
<div class="mb-4">{{ form|crispy }}</div>
|
||||||
{{ form|crispy }}
|
|
||||||
</div>
|
|
||||||
<hr class="my-4">
|
<hr class="my-4">
|
||||||
<div class="d-grid gap-2 d-md-flex justify-content-md-center mt-3">
|
<div class="d-grid gap-2 d-md-flex justify-content-md-center mt-3">
|
||||||
<button class="btn btn-phoenix-primary btn-lg me-md-2" type="submit">
|
<button class="btn btn-phoenix-primary btn-lg me-md-2" type="submit">
|
||||||
<i class="fa-solid fa-floppy-disk me-1"></i>
|
<i class="fa-solid fa-floppy-disk me-1"></i>
|
||||||
{{ _("Save") }}
|
{{ _("Save") }}
|
||||||
</button>
|
</button>
|
||||||
<a href="{% url 'bill_list' request.dealer.slug%}" class="btn btn-phoenix-secondary btn-lg">
|
<a href="{% url 'bill_list' request.dealer.slug %}"
|
||||||
|
class="btn btn-phoenix-secondary btn-lg">
|
||||||
<i class="fa-solid fa-ban me-1"></i>
|
<i class="fa-solid fa-ban me-1"></i>
|
||||||
{% trans "Cancel" %}
|
{% trans "Cancel" %}
|
||||||
</a>
|
</a>
|
||||||
@ -49,5 +49,5 @@
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</main>
|
</main>
|
||||||
{% endblock %}
|
{% endblock %}
|
||||||
@ -4,9 +4,7 @@
|
|||||||
{% load django_ledger %}
|
{% load django_ledger %}
|
||||||
{% load custom_filters %}
|
{% load custom_filters %}
|
||||||
{% block title %}Bill Details{% endblock %}
|
{% block title %}Bill Details{% endblock %}
|
||||||
|
{% block content %}
|
||||||
{% block content%}
|
|
||||||
|
|
||||||
<div class="row mt-4">
|
<div class="row mt-4">
|
||||||
<div class="col-12 mb-3">
|
<div class="col-12 mb-3">
|
||||||
<div class="card shadow-sm">
|
<div class="card shadow-sm">
|
||||||
@ -14,10 +12,8 @@
|
|||||||
{% include 'bill/includes/card_bill.html' with dealer_slug=request.dealer.slug bill=bill entity_slug=view.kwargs.entity_slug style='bill-detail' %}
|
{% include 'bill/includes/card_bill.html' with dealer_slug=request.dealer.slug bill=bill entity_slug=view.kwargs.entity_slug style='bill-detail' %}
|
||||||
</div>
|
</div>
|
||||||
{% if bill.is_configured %}
|
{% if bill.is_configured %}
|
||||||
|
|
||||||
<div class="row text-center g-3 mb-3">
|
<div class="row text-center g-3 mb-3">
|
||||||
<div class="col-12 col-md-3">
|
<div class="col-12 col-md-3">
|
||||||
|
|
||||||
<h6 class="text-uppercase text-xs text-muted mb-2">
|
<h6 class="text-uppercase text-xs text-muted mb-2">
|
||||||
{% trans 'Cash Account' %}:
|
{% trans 'Cash Account' %}:
|
||||||
{% if bill.cash_account %}
|
{% if bill.cash_account %}
|
||||||
@ -30,18 +26,14 @@
|
|||||||
<h4 class="mb-0" id="djl-bill-detail-amount-paid">
|
<h4 class="mb-0" id="djl-bill-detail-amount-paid">
|
||||||
{% currency_symbol %}{{ bill.get_amount_cash | absolute | currency_format }}
|
{% currency_symbol %}{{ bill.get_amount_cash | absolute | currency_format }}
|
||||||
</h4>
|
</h4>
|
||||||
|
|
||||||
</div>
|
</div>
|
||||||
{% if bill.accrue %}
|
{% if bill.accrue %}
|
||||||
<div class="col-12 col-md-3">
|
<div class="col-12 col-md-3">
|
||||||
|
|
||||||
<h6 class="text-uppercase text-xs text-muted mb-2">
|
<h6 class="text-uppercase text-xs text-muted mb-2">
|
||||||
{% trans 'Prepaid Account' %}:
|
{% trans 'Prepaid Account' %}:
|
||||||
{% if bill.prepaid_account %}
|
{% if bill.prepaid_account %}
|
||||||
<a href="{% url 'account_detail' request.dealer.slug bill.prepaid_account.coa_model.pk bill.prepaid_account.uuid %}"
|
<a href="{% url 'account_detail' request.dealer.slug bill.prepaid_account.coa_model.pk bill.prepaid_account.uuid %}"
|
||||||
class="text-decoration-none ms-1">
|
class="text-decoration-none ms-1">{{ bill.prepaid_account.code }}</a>
|
||||||
{{ bill.prepaid_account.code }}
|
|
||||||
</a>
|
|
||||||
{% else %}
|
{% else %}
|
||||||
{{ bill.prepaid_account.code }}
|
{{ bill.prepaid_account.code }}
|
||||||
{% endif %}
|
{% endif %}
|
||||||
@ -49,17 +41,13 @@
|
|||||||
<h4 class="text-success mb-0" id="djl-bill-detail-amount-prepaid">
|
<h4 class="text-success mb-0" id="djl-bill-detail-amount-prepaid">
|
||||||
{% currency_symbol %}{{ bill.get_amount_prepaid | currency_format }}
|
{% currency_symbol %}{{ bill.get_amount_prepaid | currency_format }}
|
||||||
</h4>
|
</h4>
|
||||||
|
|
||||||
</div>
|
</div>
|
||||||
<div class="col-12 col-md-3">
|
<div class="col-12 col-md-3">
|
||||||
|
|
||||||
<h6 class="text-uppercase text-xs text-muted mb-2">
|
<h6 class="text-uppercase text-xs text-muted mb-2">
|
||||||
{% trans 'Accounts Payable' %}:
|
{% trans 'Accounts Payable' %}:
|
||||||
{% if bill.unearned_account %}
|
{% if bill.unearned_account %}
|
||||||
<a href="{% url 'account_detail' request.dealer.slug bill.unearned_account.coa_model.pk bill.unearned_account.uuid %}"
|
<a href="{% url 'account_detail' request.dealer.slug bill.unearned_account.coa_model.pk bill.unearned_account.uuid %}"
|
||||||
class="text-decoration-none ms-1">
|
class="text-decoration-none ms-1">{{ bill.unearned_account.code }}</a>
|
||||||
{{ bill.unearned_account.code }}
|
|
||||||
</a>
|
|
||||||
{% else %}
|
{% else %}
|
||||||
{{ bill.unearned_account.code }}
|
{{ bill.unearned_account.code }}
|
||||||
{% endif %}
|
{% endif %}
|
||||||
@ -67,30 +55,23 @@
|
|||||||
<h4 class="text-danger mb-0" id="djl-bill-detail-amount-unearned">
|
<h4 class="text-danger mb-0" id="djl-bill-detail-amount-unearned">
|
||||||
{% currency_symbol %}{{ bill.get_amount_unearned | currency_format }}
|
{% currency_symbol %}{{ bill.get_amount_unearned | currency_format }}
|
||||||
</h4>
|
</h4>
|
||||||
|
|
||||||
</div>
|
</div>
|
||||||
<div class="col-12 col-md-3">
|
<div class="col-12 col-md-3">
|
||||||
|
|
||||||
<h6 class="text-uppercase text-xs text-muted mb-2">{% trans 'Accrued' %} {{ bill.get_progress | percentage }}</h6>
|
<h6 class="text-uppercase text-xs text-muted mb-2">{% trans 'Accrued' %} {{ bill.get_progress | percentage }}</h6>
|
||||||
<h4 class="mb-0">{% currency_symbol %}{{ bill.get_amount_earned | currency_format }}</h4>
|
<h4 class="mb-0">{% currency_symbol %}{{ bill.get_amount_earned | currency_format }}</h4>
|
||||||
|
|
||||||
</div>
|
</div>
|
||||||
{% else %}
|
{% else %}
|
||||||
<div class="col-12 col-md-3 offset-md-6">
|
<div class="col-12 col-md-3 offset-md-6">
|
||||||
|
|
||||||
<h6 class="text-uppercase text-xs text-muted mb-2">{% trans 'You Still Owe' %}</h6>
|
<h6 class="text-uppercase text-xs text-muted mb-2">{% trans 'You Still Owe' %}</h6>
|
||||||
<h4 class="text-danger mb-0" id="djl-bill-detail-amount-owed">
|
<h4 class="text-danger mb-0" id="djl-bill-detail-amount-owed">
|
||||||
{% currency_symbol %}{{ bill.get_amount_open | currency_format }}
|
{% currency_symbol %}{{ bill.get_amount_open | currency_format }}
|
||||||
</h4>
|
</h4>
|
||||||
|
|
||||||
</div>
|
</div>
|
||||||
{% endif %}
|
{% endif %}
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
{% endif %}
|
{% endif %}
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class="col-12">
|
<div class="col-12">
|
||||||
<div class="card mb-3 shadow-sm">
|
<div class="card mb-3 shadow-sm">
|
||||||
<div class="card-header pb-0">
|
<div class="card-header pb-0">
|
||||||
@ -166,7 +147,6 @@
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class="col-12">
|
<div class="col-12">
|
||||||
<div class="card mb-3 shadow-sm">
|
<div class="card mb-3 shadow-sm">
|
||||||
<div class="card-header pb-0">
|
<div class="card-header pb-0">
|
||||||
@ -180,4 +160,4 @@
|
|||||||
</div>
|
</div>
|
||||||
<div>
|
<div>
|
||||||
{% include "bill/includes/mark_as.html" %}
|
{% include "bill/includes/mark_as.html" %}
|
||||||
{% endblock %}
|
{% endblock %}
|
||||||
|
|||||||
@ -14,7 +14,8 @@
|
|||||||
<div class="card mb-2">
|
<div class="card mb-2">
|
||||||
<div class="card-body">
|
<div class="card-body">
|
||||||
{% include 'bill/includes/card_bill.html' with dealer_slug=request.dealer.slug bill=bill_model style='bill-detail' entity_slug=view.kwargs.entity_slug %}
|
{% include 'bill/includes/card_bill.html' with dealer_slug=request.dealer.slug bill=bill_model style='bill-detail' entity_slug=view.kwargs.entity_slug %}
|
||||||
<form action="{% url 'bill-update' dealer_slug=request.dealer.slug entity_slug=view.kwargs.entity_slug bill_pk=bill_model.uuid %}" method="post">
|
<form action="{% url 'bill-update' dealer_slug=request.dealer.slug entity_slug=view.kwargs.entity_slug bill_pk=bill_model.uuid %}"
|
||||||
|
method="post">
|
||||||
{% csrf_token %}
|
{% csrf_token %}
|
||||||
<div class="mb-3">{{ form|crispy }}</div>
|
<div class="mb-3">{{ form|crispy }}</div>
|
||||||
<button type="submit" class="btn btn-phoenix-primary mb-2 me-2">
|
<button type="submit" class="btn btn-phoenix-primary mb-2 me-2">
|
||||||
|
|||||||
@ -4,7 +4,6 @@
|
|||||||
{% if not create_bill %}
|
{% if not create_bill %}
|
||||||
{% if style == 'dashboard' %}
|
{% if style == 'dashboard' %}
|
||||||
<!-- Dashboard Style Card -->
|
<!-- Dashboard Style Card -->
|
||||||
|
|
||||||
<div class="">
|
<div class="">
|
||||||
<div class="card-body">
|
<div class="card-body">
|
||||||
<div class="d-flex justify-content-between align-items-center mb-3 text-primary">
|
<div class="d-flex justify-content-between align-items-center mb-3 text-primary">
|
||||||
@ -51,15 +50,16 @@
|
|||||||
</div>
|
</div>
|
||||||
<!-- Modal Action -->
|
<!-- Modal Action -->
|
||||||
{% modal_action bill 'get' entity_slug %}
|
{% modal_action bill 'get' entity_slug %}
|
||||||
|
|
||||||
<div class="d-grid gap-2 d-md-flex justify-content-md-end">
|
<div class="d-grid gap-2 d-md-flex justify-content-md-end">
|
||||||
<a href="{% url 'django_ledger:bill-detail' entity_slug=entity_slug bill_pk=bill.uuid %}"
|
<a href="{% url 'django_ledger:bill-detail' entity_slug=entity_slug bill_pk=bill.uuid %}"
|
||||||
class="btn btn-sm btn-phoenix-primary me-md-2">{% trans 'View' %}</a>
|
class="btn btn-sm btn-phoenix-primary me-md-2">{% trans 'View' %}</a>
|
||||||
{% if perms.django_ledger.change_billmodel %}
|
{% if perms.django_ledger.change_billmodel %}
|
||||||
<a hx-boost="true" href="{% url 'django_ledger:bill-update' entity_slug=entity_slug bill_pk=bill.uuid %}"
|
<a hx-boost="true"
|
||||||
|
href="{% url 'django_ledger:bill-update' entity_slug=entity_slug bill_pk=bill.uuid %}"
|
||||||
class="btn btn-sm btn-phoenix-warning me-md-2">{% trans 'Update' %}</a>
|
class="btn btn-sm btn-phoenix-warning me-md-2">{% trans 'Update' %}</a>
|
||||||
{% if bill.can_pay %}
|
{% if bill.can_pay %}
|
||||||
<button onclick="djLedger.toggleModal('{{ bill.get_html_id }}')" class="btn btn-sm btn-phoenix-info">{% trans 'Mark as Paid' %}</button>
|
<button onclick="djLedger.toggleModal('{{ bill.get_html_id }}')"
|
||||||
|
class="btn btn-sm btn-phoenix-info">{% trans 'Mark as Paid' %}</button>
|
||||||
{% endif %}
|
{% endif %}
|
||||||
{% if bill.can_cancel %}
|
{% if bill.can_cancel %}
|
||||||
<button onclick="djLedger.toggleModal('{{ bill.get_html_id }}')"
|
<button onclick="djLedger.toggleModal('{{ bill.get_html_id }}')"
|
||||||
@ -203,7 +203,8 @@
|
|||||||
<!-- Update Button -->
|
<!-- Update Button -->
|
||||||
{% if perms.django_ledger.change_billmodel %}
|
{% if perms.django_ledger.change_billmodel %}
|
||||||
{% if "update" not in request.path %}
|
{% if "update" not in request.path %}
|
||||||
<a hx-boost="true" href="{% url 'bill-update' dealer_slug=dealer_slug entity_slug=entity_slug bill_pk=bill.uuid %}">
|
<a hx-boost="true"
|
||||||
|
href="{% url 'bill-update' dealer_slug=dealer_slug entity_slug=entity_slug bill_pk=bill.uuid %}">
|
||||||
<button class="btn btn-phoenix-primary">
|
<button class="btn btn-phoenix-primary">
|
||||||
<i class="fas fa-edit me-2"></i>{% trans 'Update' %}
|
<i class="fas fa-edit me-2"></i>{% trans 'Update' %}
|
||||||
</button>
|
</button>
|
||||||
@ -219,7 +220,6 @@
|
|||||||
{% endif %}
|
{% endif %}
|
||||||
<!-- Mark as Review -->
|
<!-- Mark as Review -->
|
||||||
{% if bill.can_review %}
|
{% if bill.can_review %}
|
||||||
|
|
||||||
<button class="btn btn-phoenix-warning"
|
<button class="btn btn-phoenix-warning"
|
||||||
onclick="showPOModal('Mark as Review', '{% url 'bill-action-mark-as-review' dealer_slug=request.dealer.slug entity_slug=entity_slug bill_pk=bill.pk %}', 'Mark as Review')">
|
onclick="showPOModal('Mark as Review', '{% url 'bill-action-mark-as-review' dealer_slug=request.dealer.slug entity_slug=entity_slug bill_pk=bill.pk %}', 'Mark as Review')">
|
||||||
<i class="fas fa-check-circle me-2"></i>{% trans 'Mark as Review' %}
|
<i class="fas fa-check-circle me-2"></i>{% trans 'Mark as Review' %}
|
||||||
@ -261,7 +261,6 @@
|
|||||||
</button>
|
</button>
|
||||||
{% modal_action_v2 bill bill.get_mark_as_canceled_url bill.get_mark_as_canceled_message bill.get_mark_as_canceled_html_id %}
|
{% modal_action_v2 bill bill.get_mark_as_canceled_url bill.get_mark_as_canceled_message bill.get_mark_as_canceled_html_id %}
|
||||||
{% endif %}
|
{% endif %}
|
||||||
|
|
||||||
{% endif %}
|
{% endif %}
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@ -2,14 +2,19 @@
|
|||||||
{% load static %}
|
{% load static %}
|
||||||
{% load django_ledger %}
|
{% load django_ledger %}
|
||||||
{% load widget_tweaks %}
|
{% load widget_tweaks %}
|
||||||
|
|
||||||
{% if bill.get_itemtxs_data.1.total_amount__sum > 0 %}
|
{% if bill.get_itemtxs_data.1.total_amount__sum > 0 %}
|
||||||
<form id="bill-update-form" action="{% url 'bill-update-items' dealer_slug=dealer_slug entity_slug=entity_slug bill_pk=bill_pk %}"
|
<form id="bill-update-form"
|
||||||
|
action="{% url 'bill-update-items' dealer_slug=dealer_slug entity_slug=entity_slug bill_pk=bill_pk %}"
|
||||||
method="post">
|
method="post">
|
||||||
{% else %}
|
{% else %}
|
||||||
<form id="bill-update-form" hx-trigger="load delay:300ms" hx-swap="outerHTML" hx-target="#bill-update-form" hx-select="#bill-update-form" hx-post="{% url 'bill-update-items' dealer_slug=dealer_slug entity_slug=entity_slug bill_pk=bill_pk %}"
|
<form id="bill-update-form"
|
||||||
|
hx-trigger="load delay:300ms"
|
||||||
|
hx-swap="outerHTML"
|
||||||
|
hx-target="#bill-update-form"
|
||||||
|
hx-select="#bill-update-form"
|
||||||
|
hx-post="{% url 'bill-update-items' dealer_slug=dealer_slug entity_slug=entity_slug bill_pk=bill_pk %}"
|
||||||
method="post">
|
method="post">
|
||||||
{% endif %}
|
{% endif %}
|
||||||
<div class="container-fluid py-4">
|
<div class="container-fluid py-4">
|
||||||
<!-- Page Header -->
|
<!-- Page Header -->
|
||||||
<div class="row mb-4">
|
<div class="row mb-4">
|
||||||
@ -135,4 +140,4 @@
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</form>
|
</form>
|
||||||
|
|||||||
@ -2,9 +2,8 @@
|
|||||||
{% load i18n static %}
|
{% load i18n static %}
|
||||||
{% load django_ledger %}
|
{% load django_ledger %}
|
||||||
{% load widget_tweaks %}
|
{% load widget_tweaks %}
|
||||||
|
|
||||||
{% block content %}
|
{% block content %}
|
||||||
<main class="d-flex align-items-center justify-content-center min-vh-80 py-5">
|
<main class="d-flex align-items-center justify-content-center min-vh-80 py-5">
|
||||||
<div class="col-12 col-sm-10 col-md-8 col-lg-6 col-xl-5">
|
<div class="col-12 col-sm-10 col-md-8 col-lg-6 col-xl-5">
|
||||||
<div class="card shadow-lg border-0 rounded-4 overflow-hidden animate__animated animate__fadeInUp">
|
<div class="card shadow-lg border-0 rounded-4 overflow-hidden animate__animated animate__fadeInUp">
|
||||||
<div class="card-header bg-gradient py-4 border-0 rounded-top-4">
|
<div class="card-header bg-gradient py-4 border-0 rounded-top-4">
|
||||||
@ -14,19 +13,17 @@
|
|||||||
</h3>
|
</h3>
|
||||||
</div>
|
</div>
|
||||||
<div class="card-body p-4 p-md-5">
|
<div class="card-body p-4 p-md-5">
|
||||||
<form method="post" id="{{ form.get_form_id }}" class="needs-validation" novalidate>
|
<form method="post"
|
||||||
|
id="{{ form.get_form_id }}"
|
||||||
|
class="needs-validation"
|
||||||
|
novalidate>
|
||||||
{% csrf_token %}
|
{% csrf_token %}
|
||||||
|
|
||||||
{# Bootstrap form rendering #}
|
{# Bootstrap form rendering #}
|
||||||
<div class="mb-3">
|
<div class="mb-3">
|
||||||
{{ form.name.label_tag }}
|
{{ form.name.label_tag }}
|
||||||
{{ form.name|add_class:"form-control" }}
|
{{ form.name|add_class:"form-control" }}
|
||||||
{% if form.name.help_text %}
|
{% if form.name.help_text %}<small class="form-text text-muted">{{ form.name.help_text }}</small>{% endif %}
|
||||||
<small class="form-text text-muted">{{ form.name.help_text }}</small>
|
{% for error in form.name.errors %}<div class="invalid-feedback d-block">{{ error }}</div>{% endfor %}
|
||||||
{% endif %}
|
|
||||||
{% for error in form.name.errors %}
|
|
||||||
<div class="invalid-feedback d-block">{{ error }}</div>
|
|
||||||
{% endfor %}
|
|
||||||
</div>
|
</div>
|
||||||
<div class="mb-3">
|
<div class="mb-3">
|
||||||
{{ form.description.label_tag }}
|
{{ form.description.label_tag }}
|
||||||
@ -34,11 +31,8 @@
|
|||||||
{% if form.description.help_text %}
|
{% if form.description.help_text %}
|
||||||
<small class="form-text text-muted">{{ form.description.help_text }}</small>
|
<small class="form-text text-muted">{{ form.description.help_text }}</small>
|
||||||
{% endif %}
|
{% endif %}
|
||||||
{% for error in form.description.errors %}
|
{% for error in form.description.errors %}<div class="invalid-feedback d-block">{{ error }}</div>{% endfor %}
|
||||||
<div class="invalid-feedback d-block">{{ error }}</div>
|
|
||||||
{% endfor %}
|
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<hr class="my-4">
|
<hr class="my-4">
|
||||||
<div class="d-grid gap-2 d-md-flex justify-content-md-center mt-3">
|
<div class="d-grid gap-2 d-md-flex justify-content-md-center mt-3">
|
||||||
<button type="submit" class="btn btn-phoenix-primary btn-lg me-md-2">
|
<button type="submit" class="btn btn-phoenix-primary btn-lg me-md-2">
|
||||||
@ -55,5 +49,5 @@
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</main>
|
</main>
|
||||||
{% endblock %}
|
{% endblock %}
|
||||||
@ -2,7 +2,6 @@
|
|||||||
{% load i18n %}
|
{% load i18n %}
|
||||||
{% load static %}
|
{% load static %}
|
||||||
{% load icon from django_ledger %}
|
{% load icon from django_ledger %}
|
||||||
|
|
||||||
{% block content %}
|
{% block content %}
|
||||||
<div class="card mb-4">
|
<div class="card mb-4">
|
||||||
<div class="card-body">
|
<div class="card-body">
|
||||||
@ -10,17 +9,19 @@
|
|||||||
<div class="col-12">
|
<div class="col-12">
|
||||||
<div class="d-flex justify-content-between align-items-center mb-4">
|
<div class="d-flex justify-content-between align-items-center mb-4">
|
||||||
<h1 class="display-4 mb-0">{% trans "Chart of Accounts" %}</h1>
|
<h1 class="display-4 mb-0">{% trans "Chart of Accounts" %}</h1>
|
||||||
<a href="{% url 'coa-create' request.dealer.slug request.entity.slug %}" class="btn btn-phoenix-primary">
|
<a href="{% url 'coa-create' request.dealer.slug request.entity.slug %}"
|
||||||
|
class="btn btn-phoenix-primary">
|
||||||
<i class="fa-solid fa-plus"></i> {% trans "Add New" %}
|
<i class="fa-solid fa-plus"></i> {% trans "Add New" %}
|
||||||
</a>
|
</a>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
{% if not inactive %}
|
{% if not inactive %}
|
||||||
<a class="btn btn-phoenix-warning mb-4" href="{% url 'coa-list-inactive' request.dealer.slug request.entity.slug %}">
|
<a class="btn btn-phoenix-warning mb-4"
|
||||||
|
href="{% url 'coa-list-inactive' request.dealer.slug request.entity.slug %}">
|
||||||
{% trans 'Show Inactive' %}
|
{% trans 'Show Inactive' %}
|
||||||
</a>
|
</a>
|
||||||
{% else %}
|
{% else %}
|
||||||
<a class="btn btn-phoenix-warning mb-4" href="{% url 'coa-list' request.dealer.slug request.entity.slug %}">
|
<a class="btn btn-phoenix-warning mb-4"
|
||||||
|
href="{% url 'coa-list' request.dealer.slug request.entity.slug %}">
|
||||||
{% trans 'Show Active' %}
|
{% trans 'Show Active' %}
|
||||||
</a>
|
</a>
|
||||||
{% endif %}
|
{% endif %}
|
||||||
@ -28,9 +29,7 @@
|
|||||||
</div>
|
</div>
|
||||||
<div class="row row-cols-1 row-cols-md-2 g-4">
|
<div class="row row-cols-1 row-cols-md-2 g-4">
|
||||||
{% for coa_model in coa_list %}
|
{% for coa_model in coa_list %}
|
||||||
<div class="col">
|
<div class="col">{% include 'chart_of_accounts/includes/coa_card.html' with coa_model=coa_model %}</div>
|
||||||
{% include 'chart_of_accounts/includes/coa_card.html' with coa_model=coa_model %}
|
|
||||||
</div>
|
|
||||||
{% endfor %}
|
{% endfor %}
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@ -2,23 +2,20 @@
|
|||||||
{% load i18n %}
|
{% load i18n %}
|
||||||
{% load static %}
|
{% load static %}
|
||||||
{% load widget_tweaks %}
|
{% load widget_tweaks %}
|
||||||
|
|
||||||
{% block content %}
|
{% block content %}
|
||||||
<div class="row justify-content-center">
|
<div class="row justify-content-center">
|
||||||
<div class="col-lg-6 col-md-8">
|
<div class="col-lg-6 col-md-8">
|
||||||
<div class="card shadow-sm">
|
<div class="card shadow-sm">
|
||||||
<div class="card-body">
|
<div class="card-body">
|
||||||
<form action="{% url 'coa-update' request.dealer.slug request.entity.slug coa_model.slug %}" id="{{ form.form_id }}" method="post">
|
<form action="{% url 'coa-update' request.dealer.slug request.entity.slug coa_model.slug %}"
|
||||||
|
id="{{ form.form_id }}"
|
||||||
|
method="post">
|
||||||
{% csrf_token %}
|
{% csrf_token %}
|
||||||
<div class="mb-3">
|
<div class="mb-3">
|
||||||
{{ form.name.label_tag }}
|
{{ form.name.label_tag }}
|
||||||
{{ form.name|add_class:"form-control" }}
|
{{ form.name|add_class:"form-control" }}
|
||||||
{% if form.name.help_text %}
|
{% if form.name.help_text %}<small class="form-text text-muted">{{ form.name.help_text }}</small>{% endif %}
|
||||||
<small class="form-text text-muted">{{ form.name.help_text }}</small>
|
{% for error in form.name.errors %}<div class="invalid-feedback d-block">{{ error }}</div>{% endfor %}
|
||||||
{% endif %}
|
|
||||||
{% for error in form.name.errors %}
|
|
||||||
<div class="invalid-feedback d-block">{{ error }}</div>
|
|
||||||
{% endfor %}
|
|
||||||
</div>
|
</div>
|
||||||
<div class="mb-3">
|
<div class="mb-3">
|
||||||
{{ form.description.label_tag }}
|
{{ form.description.label_tag }}
|
||||||
@ -26,19 +23,13 @@
|
|||||||
{% if form.description.help_text %}
|
{% if form.description.help_text %}
|
||||||
<small class="form-text text-muted">{{ form.description.help_text }}</small>
|
<small class="form-text text-muted">{{ form.description.help_text }}</small>
|
||||||
{% endif %}
|
{% endif %}
|
||||||
{% for error in form.description.errors %}
|
{% for error in form.description.errors %}<div class="invalid-feedback d-block">{{ error }}</div>{% endfor %}
|
||||||
<div class="invalid-feedback d-block">{{ error }}</div>
|
|
||||||
{% endfor %}
|
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
|
||||||
<div class="d-flex justify-content-center gap-2 mt-4">
|
<div class="d-flex justify-content-center gap-2 mt-4">
|
||||||
<button class="btn btn-phoenix-primary" type="submit">
|
<button class="btn btn-phoenix-primary" type="submit">{% trans 'Update' %}</button>
|
||||||
{% trans 'Update'%}
|
|
||||||
</button>
|
|
||||||
<a class="btn btn-phoenix-secondary"
|
<a class="btn btn-phoenix-secondary"
|
||||||
href="{% url 'coa-list' request.dealer.slug request.entity.slug %}">
|
href="{% url 'coa-list' request.dealer.slug request.entity.slug %}">
|
||||||
{% trans 'Back'%}
|
{% trans 'Back' %}
|
||||||
</a>
|
</a>
|
||||||
</div>
|
</div>
|
||||||
</form>
|
</form>
|
||||||
|
|||||||
@ -1,7 +1,6 @@
|
|||||||
{% load django_ledger %}
|
{% load django_ledger %}
|
||||||
{% load i18n %}
|
{% load i18n %}
|
||||||
{% now "Y" as current_year %}
|
{% now "Y" as current_year %}
|
||||||
|
|
||||||
<div class="card shadow-sm border-0 mb-4">
|
<div class="card shadow-sm border-0 mb-4">
|
||||||
<div class="card-header {% if coa_model.is_default %}bg-gray-100{% elif not coa_model.is_active %}bg-danger text-white{% endif %} py-3 d-flex align-items-center">
|
<div class="card-header {% if coa_model.is_default %}bg-gray-100{% elif not coa_model.is_active %}bg-danger text-white{% endif %} py-3 d-flex align-items-center">
|
||||||
<div class="me-3">
|
<div class="me-3">
|
||||||
@ -29,7 +28,6 @@
|
|||||||
{% endif %}
|
{% endif %}
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class="card-body">
|
<div class="card-body">
|
||||||
<div class="row g-3">
|
<div class="row g-3">
|
||||||
<div class="col-sm-6">
|
<div class="col-sm-6">
|
||||||
@ -46,27 +44,22 @@
|
|||||||
<span class="text-muted ms-2">{{ coa_model.slug }}</span>
|
<span class="text-muted ms-2">{{ coa_model.slug }}</span>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class="col-sm-6">
|
<div class="col-sm-6">
|
||||||
<div class="mb-2">
|
<div class="mb-2">
|
||||||
<span class="fw-bold"><i class="fas fa-list-alt me-1"></i> {% trans 'Total Accounts' %}:</span>
|
<span class="fw-bold"><i class="fas fa-list-alt me-1"></i> {% trans 'Total Accounts' %}:</span>
|
||||||
<span class="ms-2">{{ coa_model.accountmodel_total__count }}</span>
|
<span class="ms-2">{{ coa_model.accountmodel_total__count }}</span>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class="mb-2">
|
<div class="mb-2">
|
||||||
<span class="fw-bold text-info"><i class="fas fa-check-circle me-1"></i> {% trans 'Active Accounts' %}:</span>
|
<span class="fw-bold text-info"><i class="fas fa-check-circle me-1"></i> {% trans 'Active Accounts' %}:</span>
|
||||||
<span class="ms-2">{{ coa_model.accountmodel_active__count }}</span>
|
<span class="ms-2">{{ coa_model.accountmodel_active__count }}</span>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class="mb-2">
|
<div class="mb-2">
|
||||||
<span class="fw-bold text-danger"><i class="fas fa-lock me-1"></i> {% trans 'Locked Accounts' %}:</span>
|
<span class="fw-bold text-danger"><i class="fas fa-lock me-1"></i> {% trans 'Locked Accounts' %}:</span>
|
||||||
<span class="ms-2">{{ coa_model.accountmodel_locked__count }}</span>
|
<span class="ms-2">{{ coa_model.accountmodel_locked__count }}</span>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<hr class="my-3">
|
<hr class="my-3">
|
||||||
|
|
||||||
<div class="row g-2">
|
<div class="row g-2">
|
||||||
<div class="col-sm-6">
|
<div class="col-sm-6">
|
||||||
<small class="text-muted d-block">
|
<small class="text-muted d-block">
|
||||||
@ -84,33 +77,34 @@
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class="card-footer bg-transparent border-top-0 pt-0 pt-sm-3">
|
<div class="card-footer bg-transparent border-top-0 pt-0 pt-sm-3">
|
||||||
<div class="d-flex flex-wrap gap-2">
|
<div class="d-flex flex-wrap gap-2">
|
||||||
<a href="{% url 'coa-update' request.dealer.slug request.entity.slug coa_model.slug %}" class="btn btn-sm btn-phoenix-warning fw-bold flex-grow-1 flex-sm-grow-0">
|
<a href="{% url 'coa-update' request.dealer.slug request.entity.slug coa_model.slug %}"
|
||||||
|
class="btn btn-sm btn-phoenix-warning fw-bold flex-grow-1 flex-sm-grow-0">
|
||||||
<i class="fas fa-edit me-1"></i> {% trans 'Update' %}
|
<i class="fas fa-edit me-1"></i> {% trans 'Update' %}
|
||||||
</a>
|
</a>
|
||||||
|
<a href="{% url 'account_list' request.dealer.slug coa_model.pk %}"
|
||||||
<a href="{% url 'account_list' request.dealer.slug coa_model.pk %}" class="btn btn-sm btn-phoenix-success fw-bold flex-grow-1 flex-sm-grow-0">
|
class="btn btn-sm btn-phoenix-success fw-bold flex-grow-1 flex-sm-grow-0">
|
||||||
<i class="fas fa-book me-1"></i> {% trans 'Accounts' %}
|
<i class="fas fa-book me-1"></i> {% trans 'Accounts' %}
|
||||||
</a>
|
</a>
|
||||||
|
<a href="{% url 'account_create' request.dealer.slug coa_model.pk %}"
|
||||||
<a href="{% url 'account_create' request.dealer.slug coa_model.pk %}" class="btn btn-sm btn-phoenix-info fw-bold flex-grow-1 flex-sm-grow-0">
|
class="btn btn-sm btn-phoenix-info fw-bold flex-grow-1 flex-sm-grow-0">
|
||||||
<i class="fas fa-plus-circle me-1"></i> {% trans 'Add Account' %}
|
<i class="fas fa-plus-circle me-1"></i> {% trans 'Add Account' %}
|
||||||
</a>
|
</a>
|
||||||
|
|
||||||
{% if coa_model.can_mark_as_default %}
|
{% if coa_model.can_mark_as_default %}
|
||||||
<a href="{% url 'coa-action-mark-as-default' request.dealer.slug request.entity.slug coa_model.slug %}" class="btn btn-sm btn-phoenix-danger fw-bold flex-grow-1 flex-sm-grow-0">
|
<a href="{% url 'coa-action-mark-as-default' request.dealer.slug request.entity.slug coa_model.slug %}"
|
||||||
|
class="btn btn-sm btn-phoenix-danger fw-bold flex-grow-1 flex-sm-grow-0">
|
||||||
<i class="fas fa-star me-1"></i> {% trans 'Mark as Default' %}
|
<i class="fas fa-star me-1"></i> {% trans 'Mark as Default' %}
|
||||||
</a>
|
</a>
|
||||||
{% endif %}
|
{% endif %}
|
||||||
|
|
||||||
{% if coa_model.can_deactivate %}
|
{% if coa_model.can_deactivate %}
|
||||||
<a href="{% url 'coa-action-mark-as-inactive' request.dealer.slug request.entity.slug coa_model.slug %}" class="btn btn-sm btn-phoenix-warning fw-bold flex-grow-1 flex-sm-grow-0">
|
<a href="{% url 'coa-action-mark-as-inactive' request.dealer.slug request.entity.slug coa_model.slug %}"
|
||||||
|
class="btn btn-sm btn-phoenix-warning fw-bold flex-grow-1 flex-sm-grow-0">
|
||||||
<i class="fas fa-toggle-off me-1"></i> {% trans 'Mark as Inactive' %}
|
<i class="fas fa-toggle-off me-1"></i> {% trans 'Mark as Inactive' %}
|
||||||
</a>
|
</a>
|
||||||
{% elif coa_model.can_activate %}
|
{% elif coa_model.can_activate %}
|
||||||
<a href="{% url 'coa-action-mark-as-active' request.dealer.slug request.entity.slug coa_model.slug %}" class="btn btn-sm btn-phoenix-success fw-bold flex-grow-1 flex-sm-grow-0">
|
<a href="{% url 'coa-action-mark-as-active' request.dealer.slug request.entity.slug coa_model.slug %}"
|
||||||
|
class="btn btn-sm btn-phoenix-success fw-bold flex-grow-1 flex-sm-grow-0">
|
||||||
<i class="fas fa-toggle-on me-1"></i> {% trans 'Mark as Active' %}
|
<i class="fas fa-toggle-on me-1"></i> {% trans 'Mark as Active' %}
|
||||||
</a>
|
</a>
|
||||||
{% endif %}
|
{% endif %}
|
||||||
|
|||||||
@ -1,10 +1,16 @@
|
|||||||
{% load i18n crispy_forms_tags %}
|
{% load i18n crispy_forms_tags %}
|
||||||
<div class="modal fade" id="emailModal" tabindex="-1" aria-labelledby="emailModalLabel" aria-hidden="true">
|
<div class="modal fade"
|
||||||
|
id="emailModal"
|
||||||
|
tabindex="-1"
|
||||||
|
aria-labelledby="emailModalLabel"
|
||||||
|
aria-hidden="true">
|
||||||
<div class="modal-dialog modal-xl">
|
<div class="modal-dialog modal-xl">
|
||||||
<div class="modal-content">
|
<div class="modal-content">
|
||||||
<div class="modal-header justify-content-between align-items-start gap-5 px-4 pt-4 pb-3 border-0">
|
<div class="modal-header justify-content-between align-items-start gap-5 px-4 pt-4 pb-3 border-0">
|
||||||
<h4 class="modal-title" id="emailModalLabel">{% trans 'Send Email' %}</h4>
|
<h4 class="modal-title" id="emailModalLabel">{% trans 'Send Email' %}</h4>
|
||||||
<button class="btn p-0 text-body-quaternary fs-6" data-bs-dismiss="modal" aria-label="Close">
|
<button class="btn p-0 text-body-quaternary fs-6"
|
||||||
|
data-bs-dismiss="modal"
|
||||||
|
aria-label="Close">
|
||||||
<span class="fas fa-times"></span>
|
<span class="fas fa-times"></span>
|
||||||
</button>
|
</button>
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@ -15,13 +15,11 @@
|
|||||||
</button>
|
</button>
|
||||||
</div>
|
</div>
|
||||||
<div class="modal-body">
|
<div class="modal-body">
|
||||||
<form id="noteForm" action="{% url 'add_note' request.dealer.slug content_type slug %}"
|
<form id="noteForm"
|
||||||
|
action="{% url 'add_note' request.dealer.slug content_type slug %}"
|
||||||
hx-select="#notesTable"
|
hx-select="#notesTable"
|
||||||
hx-target="#notesTable"
|
hx-target="#notesTable"
|
||||||
hx-on::after-request="{
|
hx-on::after-request="{ resetSubmitButton(document.querySelector('.add_note_form button[type=submit]')); $('#noteModal').modal('hide'); }"
|
||||||
resetSubmitButton(document.querySelector('.add_note_form button[type=submit]'));
|
|
||||||
$('#noteModal').modal('hide');
|
|
||||||
}"
|
|
||||||
hx-swap="outerHTML"
|
hx-swap="outerHTML"
|
||||||
method="post"
|
method="post"
|
||||||
class="add_note_form">
|
class="add_note_form">
|
||||||
|
|||||||
@ -15,13 +15,11 @@
|
|||||||
</button>
|
</button>
|
||||||
</div>
|
</div>
|
||||||
<div class="modal-body">
|
<div class="modal-body">
|
||||||
<form id="scheduleForm" action="{% url 'schedule_event' request.dealer.slug content_type slug %}"
|
<form id="scheduleForm"
|
||||||
|
action="{% url 'schedule_event' request.dealer.slug content_type slug %}"
|
||||||
hx-select=".taskTable"
|
hx-select=".taskTable"
|
||||||
hx-target=".taskTable"
|
hx-target=".taskTable"
|
||||||
hx-on::after-request="{
|
hx-on::after-request="{ resetSubmitButton(document.querySelector('.add_schedule_form button[type=submit]')); $('#scheduleModal').modal('hide'); }"
|
||||||
resetSubmitButton(document.querySelector('.add_schedule_form button[type=submit]'));
|
|
||||||
$('#scheduleModal').modal('hide');
|
|
||||||
}"
|
|
||||||
hx-swap="outerHTML"
|
hx-swap="outerHTML"
|
||||||
method="post"
|
method="post"
|
||||||
class="add_schedule_form">
|
class="add_schedule_form">
|
||||||
|
|||||||
@ -22,7 +22,8 @@
|
|||||||
</button>
|
</button>
|
||||||
</div>
|
</div>
|
||||||
<div class="modal-body">
|
<div class="modal-body">
|
||||||
<form id="taskForm" action="{% url 'add_task' request.dealer.slug content_type slug %}"
|
<form id="taskForm"
|
||||||
|
action="{% url 'add_task' request.dealer.slug content_type slug %}"
|
||||||
method="post"
|
method="post"
|
||||||
class="add_task_form"
|
class="add_task_form"
|
||||||
hx-post="{% url 'add_task' request.dealer.slug content_type slug %}"
|
hx-post="{% url 'add_task' request.dealer.slug content_type slug %}"
|
||||||
|
|||||||
@ -39,7 +39,6 @@
|
|||||||
<div class="col-12 col-md-auto">
|
<div class="col-12 col-md-auto">
|
||||||
<h3 class="mb-0">{{ _("Lead Details") }}</h3>
|
<h3 class="mb-0">{{ _("Lead Details") }}</h3>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
@ -47,7 +46,6 @@
|
|||||||
<div class="col-md-5 col-lg-5 col-xl-4">
|
<div class="col-md-5 col-lg-5 col-xl-4">
|
||||||
<div class="sticky-leads-sidebar">
|
<div class="sticky-leads-sidebar">
|
||||||
<div class="lead-details" data-breakpoint="md">
|
<div class="lead-details" data-breakpoint="md">
|
||||||
|
|
||||||
<div class="card mb-2">
|
<div class="card mb-2">
|
||||||
<div class="card-body">
|
<div class="card-body">
|
||||||
<div class="row align-items-center g-3 text-center text-xxl-start">
|
<div class="row align-items-center g-3 text-center text-xxl-start">
|
||||||
@ -93,7 +91,8 @@
|
|||||||
</div>
|
</div>
|
||||||
<div class="card mb-2">
|
<div class="card mb-2">
|
||||||
<div class="card-body">
|
<div class="card-body">
|
||||||
<div id="assignedTo" class="row align-items-center g-3 text-center text-xxl-start">
|
<div id="assignedTo"
|
||||||
|
class="row align-items-center g-3 text-center text-xxl-start">
|
||||||
<div class="col-6 col-sm-auto d-flex flex-column align-items-center text-center">
|
<div class="col-6 col-sm-auto d-flex flex-column align-items-center text-center">
|
||||||
<h5 class="fw-bolder mb-2 text-body-highlight">{{ _("Assigned To") }}</h5>
|
<h5 class="fw-bolder mb-2 text-body-highlight">{{ _("Assigned To") }}</h5>
|
||||||
<div class="d-flex align-items-center">
|
<div class="d-flex align-items-center">
|
||||||
@ -295,9 +294,7 @@
|
|||||||
action="{% url 'lead_transfer' request.dealer.slug lead.slug %}"
|
action="{% url 'lead_transfer' request.dealer.slug lead.slug %}"
|
||||||
hx-select-oob="#assignedTo:outerHTML,#toast-container:outerHTML"
|
hx-select-oob="#assignedTo:outerHTML,#toast-container:outerHTML"
|
||||||
hx-swap="none"
|
hx-swap="none"
|
||||||
hx-on::after-request="{
|
hx-on::after-request="{ resetSubmitButton(document.querySelector('#exampleModal button[type=submit]')); $('#exampleModal').modal('hide');}"
|
||||||
resetSubmitButton(document.querySelector('#exampleModal button[type=submit]'));
|
|
||||||
$('#exampleModal').modal('hide');}"
|
|
||||||
method="post">
|
method="post">
|
||||||
{% csrf_token %}
|
{% csrf_token %}
|
||||||
<div class="modal-header">
|
<div class="modal-header">
|
||||||
@ -488,7 +485,7 @@
|
|||||||
data-url="{% url 'update_note' request.dealer.slug note.pk %}"
|
data-url="{% url 'update_note' request.dealer.slug note.pk %}"
|
||||||
data-bs-toggle="modal"
|
data-bs-toggle="modal"
|
||||||
data-bs-target="#noteModal"
|
data-bs-target="#noteModal"
|
||||||
data-note-title="{{ _('Update') }}">
|
data-note-title="{{ _("Update") }}">
|
||||||
<i class='fas fa-pen-square text-primary ms-2'></i>
|
<i class='fas fa-pen-square text-primary ms-2'></i>
|
||||||
{{ _("Update") }}
|
{{ _("Update") }}
|
||||||
</a>
|
</a>
|
||||||
@ -528,8 +525,7 @@
|
|||||||
hx-get="{% url 'send_lead_email' request.dealer.slug lead.slug %}"
|
hx-get="{% url 'send_lead_email' request.dealer.slug lead.slug %}"
|
||||||
hx-target="#emailModalBody"
|
hx-target="#emailModalBody"
|
||||||
hx-select=".email-form"
|
hx-select=".email-form"
|
||||||
hx-swap="innerHTML"
|
hx-swap="innerHTML">
|
||||||
>
|
|
||||||
<span class="fas fa-plus me-1"></span>{{ _("Send Email") }}
|
<span class="fas fa-plus me-1"></span>{{ _("Send Email") }}
|
||||||
</button>
|
</button>
|
||||||
{% endif %}
|
{% endif %}
|
||||||
@ -807,7 +803,6 @@
|
|||||||
href="{% url 'appointment:get_user_appointments' %}"> <span class="me-2 text-body align-bottom" data-feather="calendar"></span>{{ _("View in Calendar") }}
|
href="{% url 'appointment:get_user_appointments' %}"> <span class="me-2 text-body align-bottom" data-feather="calendar"></span>{{ _("View in Calendar") }}
|
||||||
</a>
|
</a>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
@ -821,8 +816,8 @@
|
|||||||
{% include "components/note_modal.html" with content_type="lead" slug=lead.slug %}
|
{% include "components/note_modal.html" with content_type="lead" slug=lead.slug %}
|
||||||
<!-- schedule Modal -->
|
<!-- schedule Modal -->
|
||||||
{% include "components/schedule_modal.html" with content_type="lead" slug=lead.slug %}
|
{% include "components/schedule_modal.html" with content_type="lead" slug=lead.slug %}
|
||||||
{% endblock content %}
|
{% endblock content %}
|
||||||
{% block customJS %}
|
{% block customJS %}
|
||||||
<script>
|
<script>
|
||||||
function reset_form() {
|
function reset_form() {
|
||||||
document.querySelector('#id_note').value = ""
|
document.querySelector('#id_note').value = ""
|
||||||
@ -878,5 +873,5 @@
|
|||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
</script>
|
</script>
|
||||||
{% endblock customJS %}
|
{% endblock customJS %}
|
||||||
|
|||||||
@ -1,6 +1,5 @@
|
|||||||
{% extends 'base.html' %}
|
{% extends 'base.html' %}
|
||||||
{% load i18n static crispy_forms_filters %}
|
{% load i18n static crispy_forms_filters %}
|
||||||
|
|
||||||
{% block title %}
|
{% block title %}
|
||||||
{% if object %}
|
{% if object %}
|
||||||
{% trans 'Update Lead' %}
|
{% trans 'Update Lead' %}
|
||||||
@ -8,7 +7,6 @@
|
|||||||
{% trans 'Add New Lead' %}
|
{% trans 'Add New Lead' %}
|
||||||
{% endif %}
|
{% endif %}
|
||||||
{% endblock %}
|
{% endblock %}
|
||||||
|
|
||||||
{% block customcss %}
|
{% block customcss %}
|
||||||
<style>
|
<style>
|
||||||
.htmx-indicator{
|
.htmx-indicator{
|
||||||
@ -30,9 +28,8 @@
|
|||||||
}
|
}
|
||||||
</style>
|
</style>
|
||||||
{% endblock customcss %}
|
{% endblock customcss %}
|
||||||
|
|
||||||
{% block content %}
|
{% block content %}
|
||||||
<main class="d-flex align-items-center justify-content-center min-vh-100 py-5">
|
<main class="d-flex align-items-center justify-content-center min-vh-100 py-5">
|
||||||
<div class="col-md-8">
|
<div class="col-md-8">
|
||||||
<div class="card shadow-lg border-0 rounded-4 overflow-hidden animate__animated animate__fadeInUp">
|
<div class="card shadow-lg border-0 rounded-4 overflow-hidden animate__animated animate__fadeInUp">
|
||||||
<div class="card-header bg-gradient py-4 border-0 rounded-top-4">
|
<div class="card-header bg-gradient py-4 border-0 rounded-top-4">
|
||||||
@ -50,14 +47,14 @@
|
|||||||
<form class="form" method="post" enctype="multipart/form-data">
|
<form class="form" method="post" enctype="multipart/form-data">
|
||||||
{% csrf_token %}
|
{% csrf_token %}
|
||||||
{{ form|crispy }}
|
{{ form|crispy }}
|
||||||
|
|
||||||
<hr class="my-4">
|
<hr class="my-4">
|
||||||
<div class="d-grid gap-2 d-md-flex justify-content-md-center mt-3">
|
<div class="d-grid gap-2 d-md-flex justify-content-md-center mt-3">
|
||||||
<button class="btn btn-phoenix-primary btn-lg me-md-2" type="submit">
|
<button class="btn btn-phoenix-primary btn-lg me-md-2" type="submit">
|
||||||
<i class="fa-solid fa-floppy-disk me-1"></i>
|
<i class="fa-solid fa-floppy-disk me-1"></i>
|
||||||
{% trans "Save" %}
|
{% trans "Save" %}
|
||||||
</button>
|
</button>
|
||||||
<a href="{% url 'lead_list' request.dealer.slug %}" class="btn btn-phoenix-secondary btn-lg">
|
<a href="{% url 'lead_list' request.dealer.slug %}"
|
||||||
|
class="btn btn-phoenix-secondary btn-lg">
|
||||||
<i class="fa-solid fa-ban me-1"></i>
|
<i class="fa-solid fa-ban me-1"></i>
|
||||||
{% trans "Cancel" %}
|
{% trans "Cancel" %}
|
||||||
</a>
|
</a>
|
||||||
@ -66,5 +63,5 @@
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</main>
|
</main>
|
||||||
{% endblock %}
|
{% endblock %}
|
||||||
@ -1,12 +1,10 @@
|
|||||||
|
|
||||||
{% extends 'base.html' %}
|
{% extends 'base.html' %}
|
||||||
{% load i18n static humanize %}
|
{% load i18n static humanize %}
|
||||||
{% block title %}
|
{% block title %}
|
||||||
{{ _("Leads") |capfirst }}
|
{{ _("Leads") |capfirst }}
|
||||||
{% endblock title %}
|
{% endblock title %}
|
||||||
{% block content %}
|
{% block content %}
|
||||||
|
{% if page_obj.object_list or request.GET.q %}
|
||||||
{% if page_obj.object_list or request.GET.q%}
|
|
||||||
<div class="row g-3 mt-4 mb-4">
|
<div class="row g-3 mt-4 mb-4">
|
||||||
<h2 class="mb-2">
|
<h2 class="mb-2">
|
||||||
{{ _("Leads") |capfirst }}
|
{{ _("Leads") |capfirst }}
|
||||||
@ -14,7 +12,6 @@
|
|||||||
</h2>
|
</h2>
|
||||||
<!-- Action Tracking Modal -->
|
<!-- Action Tracking Modal -->
|
||||||
{% comment %} {% include "crm/leads/partials/update_action.html" %} {% endcomment %}
|
{% comment %} {% include "crm/leads/partials/update_action.html" %} {% endcomment %}
|
||||||
|
|
||||||
<div class="row g-3 justify-content-between mb-4">
|
<div class="row g-3 justify-content-between mb-4">
|
||||||
<div class="col-auto">
|
<div class="col-auto">
|
||||||
<div class="d-md-flex justify-content-between">
|
<div class="d-md-flex justify-content-between">
|
||||||
@ -30,10 +27,9 @@
|
|||||||
<div class="d-flex">{% include 'partials/search_box.html' %}</div>
|
<div class="d-flex">{% include 'partials/search_box.html' %}</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class="row g-3">
|
<div class="row g-3">
|
||||||
<div class="col-12">
|
<div class="col-12">
|
||||||
{% if page_obj.object_list or request.GET.q%}
|
{% if page_obj.object_list or request.GET.q %}
|
||||||
<div class="table-responsive scrollbar mx-n1 px-1">
|
<div class="table-responsive scrollbar mx-n1 px-1">
|
||||||
<table class="table align-items-center table-flush table-hover">
|
<table class="table align-items-center table-flush table-hover">
|
||||||
<thead>
|
<thead>
|
||||||
@ -206,7 +202,6 @@
|
|||||||
</small>
|
</small>
|
||||||
</div>
|
</div>
|
||||||
</td>
|
</td>
|
||||||
|
|
||||||
<td class="align-middle white-space-nowrap text-end">
|
<td class="align-middle white-space-nowrap text-end">
|
||||||
{% if user == lead.staff.user or request.is_dealer %}
|
{% if user == lead.staff.user or request.is_dealer %}
|
||||||
<div class="btn-reveal-trigger position-static">
|
<div class="btn-reveal-trigger position-static">
|
||||||
@ -224,8 +219,7 @@
|
|||||||
<a href="{% url 'lead_update' request.dealer.slug lead.slug %}"
|
<a href="{% url 'lead_update' request.dealer.slug lead.slug %}"
|
||||||
class="dropdown-item text-success-dark">{% trans "Edit" %}</a>
|
class="dropdown-item text-success-dark">{% trans "Edit" %}</a>
|
||||||
{% endif %}
|
{% endif %}
|
||||||
{% if perms.inventory.change_lead %}
|
{% if perms.inventory.change_lead %}{% endif %}
|
||||||
{% endif %}
|
|
||||||
{% if not lead.opportunity %}
|
{% if not lead.opportunity %}
|
||||||
{% if perms.inventory.add_opportunity %}
|
{% if perms.inventory.add_opportunity %}
|
||||||
<a href="{% url 'lead_opportunity_create' request.dealer.slug lead.slug %}"
|
<a href="{% url 'lead_opportunity_create' request.dealer.slug lead.slug %}"
|
||||||
@ -258,13 +252,12 @@
|
|||||||
<div class="d-flex">{% include 'partials/pagination.html' %}</div>
|
<div class="d-flex">{% include 'partials/pagination.html' %}</div>
|
||||||
</div>
|
</div>
|
||||||
{% endif %}
|
{% endif %}
|
||||||
|
|
||||||
{% endif %}
|
{% endif %}
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
{% else %}
|
{% else %}
|
||||||
{% url 'lead_create' request.dealer.slug as create_lead_url %}
|
{% url 'lead_create' request.dealer.slug as create_lead_url %}
|
||||||
{% include "empty-illustration-page.html" with value="lead" url=create_lead_url %}
|
{% include "empty-illustration-page.html" with value="lead" url=create_lead_url %}
|
||||||
{% endif %}
|
{% endif %}
|
||||||
{% endblock %}
|
{% endblock %}
|
||||||
|
|||||||
@ -65,8 +65,7 @@
|
|||||||
</style>
|
</style>
|
||||||
{% endblock customCSS %}
|
{% endblock customCSS %}
|
||||||
{% block content %}
|
{% block content %}
|
||||||
|
{% if leads %}
|
||||||
{% if leads %}
|
|
||||||
<div class="container-fluid my-4">
|
<div class="container-fluid my-4">
|
||||||
<div class="row justify-content-center">
|
<div class="row justify-content-center">
|
||||||
<div class="col">
|
<div class="col">
|
||||||
@ -170,8 +169,6 @@
|
|||||||
{% endfor %}
|
{% endfor %}
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@ -27,7 +27,7 @@
|
|||||||
<h5 class="mb-0">مرحبًا</h5>
|
<h5 class="mb-0">مرحبًا</h5>
|
||||||
<div>
|
<div>
|
||||||
<button class="btn btn-phoenix-secondary dropdown-toggle"
|
<button class="btn btn-phoenix-secondary dropdown-toggle"
|
||||||
data-bs-toggle="dropdown">الصفحة الرئيسية لـ </button>
|
data-bs-toggle="dropdown">الصفحة الرئيسية لـ</button>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<!-- Main Row -->
|
<!-- Main Row -->
|
||||||
|
|||||||
@ -16,10 +16,7 @@
|
|||||||
action="{% url 'update_lead_actions' request.dealer.slug %}"
|
action="{% url 'update_lead_actions' request.dealer.slug %}"
|
||||||
hx-select-oob="#currentStage:outerHTML,#leadStatus:outerHTML,#toast-container:outerHTML"
|
hx-select-oob="#currentStage:outerHTML,#leadStatus:outerHTML,#toast-container:outerHTML"
|
||||||
hx-swap="none"
|
hx-swap="none"
|
||||||
hx-on::after-request="{
|
hx-on::after-request="{ resetSubmitButton(document.querySelector('#actionTrackingForm button[type=submit]')); $('#actionTrackingModal').modal('hide'); }"
|
||||||
resetSubmitButton(document.querySelector('#actionTrackingForm button[type=submit]'));
|
|
||||||
$('#actionTrackingModal').modal('hide');
|
|
||||||
}"
|
|
||||||
method="post">
|
method="post">
|
||||||
<div class="modal-body">
|
<div class="modal-body">
|
||||||
{% csrf_token %}
|
{% csrf_token %}
|
||||||
|
|||||||
@ -3,7 +3,9 @@
|
|||||||
<div class="content">
|
<div class="content">
|
||||||
<h2 class="mb-5">{{ _("Notifications") }}</h2>
|
<h2 class="mb-5">{{ _("Notifications") }}</h2>
|
||||||
<div class="d-flex justify-content-end mb-3">
|
<div class="d-flex justify-content-end mb-3">
|
||||||
<a href="{% url 'mark_all_notifications_as_read' %}" hx-select-oob="#toast-container:outerHTML" class="btn btn-phoenix-primary"><i class="far fa-envelope fs-8 me-2"></i>{{ _("Mark all as read") }}</a>
|
<a href="{% url 'mark_all_notifications_as_read' %}"
|
||||||
|
hx-select-oob="#toast-container:outerHTML"
|
||||||
|
class="btn btn-phoenix-primary"><i class="far fa-envelope fs-8 me-2"></i>{{ _("Mark all as read") }}</a>
|
||||||
</div>
|
</div>
|
||||||
{% if notifications %}
|
{% if notifications %}
|
||||||
<div class="mx-n4 mx-lg-n6 mb-5 border-bottom">
|
<div class="mx-n4 mx-lg-n6 mb-5 border-bottom">
|
||||||
|
|||||||
@ -40,7 +40,8 @@
|
|||||||
href="{% url 'update_opportunity' request.dealer.slug opportunity.slug %}">Update Opportunity</a>
|
href="{% url 'update_opportunity' request.dealer.slug opportunity.slug %}">Update Opportunity</a>
|
||||||
</li>
|
</li>
|
||||||
<li>
|
<li>
|
||||||
<a class="dropdown-item" type="button"
|
<a class="dropdown-item"
|
||||||
|
type="button"
|
||||||
data-bs-toggle="modal"
|
data-bs-toggle="modal"
|
||||||
data-bs-target="#updateStageModal">Update Stage</a>
|
data-bs-target="#updateStageModal">Update Stage</a>
|
||||||
</li>
|
</li>
|
||||||
@ -835,8 +836,7 @@
|
|||||||
hx-get="{% url 'send_lead_email' request.dealer.slug opportunity.lead.slug %}"
|
hx-get="{% url 'send_lead_email' request.dealer.slug opportunity.lead.slug %}"
|
||||||
hx-target="#emailModalBody"
|
hx-target="#emailModalBody"
|
||||||
hx-select=".email-form"
|
hx-select=".email-form"
|
||||||
hx-swap="innerHTML"
|
hx-swap="innerHTML">
|
||||||
>
|
|
||||||
<span class="fas fa-plus me-1"></span>{{ _("Send Email") }}
|
<span class="fas fa-plus me-1"></span>{{ _("Send Email") }}
|
||||||
</button>
|
</button>
|
||||||
{% endif %}
|
{% endif %}
|
||||||
@ -858,7 +858,6 @@
|
|||||||
</li>
|
</li>
|
||||||
</ul>
|
</ul>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class="tab-content" id="profileTabContent">
|
<div class="tab-content" id="profileTabContent">
|
||||||
<div class="tab-pane fade show active"
|
<div class="tab-pane fade show active"
|
||||||
id="tab-mail"
|
id="tab-mail"
|
||||||
@ -871,7 +870,6 @@
|
|||||||
<table class="table fs-9 mb-0">
|
<table class="table fs-9 mb-0">
|
||||||
<thead>
|
<thead>
|
||||||
<tr>
|
<tr>
|
||||||
|
|
||||||
<th class="sort white-space-nowrap align-middle pe-3 ps-0 text-uppercase"
|
<th class="sort white-space-nowrap align-middle pe-3 ps-0 text-uppercase"
|
||||||
scope="col"
|
scope="col"
|
||||||
data-sort="subject"
|
data-sort="subject"
|
||||||
@ -896,14 +894,12 @@
|
|||||||
<tbody class="list" id="all-email-table-body">
|
<tbody class="list" id="all-email-table-body">
|
||||||
{% for email in opportunity.lead.get_emails %}
|
{% for email in opportunity.lead.get_emails %}
|
||||||
<tr class="hover-actions-trigger btn-reveal-trigger position-static">
|
<tr class="hover-actions-trigger btn-reveal-trigger position-static">
|
||||||
|
|
||||||
<td class="subject order align-middle white-space-nowrap py-2 ps-0">
|
<td class="subject order align-middle white-space-nowrap py-2 ps-0">
|
||||||
<a class="fw-semibold text-primary" href="#!">{{ email.subject }}</a>
|
<a class="fw-semibold text-primary" href="#!">{{ email.subject }}</a>
|
||||||
<div class="fs-10 d-block">{{ email.to_email }}</div>
|
<div class="fs-10 d-block">{{ email.to_email }}</div>
|
||||||
</td>
|
</td>
|
||||||
<td class="sent align-middle white-space-nowrap text-start fw-bold text-body-tertiary py-2">{{ email.from_email }}</td>
|
<td class="sent align-middle white-space-nowrap text-start fw-bold text-body-tertiary py-2">{{ email.from_email }}</td>
|
||||||
<td class="date align-middle white-space-nowrap text-body py-2">{{ email.created }}</td>
|
<td class="date align-middle white-space-nowrap text-body py-2">{{ email.created }}</td>
|
||||||
|
|
||||||
<td class="status align-middle fw-semibold text-end py-2">
|
<td class="status align-middle fw-semibold text-end py-2">
|
||||||
<span class="badge badge-phoenix fs-10 badge-phoenix-success">sent</span>
|
<span class="badge badge-phoenix fs-10 badge-phoenix-success">sent</span>
|
||||||
</td>
|
</td>
|
||||||
@ -1097,7 +1093,10 @@
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div class="modal fade" id="updateStageModal" tabindex="-1" aria-hidden="true">
|
<div class="modal fade"
|
||||||
|
id="updateStageModal"
|
||||||
|
tabindex="-1"
|
||||||
|
aria-hidden="true">
|
||||||
<div class="modal-dialog">
|
<div class="modal-dialog">
|
||||||
<div class="modal-content">
|
<div class="modal-content">
|
||||||
<form method="post"
|
<form method="post"
|
||||||
@ -1113,9 +1112,7 @@
|
|||||||
data-bs-dismiss="modal"
|
data-bs-dismiss="modal"
|
||||||
aria-label="Close"></button>
|
aria-label="Close"></button>
|
||||||
</div>
|
</div>
|
||||||
<div class="modal-body">
|
<div class="modal-body">{{ stage_form|crispy }}</div>
|
||||||
{{ stage_form|crispy }}
|
|
||||||
</div>
|
|
||||||
<div class="modal-footer">
|
<div class="modal-footer">
|
||||||
<button class="btn btn-phoenix-primary" type="submit">{{ _("Save") }}</button>
|
<button class="btn btn-phoenix-primary" type="submit">{{ _("Save") }}</button>
|
||||||
<button class="btn btn-phoenix-secondary"
|
<button class="btn btn-phoenix-secondary"
|
||||||
@ -1125,9 +1122,7 @@
|
|||||||
</form>
|
</form>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
|
||||||
{% include 'modal/delete_modal.html' %}
|
{% include 'modal/delete_modal.html' %}
|
||||||
<!-- email Modal -->
|
<!-- email Modal -->
|
||||||
{% include "components/email_modal.html" %}
|
{% include "components/email_modal.html" %}
|
||||||
@ -1138,10 +1133,8 @@
|
|||||||
<!-- schedule Modal -->
|
<!-- schedule Modal -->
|
||||||
{% include "components/schedule_modal.html" with content_type="opportunity" slug=opportunity.slug %}
|
{% include "components/schedule_modal.html" with content_type="opportunity" slug=opportunity.slug %}
|
||||||
{% endblock %}
|
{% endblock %}
|
||||||
|
|
||||||
{% block customJS %}
|
{% block customJS %}
|
||||||
<script>
|
<script>
|
||||||
|
|
||||||
document.body.addEventListener('htmx:afterSwap', function(evt) {
|
document.body.addEventListener('htmx:afterSwap', function(evt) {
|
||||||
if (evt.detail.target.id === 'main_content') {
|
if (evt.detail.target.id === 'main_content') {
|
||||||
var modal = bootstrap.Modal.getInstance(document.getElementById('exampleModal'));
|
var modal = bootstrap.Modal.getInstance(document.getElementById('exampleModal'));
|
||||||
@ -1161,5 +1154,5 @@
|
|||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
</script>
|
</script>
|
||||||
{% endblock customJS %}
|
{% endblock customJS %}
|
||||||
@ -21,25 +21,19 @@
|
|||||||
</h2>
|
</h2>
|
||||||
</div>
|
</div>
|
||||||
<div class="col-auto">
|
<div class="col-auto">
|
||||||
<a href="{% url 'opportunity_list' request.dealer.slug %}" class="btn btn-phoenix-secondary">
|
<a href="{% url 'opportunity_list' request.dealer.slug %}"
|
||||||
|
class="btn btn-phoenix-secondary">
|
||||||
<span class="fas fa-arrow-left me-2"></span>{% trans "Back to list" %}
|
<span class="fas fa-arrow-left me-2"></span>{% trans "Back to list" %}
|
||||||
</a>
|
</a>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class="row g-3">
|
<div class="row g-3">
|
||||||
<div class="col-lg-8">
|
<div class="col-lg-8">
|
||||||
<div class="card">
|
<div class="card">
|
||||||
<div class="card-body p-4 p-sm-5">
|
<div class="card-body p-4 p-sm-5">
|
||||||
<form method="post" enctype="multipart/form-data">
|
<form method="post" enctype="multipart/form-data">
|
||||||
{% csrf_token %}
|
{% csrf_token %}
|
||||||
|
{% if form.non_field_errors %}<div class="alert alert-danger">{{ form.non_field_errors }}</div>{% endif %}
|
||||||
{% if form.non_field_errors %}
|
|
||||||
<div class="alert alert-danger">
|
|
||||||
{{ form.non_field_errors }}
|
|
||||||
</div>
|
|
||||||
{% endif %}
|
|
||||||
|
|
||||||
<!-- Lead Field -->
|
<!-- Lead Field -->
|
||||||
<div class="mb-4">
|
<div class="mb-4">
|
||||||
<label class="form-label" for="{{ form.lead.id_for_label }}">
|
<label class="form-label" for="{{ form.lead.id_for_label }}">
|
||||||
@ -47,13 +41,8 @@
|
|||||||
<span class="text-danger">*</span>
|
<span class="text-danger">*</span>
|
||||||
</label>
|
</label>
|
||||||
{{ form.lead|add_class:"form-control" }}
|
{{ form.lead|add_class:"form-control" }}
|
||||||
{% if form.lead.errors %}
|
{% if form.lead.errors %}<div class="invalid-feedback d-block">{{ form.lead.errors }}</div>{% endif %}
|
||||||
<div class="invalid-feedback d-block">
|
|
||||||
{{ form.lead.errors }}
|
|
||||||
</div>
|
</div>
|
||||||
{% endif %}
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<!-- Car Field -->
|
<!-- Car Field -->
|
||||||
<div class="mb-4">
|
<div class="mb-4">
|
||||||
<label class="form-label" for="{{ form.car.id_for_label }}">
|
<label class="form-label" for="{{ form.car.id_for_label }}">
|
||||||
@ -61,13 +50,8 @@
|
|||||||
<span class="text-danger">*</span>
|
<span class="text-danger">*</span>
|
||||||
</label>
|
</label>
|
||||||
{{ form.car|add_class:"form-control" }}
|
{{ form.car|add_class:"form-control" }}
|
||||||
{% if form.car.errors %}
|
{% if form.car.errors %}<div class="invalid-feedback d-block">{{ form.car.errors }}</div>{% endif %}
|
||||||
<div class="invalid-feedback d-block">
|
|
||||||
{{ form.car.errors }}
|
|
||||||
</div>
|
</div>
|
||||||
{% endif %}
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<!-- Stage Field -->
|
<!-- Stage Field -->
|
||||||
<div class="mb-4">
|
<div class="mb-4">
|
||||||
<label class="form-label" for="{{ form.stage.id_for_label }}">
|
<label class="form-label" for="{{ form.stage.id_for_label }}">
|
||||||
@ -75,11 +59,7 @@
|
|||||||
<span class="text-danger">*</span>
|
<span class="text-danger">*</span>
|
||||||
</label>
|
</label>
|
||||||
{{ form.stage|add_class:"form-control" }}
|
{{ form.stage|add_class:"form-control" }}
|
||||||
{% if form.stage.errors %}
|
{% if form.stage.errors %}<div class="invalid-feedback d-block">{{ form.stage.errors }}</div>{% endif %}
|
||||||
<div class="invalid-feedback d-block">
|
|
||||||
{{ form.stage.errors }}
|
|
||||||
</div>
|
|
||||||
{% endif %}
|
|
||||||
</div>
|
</div>
|
||||||
<!-- Amount Field -->
|
<!-- Amount Field -->
|
||||||
<div class="mb-4">
|
<div class="mb-4">
|
||||||
@ -91,13 +71,8 @@
|
|||||||
<span class="input-group-text"><span class="icon-saudi_riyal"></span></span>
|
<span class="input-group-text"><span class="icon-saudi_riyal"></span></span>
|
||||||
{{ form.amount|add_class:"form-control" }}
|
{{ form.amount|add_class:"form-control" }}
|
||||||
</div>
|
</div>
|
||||||
{% if form.amount.errors %}
|
{% if form.amount.errors %}<div class="invalid-feedback d-block">{{ form.amount.errors }}</div>{% endif %}
|
||||||
<div class="invalid-feedback d-block">
|
|
||||||
{{ form.amount.errors }}
|
|
||||||
</div>
|
</div>
|
||||||
{% endif %}
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<!-- Probability Field -->
|
<!-- Probability Field -->
|
||||||
<div class="mb-4">
|
<div class="mb-4">
|
||||||
<label class="form-label" for="{{ form.probability.id_for_label }}">
|
<label class="form-label" for="{{ form.probability.id_for_label }}">
|
||||||
@ -108,54 +83,41 @@
|
|||||||
<input type="range"
|
<input type="range"
|
||||||
name="{{ form.probability.name }}"
|
name="{{ form.probability.name }}"
|
||||||
id="{{ form.probability.id_for_label }}"
|
id="{{ form.probability.id_for_label }}"
|
||||||
min="0" max="100" step="1"
|
min="0"
|
||||||
|
max="100"
|
||||||
|
step="1"
|
||||||
value="{{ form.probability.value|default:'50' }}"
|
value="{{ form.probability.value|default:'50' }}"
|
||||||
class="form-control form-range"
|
class="form-control form-range"
|
||||||
oninput="updateProbabilityValue(this.value)">
|
oninput="updateProbabilityValue(this.value)">
|
||||||
<span id="probability-value" class="badge badge-phoenix fs-6 badge-phoenix-primary">
|
<span id="probability-value"
|
||||||
|
class="badge badge-phoenix fs-6 badge-phoenix-primary">
|
||||||
{{ form.probability.value|default:'50' }}%
|
{{ form.probability.value|default:'50' }}%
|
||||||
</span>
|
</span>
|
||||||
</div>
|
</div>
|
||||||
{% if form.probability.errors %}
|
{% if form.probability.errors %}<div class="invalid-feedback d-block">{{ form.probability.errors }}</div>{% endif %}
|
||||||
<div class="invalid-feedback d-block">
|
|
||||||
{{ form.probability.errors }}
|
|
||||||
</div>
|
</div>
|
||||||
{% endif %}
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<!-- Expected Revenue -->
|
<!-- Expected Revenue -->
|
||||||
<div class="mb-4">
|
<div class="mb-4">
|
||||||
<label class="form-label" for="{{ form.expected_revenue.id_for_label }}">
|
<label class="form-label" for="{{ form.expected_revenue.id_for_label }}">{{ form.expected_revenue.label }}</label>
|
||||||
{{ form.expected_revenue.label }}
|
|
||||||
</label>
|
|
||||||
<div class="input-group">
|
<div class="input-group">
|
||||||
<span class="input-group-text"><span class="icon-saudi_riyal"></span></span>
|
<span class="input-group-text"><span class="icon-saudi_riyal"></span></span>
|
||||||
{{ form.expected_revenue|add_class:"form-control" }}
|
{{ form.expected_revenue|add_class:"form-control" }}
|
||||||
</div>
|
</div>
|
||||||
{% if form.expected_revenue.errors %}
|
{% if form.expected_revenue.errors %}
|
||||||
<div class="invalid-feedback d-block">
|
<div class="invalid-feedback d-block">{{ form.expected_revenue.errors }}</div>
|
||||||
{{ form.expected_revenue.errors }}
|
|
||||||
</div>
|
|
||||||
{% endif %}
|
{% endif %}
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
|
||||||
<!-- Closing Date -->
|
<!-- Closing Date -->
|
||||||
<div class="mb-5">
|
<div class="mb-5">
|
||||||
<label class="form-label" for="{{ form.closing_date.id_for_label }}">
|
<label class="form-label" for="{{ form.closing_date.id_for_label }}">{{ form.closing_date.label }}</label>
|
||||||
{{ form.closing_date.label }}
|
|
||||||
</label>
|
|
||||||
<div class="input-group">
|
<div class="input-group">
|
||||||
{{ form.expected_close_date|add_class:"form-control" }}
|
{{ form.expected_close_date|add_class:"form-control" }}
|
||||||
<span class="input-group-text"><span class="far fa-calendar"></span></span>
|
<span class="input-group-text"><span class="far fa-calendar"></span></span>
|
||||||
</div>
|
</div>
|
||||||
{% if form.expected_close_date.errors %}
|
{% if form.expected_close_date.errors %}
|
||||||
<div class="invalid-feedback d-block">
|
<div class="invalid-feedback d-block">{{ form.expected_close_date.errors }}</div>
|
||||||
{{ form.expected_close_date.errors }}
|
|
||||||
</div>
|
|
||||||
{% endif %}
|
{% endif %}
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<!-- Form Actions -->
|
<!-- Form Actions -->
|
||||||
<div class="d-flex justify-content-end gap-3">
|
<div class="d-flex justify-content-end gap-3">
|
||||||
<button type="reset" class="btn btn-phoenix-danger px-4">
|
<button type="reset" class="btn btn-phoenix-danger px-4">
|
||||||
@ -202,7 +164,6 @@
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<script>
|
<script>
|
||||||
function updateProbabilityValue(value) {
|
function updateProbabilityValue(value) {
|
||||||
const amount = document.getElementById('id_amount');
|
const amount = document.getElementById('id_amount');
|
||||||
|
|||||||
@ -75,7 +75,8 @@
|
|||||||
height: 12px;
|
height: 12px;
|
||||||
width: 12px"></span>{{ opportunity.get_stage_display }}
|
width: 12px"></span>{{ opportunity.get_stage_display }}
|
||||||
</p>
|
</p>
|
||||||
<p class="ms-auto fs-9 text-body-emphasis fw-semibold mb-0 deals-revenue">{{ opportunity.car.total }}</p># TODO : check later
|
<p class="ms-auto fs-9 text-body-emphasis fw-semibold mb-0 deals-revenue">{{ opportunity.car.total }}</p>
|
||||||
|
# TODO : check later
|
||||||
</div>
|
</div>
|
||||||
<div class="deals-company-agent d-flex flex-between-center">
|
<div class="deals-company-agent d-flex flex-between-center">
|
||||||
<div class="d-flex align-items-center">
|
<div class="d-flex align-items-center">
|
||||||
|
|||||||
@ -5,8 +5,7 @@
|
|||||||
{{ _("Opportunities") }}
|
{{ _("Opportunities") }}
|
||||||
{% endblock title %}
|
{% endblock title %}
|
||||||
{% block content %}
|
{% block content %}
|
||||||
|
{% if opportunities or request.GET.q %}
|
||||||
{% if opportunities or request.GET.q%}
|
|
||||||
<div class="row g-3 mt-4">
|
<div class="row g-3 mt-4">
|
||||||
<div class="row g-3 justify-content-between mb-4">
|
<div class="row g-3 justify-content-between mb-4">
|
||||||
<div class="col-auto">
|
<div class="col-auto">
|
||||||
@ -34,38 +33,34 @@
|
|||||||
<div class="d-flex flex-column flex-lg-row align-items-start align-items-lg-center gap-3 w-100"
|
<div class="d-flex flex-column flex-lg-row align-items-start align-items-lg-center gap-3 w-100"
|
||||||
id="filter-container">
|
id="filter-container">
|
||||||
<!-- Search Input - Wider and properly aligned -->
|
<!-- Search Input - Wider and properly aligned -->
|
||||||
<div class="search-box position-relative flex-grow-1 me-2" style="min-width: 200px">
|
<div class="search-box position-relative flex-grow-1 me-2"
|
||||||
<form class="position-relative show" id="search-form"
|
style="min-width: 200px">
|
||||||
|
<form class="position-relative show"
|
||||||
|
id="search-form"
|
||||||
hx-get=""
|
hx-get=""
|
||||||
hx-boost="false"
|
hx-boost="false"
|
||||||
hx-trigger="keyup changed delay:500ms, search">
|
hx-trigger="keyup changed delay:500ms, search">
|
||||||
|
|
||||||
<input name="q"
|
<input name="q"
|
||||||
id="search-input"
|
id="search-input"
|
||||||
class="form-control form-control-sm search-input search"
|
class="form-control form-control-sm search-input search"
|
||||||
type="search"
|
type="search"
|
||||||
aria-label="Search"
|
aria-label="Search"
|
||||||
placeholder="{{ _("Search") }}..."
|
placeholder="{{ _("Search") }}..."
|
||||||
value="{{ request.GET.q}}" />
|
value="{{ request.GET.q }}" />
|
||||||
|
|
||||||
<span class="fa fa-magnifying-glass search-box-icon"></span>
|
<span class="fa fa-magnifying-glass search-box-icon"></span>
|
||||||
|
|
||||||
{% if request.GET.q %}
|
{% if request.GET.q %}
|
||||||
<button type="button"
|
<button type="button"
|
||||||
class="btn-close position-absolute end-0 top-50 translate-middle cursor-pointer shadow-none"
|
class="btn-close position-absolute end-0 top-50 translate-middle cursor-pointer shadow-none"
|
||||||
id="clear-search"
|
id="clear-search"
|
||||||
aria-label="Clear Search"></button>
|
aria-label="Clear Search"></button>
|
||||||
{% endif %}
|
{% endif %}
|
||||||
|
|
||||||
</form>
|
</form>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<!-- Filter Dropdowns - Aligned in a row -->
|
<!-- Filter Dropdowns - Aligned in a row -->
|
||||||
<div class="d-flex flex-column flex-sm-row gap-3 w-100"
|
<div class="d-flex flex-column flex-sm-row gap-3 w-100"
|
||||||
style="max-width: 400px">
|
style="max-width: 400px">
|
||||||
<!-- Stage Filter -->
|
<!-- Stage Filter -->
|
||||||
<!-- Stage Filter -->
|
<!-- Stage Filter -->
|
||||||
|
|
||||||
<div class="flex-grow-1">
|
<div class="flex-grow-1">
|
||||||
<select class="form-select"
|
<select class="form-select"
|
||||||
name="stage"
|
name="stage"
|
||||||
@ -78,7 +73,9 @@
|
|||||||
<option value="">{% trans "All Stages" %}</option>
|
<option value="">{% trans "All Stages" %}</option>
|
||||||
{% for value, label in stage_choices %}
|
{% for value, label in stage_choices %}
|
||||||
<option value="{{ value }}"
|
<option value="{{ value }}"
|
||||||
{% if request.GET.stage == value %}selected{% endif %}>{{ label }}</option>
|
{% if request.GET.stage == value %}selected{% endif %}>
|
||||||
|
{{ label }}
|
||||||
|
</option>
|
||||||
{% endfor %}
|
{% endfor %}
|
||||||
</select>
|
</select>
|
||||||
</div>
|
</div>
|
||||||
@ -93,19 +90,23 @@
|
|||||||
hx-swap="outerHTML"
|
hx-swap="outerHTML"
|
||||||
hx-include="#filter-container input, #filter-container select">
|
hx-include="#filter-container input, #filter-container select">
|
||||||
<option value="newest"
|
<option value="newest"
|
||||||
{% if request.GET.sort == 'newest' %}selected{% endif %}>{% trans "Newest First" %}</option>
|
{% if request.GET.sort == 'newest' %}selected{% endif %}>
|
||||||
|
{% trans "Newest First" %}
|
||||||
|
</option>
|
||||||
<option value="highest"
|
<option value="highest"
|
||||||
{% if request.GET.sort == 'highest' %}selected{% endif %}>{% trans "Highest Value" %}</option>
|
{% if request.GET.sort == 'highest' %}selected{% endif %}>
|
||||||
|
{% trans "Highest Value" %}
|
||||||
|
</option>
|
||||||
<option value="closing"
|
<option value="closing"
|
||||||
{% if request.GET.sort == 'closing' %}selected{% endif %}>{% trans "Earliest Close Date" %}</option>
|
{% if request.GET.sort == 'closing' %}selected{% endif %}>
|
||||||
|
{% trans "Earliest Close Date" %}
|
||||||
|
</option>
|
||||||
</select>
|
</select>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
</div>
|
</div>
|
||||||
<div id="opportunities-grid" class="row g-4 px-2 px-lg-4 mt-1 mb-4">
|
<div id="opportunities-grid" class="row g-4 px-2 px-lg-4 mt-1 mb-4">
|
||||||
{% include 'crm/opportunities/partials/opportunity_grid.html' %}
|
{% include 'crm/opportunities/partials/opportunity_grid.html' %}
|
||||||
@ -115,10 +116,10 @@
|
|||||||
<div class="d-flex">{% include 'partials/pagination.html' %}</div>
|
<div class="d-flex">{% include 'partials/pagination.html' %}</div>
|
||||||
</div>
|
</div>
|
||||||
{% endif %}
|
{% endif %}
|
||||||
{% else %}
|
{% else %}
|
||||||
{% url 'opportunity_create' request.dealer.slug as create_opportunity_url %}
|
{% url 'opportunity_create' request.dealer.slug as create_opportunity_url %}
|
||||||
{% include "empty-illustration-page.html" with value="opportunity" url=create_opportunity_url %}
|
{% include "empty-illustration-page.html" with value="opportunity" url=create_opportunity_url %}
|
||||||
{% endif %}
|
{% endif %}
|
||||||
{% block customJS %}
|
{% block customJS %}
|
||||||
<script>
|
<script>
|
||||||
document.addEventListener("DOMContentLoaded", function() {
|
document.addEventListener("DOMContentLoaded", function() {
|
||||||
@ -135,6 +136,6 @@
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
</script>
|
</script>
|
||||||
{% endblock %}
|
{% endblock %}
|
||||||
{% endblock %}
|
{% endblock %}
|
||||||
|
|||||||
@ -14,12 +14,7 @@
|
|||||||
{% endblock customCSS %}
|
{% endblock customCSS %}
|
||||||
{% for opportunity in opportunities %}
|
{% for opportunity in opportunities %}
|
||||||
<div class="col-12 col-md-6 col-lg-4 col-xl-3">
|
<div class="col-12 col-md-6 col-lg-4 col-xl-3">
|
||||||
<div class="card h-100
|
<div class="card h-100 {% if opportunity.get_stage_display == 'Closed Won' %} bg-success-soft {% elif opportunity.get_stage_display == 'Closed Lost' %} bg-danger-soft {% endif %}">
|
||||||
{% if opportunity.get_stage_display == 'Closed Won' %}
|
|
||||||
bg-success-soft
|
|
||||||
{% elif opportunity.get_stage_display == 'Closed Lost' %}
|
|
||||||
bg-danger-soft
|
|
||||||
{% endif %}">
|
|
||||||
<div class="card-body">
|
<div class="card-body">
|
||||||
<div class="avatar avatar-xl me-3 mb-3">
|
<div class="avatar avatar-xl me-3 mb-3">
|
||||||
{% if opportunity.car.id_car_make.logo %}
|
{% if opportunity.car.id_car_make.logo %}
|
||||||
@ -53,12 +48,7 @@
|
|||||||
<span class="badge badge-phoenix fs-10 badge-phoenix-secondary">
|
<span class="badge badge-phoenix fs-10 badge-phoenix-secondary">
|
||||||
{% endif %}
|
{% endif %}
|
||||||
{{ opportunity.stage }}</span>
|
{{ opportunity.stage }}</span>
|
||||||
<span class="badge badge-phoenix fs-10
|
<span class="badge badge-phoenix fs-10 {% if opportunity.get_stage_display == 'Won' %} badge-phoenix-success {% elif opportunity.get_stage_display == 'Lost' %} badge-phoenix-danger {% endif %}">
|
||||||
{% if opportunity.get_stage_display == 'Won' %}
|
|
||||||
badge-phoenix-success
|
|
||||||
{% elif opportunity.get_stage_display == 'Lost' %}
|
|
||||||
badge-phoenix-danger
|
|
||||||
{% endif %}">
|
|
||||||
{{ opportunity.get_status_display }}
|
{{ opportunity.get_status_display }}
|
||||||
</span>
|
</span>
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@ -3,7 +3,6 @@
|
|||||||
{% block title %}
|
{% block title %}
|
||||||
{% trans "Car Bulk Upload"|capfirst %}
|
{% trans "Car Bulk Upload"|capfirst %}
|
||||||
{% endblock %}
|
{% endblock %}
|
||||||
|
|
||||||
{% block customCSS %}
|
{% block customCSS %}
|
||||||
<style>
|
<style>
|
||||||
.color-card {
|
.color-card {
|
||||||
@ -74,13 +73,13 @@
|
|||||||
</style>
|
</style>
|
||||||
{% endblock customCSS %}
|
{% endblock customCSS %}
|
||||||
{% block content %}
|
{% block content %}
|
||||||
|
|
||||||
<div class="container mt-4">
|
<div class="container mt-4">
|
||||||
<h2>
|
<h2>
|
||||||
Upload Cars CSV <i class="fa-solid fa-file-csv text-primary"></i>
|
Upload Cars CSV <i class="fa-solid fa-file-csv text-primary"></i>
|
||||||
</h2>
|
</h2>
|
||||||
<div class="d-flex justify-content-end">
|
<div class="d-flex justify-content-end">
|
||||||
<a href="{% static 'sample/cars_sample.csv' %}" class="btn btn-phoenix-primary mt-4">
|
<a href="{% static 'sample/cars_sample.csv' %}"
|
||||||
|
class="btn btn-phoenix-primary mt-4">
|
||||||
<i class="fa-solid fa-file-csv me-2"></i>Download Sample CSV
|
<i class="fa-solid fa-file-csv me-2"></i>Download Sample CSV
|
||||||
</a>
|
</a>
|
||||||
</div>
|
</div>
|
||||||
@ -182,7 +181,8 @@
|
|||||||
<div class="form-text">{{ _("CSV should include columns: vin") }}</div>
|
<div class="form-text">{{ _("CSV should include columns: vin") }}</div>
|
||||||
</div>
|
</div>
|
||||||
<button type="submit" class="btn btn-phoenix-primary mb-2">Upload</button>
|
<button type="submit" class="btn btn-phoenix-primary mb-2">Upload</button>
|
||||||
<a href="{% url 'car_list' request.dealer.slug %}" class="btn btn-phoenix-secondary mb-2">Cancel</a>
|
<a href="{% url 'car_list' request.dealer.slug %}"
|
||||||
|
class="btn btn-phoenix-secondary mb-2">Cancel</a>
|
||||||
</form>
|
</form>
|
||||||
</div>
|
</div>
|
||||||
{% endblock %}
|
{% endblock %}
|
||||||
|
|||||||
@ -1,7 +1,6 @@
|
|||||||
{% extends "base.html" %}
|
{% extends "base.html" %}
|
||||||
{% load i18n static %}
|
{% load i18n static %}
|
||||||
{% load crispy_forms_filters %}
|
{% load crispy_forms_filters %}
|
||||||
|
|
||||||
{% block title %}
|
{% block title %}
|
||||||
{% if object %}
|
{% if object %}
|
||||||
{% trans 'Update Customer' %}
|
{% trans 'Update Customer' %}
|
||||||
@ -9,9 +8,8 @@
|
|||||||
{% trans 'Add New Customer' %}
|
{% trans 'Add New Customer' %}
|
||||||
{% endif %}
|
{% endif %}
|
||||||
{% endblock %}
|
{% endblock %}
|
||||||
|
|
||||||
{% block content %}
|
{% block content %}
|
||||||
<main class="d-flex align-items-center justify-content-center min-vh-100 py-5 ">
|
<main class="d-flex align-items-center justify-content-center min-vh-100 py-5 ">
|
||||||
<div class="col-md-8">
|
<div class="col-md-8">
|
||||||
<div class="card shadow-lg border-0 rounded-4 overflow-hidden animate__animated animate__fadeInUp">
|
<div class="card shadow-lg border-0 rounded-4 overflow-hidden animate__animated animate__fadeInUp">
|
||||||
<div class="card-header bg-gradient py-4 border-0 rounded-top-4">
|
<div class="card-header bg-gradient py-4 border-0 rounded-top-4">
|
||||||
@ -29,25 +27,27 @@
|
|||||||
<form method="post" class="form" enctype="multipart/form-data" novalidate>
|
<form method="post" class="form" enctype="multipart/form-data" novalidate>
|
||||||
{% csrf_token %}
|
{% csrf_token %}
|
||||||
{{ form|crispy }}
|
{{ form|crispy }}
|
||||||
|
|
||||||
{% if form.errors %}
|
{% if form.errors %}
|
||||||
<div class="alert alert-danger mt-4" role="alert">
|
<div class="alert alert-danger mt-4" role="alert">
|
||||||
<h4 class="alert-heading small">{% trans "Please correct the following errors:" %}</h4>
|
<h4 class="alert-heading small">{% trans "Please correct the following errors:" %}</h4>
|
||||||
<ul class="mb-0">
|
<ul class="mb-0">
|
||||||
{% for field, errors in form.errors.items %}
|
{% for field, errors in form.errors.items %}
|
||||||
<li><strong>{{ field|capfirst }}:</strong> {% for error in errors %}{{ error }}{% endfor %}</li>
|
<li>
|
||||||
|
<strong>{{ field|capfirst }}:</strong>
|
||||||
|
{% for error in errors %}{{ error }}{% endfor %}
|
||||||
|
</li>
|
||||||
{% endfor %}
|
{% endfor %}
|
||||||
</ul>
|
</ul>
|
||||||
</div>
|
</div>
|
||||||
{% endif %}
|
{% endif %}
|
||||||
|
|
||||||
<hr class="my-4">
|
<hr class="my-4">
|
||||||
<div class="d-grid gap-2 d-md-flex justify-content-md-center">
|
<div class="d-grid gap-2 d-md-flex justify-content-md-center">
|
||||||
<button class="btn btn-phoenix-primary btn-lg me-md-2" type="submit">
|
<button class="btn btn-phoenix-primary btn-lg me-md-2" type="submit">
|
||||||
<i class="fa-solid fa-floppy-disk me-1"></i>
|
<i class="fa-solid fa-floppy-disk me-1"></i>
|
||||||
{% trans "Save" %}
|
{% trans "Save" %}
|
||||||
</button>
|
</button>
|
||||||
<a href="{% url 'customer_list' request.dealer.slug %}" class="btn btn-phoenix-secondary btn-lg">
|
<a href="{% url 'customer_list' request.dealer.slug %}"
|
||||||
|
class="btn btn-phoenix-secondary btn-lg">
|
||||||
<i class="fa-solid fa-ban me-1"></i>
|
<i class="fa-solid fa-ban me-1"></i>
|
||||||
{% trans "Cancel" %}
|
{% trans "Cancel" %}
|
||||||
</a>
|
</a>
|
||||||
@ -56,5 +56,5 @@
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</main>
|
</main>
|
||||||
{% endblock %}
|
{% endblock %}
|
||||||
@ -6,7 +6,7 @@
|
|||||||
{% endblock title %}
|
{% endblock title %}
|
||||||
{% block vendors %}<a class="nav-link active">{{ _("Customers") |capfirst }}</a>{% endblock %}
|
{% block vendors %}<a class="nav-link active">{{ _("Customers") |capfirst }}</a>{% endblock %}
|
||||||
{% block content %}
|
{% block content %}
|
||||||
{% if customers or request.GET.q %}
|
{% if customers or request.GET.q %}
|
||||||
<div class="row g-3 mt-4">
|
<div class="row g-3 mt-4">
|
||||||
<h2 class="mb-2">
|
<h2 class="mb-2">
|
||||||
{{ _("Customers") |capfirst }}
|
{{ _("Customers") |capfirst }}
|
||||||
@ -27,7 +27,7 @@
|
|||||||
<div class="d-flex">{% include 'partials/search_box.html' %}</div>
|
<div class="d-flex">{% include 'partials/search_box.html' %}</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
{% if page_obj.object_list or request.GET.q%}
|
{% if page_obj.object_list or request.GET.q %}
|
||||||
<div class="table-responsive scrollbar transition">
|
<div class="table-responsive scrollbar transition">
|
||||||
<table class="table align-items-center table-flush table-hover">
|
<table class="table align-items-center table-flush table-hover">
|
||||||
<thead>
|
<thead>
|
||||||
@ -163,7 +163,6 @@
|
|||||||
<tr>
|
<tr>
|
||||||
<td colspan="6" class="text-center">{% trans "No Customers found." %}</td>
|
<td colspan="6" class="text-center">{% trans "No Customers found." %}</td>
|
||||||
</tr>
|
</tr>
|
||||||
|
|
||||||
{% endfor %}
|
{% endfor %}
|
||||||
</tbody>
|
</tbody>
|
||||||
{% endif %}
|
{% endif %}
|
||||||
|
|||||||
@ -6,11 +6,12 @@
|
|||||||
{% block content %}
|
{% block content %}
|
||||||
{% include 'modal/delete_modal.html' %}
|
{% include 'modal/delete_modal.html' %}
|
||||||
{% include 'components/note_modal.html' with content_type="customer" slug=customer.slug %}
|
{% include 'components/note_modal.html' with content_type="customer" slug=customer.slug %}
|
||||||
|
|
||||||
<div class="mt-4">
|
<div class="mt-4">
|
||||||
<div class="row align-items-center justify-content-between g-3 mb-4">
|
<div class="row align-items-center justify-content-between g-3 mb-4">
|
||||||
<div class="col-auto">
|
<div class="col-auto">
|
||||||
<h3 class="mb-0">{% trans 'Customer details' %}<i class="fas fa-user ms-2 text-primary"></i></h3>
|
<h3 class="mb-0">
|
||||||
|
{% trans 'Customer details' %}<i class="fas fa-user ms-2 text-primary"></i>
|
||||||
|
</h3>
|
||||||
</div>
|
</div>
|
||||||
<div class="col-auto d-flex gap-2">
|
<div class="col-auto d-flex gap-2">
|
||||||
{% if perms.inventory.change_customer %}
|
{% if perms.inventory.change_customer %}
|
||||||
@ -30,7 +31,6 @@
|
|||||||
{% endif %}
|
{% endif %}
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class="row g-4 mb-4">
|
<div class="row g-4 mb-4">
|
||||||
<div class="col-12 col-lg-4">
|
<div class="col-12 col-lg-4">
|
||||||
<div class="card h-100 shadow-sm">
|
<div class="card h-100 shadow-sm">
|
||||||
@ -39,7 +39,9 @@
|
|||||||
<div class="col-12 col-sm-auto mb-sm-2">
|
<div class="col-12 col-sm-auto mb-sm-2">
|
||||||
<div class="avatar avatar-5xl">
|
<div class="avatar avatar-5xl">
|
||||||
{% if customer.image %}
|
{% if customer.image %}
|
||||||
<img class="rounded-circle border border-2 border-primary" src="{{ customer.image.url }}" alt="{{ customer.full_name }}"/>
|
<img class="rounded-circle border border-2 border-primary"
|
||||||
|
src="{{ customer.image.url }}"
|
||||||
|
alt="{{ customer.full_name }}" />
|
||||||
{% else %}
|
{% else %}
|
||||||
<div class="avatar-text rounded-circle bg-secondary text-white border border-2 border-primary">
|
<div class="avatar-text rounded-circle bg-secondary text-white border border-2 border-primary">
|
||||||
<span class="fs-4">{{ customer.full_name|first|default:"?" }}</span>
|
<span class="fs-4">{{ customer.full_name|first|default:"?" }}</span>
|
||||||
@ -65,7 +67,6 @@
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class="col-12 col-lg-8">
|
<div class="col-12 col-lg-8">
|
||||||
<div class="card h-100 shadow-sm">
|
<div class="card h-100 shadow-sm">
|
||||||
<div class="card-body p-4">
|
<div class="card-body p-4">
|
||||||
@ -79,18 +80,19 @@
|
|||||||
</li>
|
</li>
|
||||||
<li class="mb-2">
|
<li class="mb-2">
|
||||||
<strong class="text-body-secondary d-block">{% trans 'Email' %}:</strong>
|
<strong class="text-body-secondary d-block">{% trans 'Email' %}:</strong>
|
||||||
<a href="mailto:{{ customer.email|default:"" }}" class="text-decoration-none">{{ customer.email|default:_("N/A") }}</a>
|
<a href="mailto:{{ customer.email|default:"" }}"
|
||||||
|
class="text-decoration-none">{{ customer.email|default:_("N/A") }}</a>
|
||||||
</li>
|
</li>
|
||||||
<li class="mb-0">
|
<li class="mb-0">
|
||||||
<strong class="text-body-secondary d-block">{% trans 'Phone Number' %}:</strong>
|
<strong class="text-body-secondary d-block">{% trans 'Phone Number' %}:</strong>
|
||||||
<a href="tel:{{ customer.phone_number|default:"" }}" class="text-decoration-none">{{ customer.phone_number|default:_("N/A") }}</a>
|
<a href="tel:{{ customer.phone_number|default:"" }}"
|
||||||
|
class="text-decoration-none">{{ customer.phone_number|default:_("N/A") }}</a>
|
||||||
</li>
|
</li>
|
||||||
</ul>
|
</ul>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class="row g-4 mb-4">
|
<div class="row g-4 mb-4">
|
||||||
<div class="col-12">
|
<div class="col-12">
|
||||||
<div class="card shadow-sm">
|
<div class="card shadow-sm">
|
||||||
@ -134,7 +136,6 @@
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class="row g-4 mb-3">
|
<div class="row g-4 mb-3">
|
||||||
<div class="col-12">
|
<div class="col-12">
|
||||||
<div class="card shadow-sm">
|
<div class="card shadow-sm">
|
||||||
@ -142,48 +143,82 @@
|
|||||||
<h5 class="card-title mb-3">{% trans 'Sales History' %}</h5>
|
<h5 class="card-title mb-3">{% trans 'Sales History' %}</h5>
|
||||||
<ul class="nav nav-tabs" id="myTab" role="tablist">
|
<ul class="nav nav-tabs" id="myTab" role="tablist">
|
||||||
<li class="nav-item me-6" role="presentation">
|
<li class="nav-item me-6" role="presentation">
|
||||||
<button class="nav-link active" id="leads-tab" data-bs-toggle="tab" data-bs-target="#leads-tab-pane" type="button" role="tab" aria-controls="leads-tab-pane" aria-selected="true">{% trans 'Leads' %}</button>
|
<button class="nav-link active"
|
||||||
|
id="leads-tab"
|
||||||
|
data-bs-toggle="tab"
|
||||||
|
data-bs-target="#leads-tab-pane"
|
||||||
|
type="button"
|
||||||
|
role="tab"
|
||||||
|
aria-controls="leads-tab-pane"
|
||||||
|
aria-selected="true">{% trans 'Leads' %}</button>
|
||||||
</li>
|
</li>
|
||||||
<li class="nav-item me-6" role="presentation">
|
<li class="nav-item me-6" role="presentation">
|
||||||
<button class="nav-link" id="opportunities-tab" data-bs-toggle="tab" data-bs-target="#opportunities-tab-pane" type="button" role="tab" aria-controls="opportunities-tab-pane" aria-selected="false">{% trans 'Opportunities' %}</button>
|
<button class="nav-link"
|
||||||
|
id="opportunities-tab"
|
||||||
|
data-bs-toggle="tab"
|
||||||
|
data-bs-target="#opportunities-tab-pane"
|
||||||
|
type="button"
|
||||||
|
role="tab"
|
||||||
|
aria-controls="opportunities-tab-pane"
|
||||||
|
aria-selected="false">{% trans 'Opportunities' %}</button>
|
||||||
</li>
|
</li>
|
||||||
<li class="nav-item" role="presentation">
|
<li class="nav-item" role="presentation">
|
||||||
<button class="nav-link" id="estimates-tab" data-bs-toggle="tab" data-bs-target="#estimates-tab-pane" type="button" role="tab" aria-controls="estimates-tab-pane" aria-selected="false">{% trans 'Estimates' %}</button>
|
<button class="nav-link"
|
||||||
|
id="estimates-tab"
|
||||||
|
data-bs-toggle="tab"
|
||||||
|
data-bs-target="#estimates-tab-pane"
|
||||||
|
type="button"
|
||||||
|
role="tab"
|
||||||
|
aria-controls="estimates-tab-pane"
|
||||||
|
aria-selected="false">{% trans 'Estimates' %}</button>
|
||||||
</li>
|
</li>
|
||||||
</ul>
|
</ul>
|
||||||
<div class="tab-content pt-3" id="myTabContent">
|
<div class="tab-content pt-3" id="myTabContent">
|
||||||
<div class="tab-pane fade show active" id="leads-tab-pane" role="tabpanel" aria-labelledby="leads-tab" tabindex="0">
|
<div class="tab-pane fade show active"
|
||||||
|
id="leads-tab-pane"
|
||||||
|
role="tabpanel"
|
||||||
|
aria-labelledby="leads-tab"
|
||||||
|
tabindex="0">
|
||||||
{% for lead in leads %}
|
{% for lead in leads %}
|
||||||
<div class="d-flex align-items-center mb-2">
|
<div class="d-flex align-items-center mb-2">
|
||||||
<i class="fas fa-handshake me-2 text-primary"></i>
|
<i class="fas fa-handshake me-2 text-primary"></i>
|
||||||
<a href="{% url 'lead_detail' request.dealer.slug lead.slug %}" class="fw-bold">{{ lead }}</a>
|
<a href="{% url 'lead_detail' request.dealer.slug lead.slug %}"
|
||||||
|
class="fw-bold">{{ lead }}</a>
|
||||||
</div>
|
</div>
|
||||||
{% empty %}
|
{% empty %}
|
||||||
<p class="text-body-secondary">{% trans 'No leads found for this customer.' %}</p>
|
<p class="text-body-secondary">{% trans 'No leads found for this customer.' %}</p>
|
||||||
{% endfor %}
|
{% endfor %}
|
||||||
</div>
|
</div>
|
||||||
|
<div class="tab-pane fade"
|
||||||
<div class="tab-pane fade" id="opportunities-tab-pane" role="tabpanel" aria-labelledby="opportunities-tab" tabindex="0">
|
id="opportunities-tab-pane"
|
||||||
|
role="tabpanel"
|
||||||
|
aria-labelledby="opportunities-tab"
|
||||||
|
tabindex="0">
|
||||||
{% for lead in leads %}
|
{% for lead in leads %}
|
||||||
{% if lead.opportunity %}
|
{% if lead.opportunity %}
|
||||||
<div class="d-flex align-items-center mb-2">
|
<div class="d-flex align-items-center mb-2">
|
||||||
<i class="fas fa-chart-line me-2 text-success"></i>
|
<i class="fas fa-chart-line me-2 text-success"></i>
|
||||||
<a href="{% url 'opportunity_detail' request.dealer.slug lead.opportunity.slug %}" class="fw-bold">{{ lead.opportunity }}</a>
|
<a href="{% url 'opportunity_detail' request.dealer.slug lead.opportunity.slug %}"
|
||||||
|
class="fw-bold">{{ lead.opportunity }}</a>
|
||||||
</div>
|
</div>
|
||||||
{% endif %}
|
{% endif %}
|
||||||
{% empty %}
|
{% empty %}
|
||||||
<p class="text-body-secondary">{% trans 'No opportunities found for this customer.' %}</p>
|
<p class="text-body-secondary">{% trans 'No opportunities found for this customer.' %}</p>
|
||||||
{% endfor %}
|
{% endfor %}
|
||||||
</div>
|
</div>
|
||||||
|
<div class="tab-pane fade"
|
||||||
<div class="tab-pane fade" id="estimates-tab-pane" role="tabpanel" aria-labelledby="estimates-tab" tabindex="0">
|
id="estimates-tab-pane"
|
||||||
|
role="tabpanel"
|
||||||
|
aria-labelledby="estimates-tab"
|
||||||
|
tabindex="0">
|
||||||
{% for estimate in estimates %}
|
{% for estimate in estimates %}
|
||||||
<div class="card mb-3 shadow-sm">
|
<div class="card mb-3 shadow-sm">
|
||||||
<div class="card-body p-3">
|
<div class="card-body p-3">
|
||||||
<div class="d-flex align-items-center justify-content-between mb-3">
|
<div class="d-flex align-items-center justify-content-between mb-3">
|
||||||
<h6 class="mb-0">
|
<h6 class="mb-0">
|
||||||
<i class="fas fa-file-invoice me-2 text-info"></i>
|
<i class="fas fa-file-invoice me-2 text-info"></i>
|
||||||
<a href="{% url 'estimate_detail' request.dealer.slug estimate.pk %}" class="text-decoration-none">{{ estimate }}</a>
|
<a href="{% url 'estimate_detail' request.dealer.slug estimate.pk %}"
|
||||||
|
class="text-decoration-none">{{ estimate }}</a>
|
||||||
</h6>
|
</h6>
|
||||||
<span class="badge bg-success">{{ estimate.created|date:"d M Y" }}</span>
|
<span class="badge bg-success">{{ estimate.created|date:"d M Y" }}</span>
|
||||||
</div>
|
</div>
|
||||||
@ -197,16 +232,22 @@
|
|||||||
{% for invoice in estimate.invoicemodel_set.all %}
|
{% for invoice in estimate.invoicemodel_set.all %}
|
||||||
<li class="mb-2">
|
<li class="mb-2">
|
||||||
<i class="fas fa-receipt me-2 {% if invoice.is_paid %}text-success{% else %}text-warning{% endif %}"></i>
|
<i class="fas fa-receipt me-2 {% if invoice.is_paid %}text-success{% else %}text-warning{% endif %}"></i>
|
||||||
<a href="{% url 'invoice_detail' request.dealer.slug request.entity.slug invoice.pk %}" class="text-decoration-none">{{ invoice }}</a>
|
<a href="{% url 'invoice_detail' request.dealer.slug request.entity.slug invoice.pk %}"
|
||||||
<span class="badge rounded-pill {% if invoice.is_paid %}bg-success{% else %}bg-warning{% endif %} ms-2">{% if invoice.is_paid %}{% trans "Paid" %}{% else %}{% trans "Unpaid" %}{% endif %}</span>
|
class="text-decoration-none">{{ invoice }}</a>
|
||||||
|
<span class="badge rounded-pill {% if invoice.is_paid %}bg-success{% else %}bg-warning{% endif %} ms-2">
|
||||||
|
{% if invoice.is_paid %}
|
||||||
|
{% trans "Paid" %}
|
||||||
|
{% else %}
|
||||||
|
{% trans "Unpaid" %}
|
||||||
|
{% endif %}
|
||||||
|
</span>
|
||||||
</li>
|
</li>
|
||||||
{% endfor %}
|
{% endfor %}
|
||||||
{% for item in estimate.itemtransactionmodel_set.all %}
|
{% for item in estimate.itemtransactionmodel_set.all %}
|
||||||
<li>
|
<li>
|
||||||
<i class="fas fa-car me-2 text-primary"></i>
|
<i class="fas fa-car me-2 text-primary"></i>
|
||||||
<a href="{% url 'car_detail' request.dealer.slug item.item_model.car.slug %}">{{ item.item_model.car.vin}} | {{item.item_model.car.id_car_make.name}} | {{item.item_model.car.id_car_model.name}}</a>
|
<a href="{% url 'car_detail' request.dealer.slug item.item_model.car.slug %}">{{ item.item_model.car.vin }} | {{ item.item_model.car.id_car_make.name }} | {{ item.item_model.car.id_car_model.name }}</a>
|
||||||
</li>
|
</li>
|
||||||
|
|
||||||
{% endfor %}
|
{% endfor %}
|
||||||
</ul>
|
</ul>
|
||||||
</div>
|
</div>
|
||||||
@ -222,11 +263,7 @@
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
{% include "components/note_modal.html" with content_type="customer" slug=customer.slug %}
|
{% include "components/note_modal.html" with content_type="customer" slug=customer.slug %}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
<!---->
|
<!---->
|
||||||
|
|
||||||
<script>
|
<script>
|
||||||
document.addEventListener("DOMContentLoaded", function () {
|
document.addEventListener("DOMContentLoaded", function () {
|
||||||
const noteModal = document.getElementById("noteModal");
|
const noteModal = document.getElementById("noteModal");
|
||||||
@ -253,5 +290,4 @@
|
|||||||
});
|
});
|
||||||
|
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
{% endblock %}
|
{% endblock %}
|
||||||
@ -2,20 +2,21 @@
|
|||||||
{% load i18n %}
|
{% load i18n %}
|
||||||
{% load tenhal_tag %}
|
{% load tenhal_tag %}
|
||||||
{% block content %}
|
{% block content %}
|
||||||
<div class="main-content flex-grow-1 container-fluid mt-4 mb-3">
|
<div class="main-content flex-grow-1 container-fluid mt-4 mb-3">
|
||||||
<div class="d-flex flex-column flex-md-row justify-content-between align-items-md-center mb-5 pb-3 border-bottom">
|
<div class="d-flex flex-column flex-md-row justify-content-between align-items-md-center mb-5 pb-3 border-bottom">
|
||||||
<h2 class="h3 fw-bold mb-3 mb-md-0">
|
<h2 class="h3 fw-bold mb-3 mb-md-0">
|
||||||
{% trans "Aging Inventory" %}
|
{% trans "Aging Inventory" %}
|
||||||
<i class="fas fa-box-open text-danger ms-2"></i>
|
<i class="fas fa-box-open text-danger ms-2"></i>
|
||||||
</h2>
|
</h2>
|
||||||
<h4 class="text-muted mb-3 ">{% trans "Aging Inventory Total" %} :: <span class=" text-danger">{{total_aging_inventory_value|default:0.00}}<span class="icon-saudi_riyal"></span></span></h4>
|
<h4 class="text-muted mb-3 ">
|
||||||
|
{% trans "Aging Inventory Total" %} :: <span class=" text-danger">{{ total_aging_inventory_value|default:0.00 }}<span class="icon-saudi_riyal"></span></span>
|
||||||
|
</h4>
|
||||||
<p class="text-muted mb-0">{% trans "Cars in inventory for more than 60 days." %}</p>
|
<p class="text-muted mb-0">{% trans "Cars in inventory for more than 60 days." %}</p>
|
||||||
|
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<form method="GET" class="d-flex flex-wrap align-items-center mb-4 g-3">
|
<form method="GET" class="d-flex flex-wrap align-items-center mb-4 g-3">
|
||||||
<div class="col-sm-6 col-md-2 me-2">
|
<div class="col-sm-6 col-md-2 me-2">
|
||||||
<label for="make-filter" class="form-label mb-0 small text-uppercase fw-bold">{% trans "Make:" %}</label>
|
<label for="make-filter"
|
||||||
|
class="form-label mb-0 small text-uppercase fw-bold">{% trans "Make:" %}</label>
|
||||||
<select class="form-select" name="make" id="make-filter">
|
<select class="form-select" name="make" id="make-filter">
|
||||||
<option value="">{% trans "All" %}</option>
|
<option value="">{% trans "All" %}</option>
|
||||||
{% for make in all_makes %}
|
{% for make in all_makes %}
|
||||||
@ -23,59 +24,62 @@
|
|||||||
{% endfor %}
|
{% endfor %}
|
||||||
</select>
|
</select>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class="col-sm-6 col-md-2 me-2">
|
<div class="col-sm-6 col-md-2 me-2">
|
||||||
<label for="model-filter" class="form-label mb-0 small text-uppercase fw-bold">{% trans "Model:" %}</label>
|
<label for="model-filter"
|
||||||
|
class="form-label mb-0 small text-uppercase fw-bold">{% trans "Model:" %}</label>
|
||||||
<select class="form-select" name="model" id="model-filter">
|
<select class="form-select" name="model" id="model-filter">
|
||||||
<option value="">{% trans "All" %}</option>
|
<option value="">{% trans "All" %}</option>
|
||||||
{% for model in all_models %}
|
{% for model in all_models %}
|
||||||
<option value="{{ model }}" {% if model == selected_model %}selected{% endif %}>{{ model }}</option>
|
<option value="{{ model }}"
|
||||||
|
{% if model == selected_model %}selected{% endif %}>{{ model }}</option>
|
||||||
{% endfor %}
|
{% endfor %}
|
||||||
</select>
|
</select>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class="col-sm-6 col-md-2 me-2">
|
<div class="col-sm-6 col-md-2 me-2">
|
||||||
<label for="series-filter" class="form-label mb-0 small text-uppercase fw-bold">{% trans "Series:" %}</label>
|
<label for="series-filter"
|
||||||
|
class="form-label mb-0 small text-uppercase fw-bold">{% trans "Series:" %}</label>
|
||||||
<select class="form-select" name="series" id="series-filter">
|
<select class="form-select" name="series" id="series-filter">
|
||||||
<option value="">{% trans "All" %}</option>
|
<option value="">{% trans "All" %}</option>
|
||||||
{% for series in all_series %}
|
{% for series in all_series %}
|
||||||
<option value="{{ series }}" {% if series == selected_series %}selected{% endif %}>{{ series }}</option>
|
<option value="{{ series }}"
|
||||||
|
{% if series == selected_series %}selected{% endif %}>{{ series }}</option>
|
||||||
{% endfor %}
|
{% endfor %}
|
||||||
</select>
|
</select>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class="col-sm-6 col-md-2 me-2">
|
<div class="col-sm-6 col-md-2 me-2">
|
||||||
<label for="year-filter" class="form-label mb-0 small text-uppercase fw-bold">{% trans "Year:" %}</label>
|
<label for="year-filter"
|
||||||
|
class="form-label mb-0 small text-uppercase fw-bold">{% trans "Year:" %}</label>
|
||||||
<select class="form-select" name="year" id="year-filter">
|
<select class="form-select" name="year" id="year-filter">
|
||||||
<option value="">{% trans "All" %}</option>
|
<option value="">{% trans "All" %}</option>
|
||||||
{% for year in all_years %}
|
{% for year in all_years %}
|
||||||
<option value="{{ year }}" {% if year|stringformat:"s" == selected_year %}selected{% endif %}>{{ year }}</option>
|
<option value="{{ year }}"
|
||||||
|
{% if year|stringformat:"s" == selected_year %}selected{% endif %}>{{ year }}</option>
|
||||||
{% endfor %}
|
{% endfor %}
|
||||||
</select>
|
</select>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class="col-sm-6 col-md-2 me-2">
|
<div class="col-sm-6 col-md-2 me-2">
|
||||||
<label for="stock-type-filter" class="form-label mb-0 small text-uppercase fw-bold">{% trans "Stock Type:" %}</label>
|
<label for="stock-type-filter"
|
||||||
|
class="form-label mb-0 small text-uppercase fw-bold">{% trans "Stock Type:" %}</label>
|
||||||
<select class="form-select" name="stock_type" id="stock-type-filter">
|
<select class="form-select" name="stock_type" id="stock-type-filter">
|
||||||
<option value="">{% trans "All" %}</option>
|
<option value="">{% trans "All" %}</option>
|
||||||
{% for stock_type in all_stock_types %}
|
{% for stock_type in all_stock_types %}
|
||||||
<option value="{{ stock_type }}" {% if stock_type == selected_stock_type %}selected{% endif %}>{{ stock_type|title }}</option>
|
<option value="{{ stock_type }}"
|
||||||
|
{% if stock_type == selected_stock_type %}selected{% endif %}>
|
||||||
|
{{ stock_type|title }}
|
||||||
|
</option>
|
||||||
{% endfor %}
|
{% endfor %}
|
||||||
</select>
|
</select>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class="col-auto">
|
<div class="col-auto">
|
||||||
<button type="submit" class="btn btn-primary mt-4">{% trans "Filter" %}</button>
|
<button type="submit" class="btn btn-primary mt-4">{% trans "Filter" %}</button>
|
||||||
</div>
|
</div>
|
||||||
</form>
|
</form>
|
||||||
|
|
||||||
{% if is_paginated %}
|
{% if is_paginated %}
|
||||||
<div class="d-flex justify-content-between mb-4">
|
<div class="d-flex justify-content-between mb-4">
|
||||||
<span class="text-muted">{% trans "Page" %} {{ page_obj.number }} {% trans "of" %} {{ page_obj.paginator.num_pages }}</span>
|
<span class="text-muted">{% trans "Page" %} {{ page_obj.number }} {% trans "of" %} {{ page_obj.paginator.num_pages }}</span>
|
||||||
<span class="text-muted">{% trans "Total Aging Cars:" %} {{ page_obj.paginator.count }}</span>
|
<span class="text-muted">{% trans "Total Aging Cars:" %} {{ page_obj.paginator.count }}</span>
|
||||||
</div>
|
</div>
|
||||||
{% endif %}
|
{% endif %}
|
||||||
|
|
||||||
{% if cars %}
|
{% if cars %}
|
||||||
<div class="row row-cols-1 row-cols-md-2 row-cols-lg-3 g-4">
|
<div class="row row-cols-1 row-cols-md-2 row-cols-lg-3 g-4">
|
||||||
{% for car in cars %}
|
{% for car in cars %}
|
||||||
@ -83,7 +87,8 @@
|
|||||||
<div class="card h-100 shadow-sm border-0">
|
<div class="card h-100 shadow-sm border-0">
|
||||||
<div class="card-body d-flex flex-column">
|
<div class="card-body d-flex flex-column">
|
||||||
<h5 class="card-title text-danger fw-bold">
|
<h5 class="card-title text-danger fw-bold">
|
||||||
<img src="{{car.logo}}" width="40" height="40" class=""> {{ car.id_car_make.name }} {{ car.id_car_model.name }} {{ car.id_car_serie.name }} {{ car.year}}
|
<img src="{{ car.logo }}" width="40" height="40" class="">
|
||||||
|
{{ car.id_car_make.name }} {{ car.id_car_model.name }} {{ car.id_car_serie.name }} {{ car.year }}
|
||||||
</h5>
|
</h5>
|
||||||
<p class="card-text text-muted mb-2">
|
<p class="card-text text-muted mb-2">
|
||||||
<strong>{% trans "VIN:" %}</strong> {{ car.vin }}
|
<strong>{% trans "VIN:" %}</strong> {{ car.vin }}
|
||||||
@ -96,9 +101,8 @@
|
|||||||
<strong>{% trans "Acquisition Date:" %}</strong> {{ car.receiving_date|date:"F j, Y" }}
|
<strong>{% trans "Acquisition Date:" %}</strong> {{ car.receiving_date|date:"F j, Y" }}
|
||||||
</p>
|
</p>
|
||||||
<div class="mt-auto pt-3 border-top">
|
<div class="mt-auto pt-3 border-top">
|
||||||
<a href="{% url 'car_detail' request.dealer.slug car.slug %}" class="btn btn-outline-primary btn-sm w-100">
|
<a href="{% url 'car_detail' request.dealer.slug car.slug %}"
|
||||||
{% trans "View Details" %}
|
class="btn btn-outline-primary btn-sm w-100">{% trans "View Details" %}</a>
|
||||||
</a>
|
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
@ -108,9 +112,7 @@
|
|||||||
{% else %}
|
{% else %}
|
||||||
<div class="alert alert-success d-flex align-items-center" role="alert">
|
<div class="alert alert-success d-flex align-items-center" role="alert">
|
||||||
<i class="fas fa-check-circle me-2"></i>
|
<i class="fas fa-check-circle me-2"></i>
|
||||||
<div>
|
<div>{% trans "Excellent! There are no cars in the aging inventory at the moment." %}</div>
|
||||||
{% trans "Excellent! There are no cars in the aging inventory at the moment." %}
|
|
||||||
</div>
|
|
||||||
</div>
|
</div>
|
||||||
{% endif %}
|
{% endif %}
|
||||||
<div class="d-flex justify-content-end mt-3">
|
<div class="d-flex justify-content-end mt-3">
|
||||||
@ -120,5 +122,5 @@
|
|||||||
{% endif %}
|
{% endif %}
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
{% endblock content %}
|
{% endblock content %}
|
||||||
@ -5,7 +5,6 @@
|
|||||||
Monthly Performance Trends ({{ start_date }} - {{ end_date }})
|
Monthly Performance Trends ({{ start_date }} - {{ end_date }})
|
||||||
{% endblocktrans %}
|
{% endblocktrans %}
|
||||||
</h3>
|
</h3>
|
||||||
|
|
||||||
<div class="row g-4 mb-5">
|
<div class="row g-4 mb-5">
|
||||||
<div class="col-12">
|
<div class="col-12">
|
||||||
<div class="card h-100 shadow-sm border-0">
|
<div class="card h-100 shadow-sm border-0">
|
||||||
@ -22,25 +21,25 @@
|
|||||||
<div class="card-header bg-white border-bottom-0">
|
<div class="card-header bg-white border-bottom-0">
|
||||||
<h5 class="fw-bold mb-0 text-dark">{% trans "Monthly Cars Sold" %}</h5>
|
<h5 class="fw-bold mb-0 text-dark">{% trans "Monthly Cars Sold" %}</h5>
|
||||||
</div>
|
</div>
|
||||||
<div class="card-body d-flex align-items-center justify-content-center" style="height: 400px;">
|
<div class="card-body d-flex align-items-center justify-content-center"
|
||||||
|
style="height: 400px">
|
||||||
<canvas id="CarsSoldByMonthChart"></canvas>
|
<canvas id="CarsSoldByMonthChart"></canvas>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class="row g-4 mb-5">
|
<div class="row g-4 mb-5">
|
||||||
<div class="col-lg-6 col-12">
|
<div class="col-lg-6 col-12">
|
||||||
<div class="card h-100 shadow-sm border-0">
|
<div class="card h-100 shadow-sm border-0">
|
||||||
<div class="card-header bg-white border-bottom-0">
|
<div class="card-header bg-white border-bottom-0">
|
||||||
<h5 class="fw-bold mb-0 text-dark">{% trans "Sales by Make" %}</h5>
|
<h5 class="fw-bold mb-0 text-dark">{% trans "Sales by Make" %}</h5>
|
||||||
</div>
|
</div>
|
||||||
<div class="card-body d-flex align-items-center justify-content-center" style="height: 400px;">
|
<div class="card-body d-flex align-items-center justify-content-center"
|
||||||
|
style="height: 400px">
|
||||||
<canvas id="salesByBrandChart"></canvas>
|
<canvas id="salesByBrandChart"></canvas>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class="col-lg-6 col-12">
|
<div class="col-lg-6 col-12">
|
||||||
<div class="card h-100 shadow-sm border-0">
|
<div class="card h-100 shadow-sm border-0">
|
||||||
<div class="card-header bg-white border-bottom-0 d-flex justify-content-between align-items-center">
|
<div class="card-header bg-white border-bottom-0 d-flex justify-content-between align-items-center">
|
||||||
@ -51,7 +50,10 @@
|
|||||||
<select id="carMakeSelectSales" class="form-select" name="make_sold">
|
<select id="carMakeSelectSales" class="form-select" name="make_sold">
|
||||||
<option value="">{% trans "All Makes" %}</option>
|
<option value="">{% trans "All Makes" %}</option>
|
||||||
{% for make_sold in all_makes_sold %}
|
{% for make_sold in all_makes_sold %}
|
||||||
<option value="{{ make_sold }}" {% if make_sold == selected_make_sales %}selected{% endif %}>{{ make_sold }}</option>
|
<option value="{{ make_sold }}"
|
||||||
|
{% if make_sold == selected_make_sales %}selected{% endif %}>
|
||||||
|
{{ make_sold }}
|
||||||
|
</option>
|
||||||
{% endfor %}
|
{% endfor %}
|
||||||
</select>
|
</select>
|
||||||
</div>
|
</div>
|
||||||
@ -67,22 +69,20 @@
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
{% endif %}
|
{% endif %}
|
||||||
|
|
||||||
{% if request.is_dealer or request.is_manager or request.is_inventory %}
|
{% if request.is_dealer or request.is_manager or request.is_inventory %}
|
||||||
<h3 class="fw-bold mb-3">{% trans "Inventory Trends" %}</h3>
|
<h3 class="fw-bold mb-3">{% trans "Inventory Trends" %}</h3>
|
||||||
|
|
||||||
<div class="row g-4 mb-5">
|
<div class="row g-4 mb-5">
|
||||||
<div class="col-lg-6 col-12">
|
<div class="col-lg-6 col-12">
|
||||||
<div class="card h-100 shadow-sm border-0">
|
<div class="card h-100 shadow-sm border-0">
|
||||||
<div class="card-header bg-white border-bottom-0">
|
<div class="card-header bg-white border-bottom-0">
|
||||||
<h5 class="fw-bold mb-0 text-dark">{% trans "Inventory by Make" %}</h5>
|
<h5 class="fw-bold mb-0 text-dark">{% trans "Inventory by Make" %}</h5>
|
||||||
</div>
|
</div>
|
||||||
<div class="card-body d-flex align-items-center justify-content-center" style="height: 400px;">
|
<div class="card-body d-flex align-items-center justify-content-center"
|
||||||
|
style="height: 400px">
|
||||||
<canvas id="inventoryByMakeChart"></canvas>
|
<canvas id="inventoryByMakeChart"></canvas>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class="col-lg-6 col-12">
|
<div class="col-lg-6 col-12">
|
||||||
<div class="card h-100 shadow-sm border-0">
|
<div class="card h-100 shadow-sm border-0">
|
||||||
<div class="card-header bg-white border-bottom-0 d-flex justify-content-between align-items-center">
|
<div class="card-header bg-white border-bottom-0 d-flex justify-content-between align-items-center">
|
||||||
@ -93,7 +93,10 @@
|
|||||||
<select id="carMakeSelectInventory" class="form-select" name="make_inventory">
|
<select id="carMakeSelectInventory" class="form-select" name="make_inventory">
|
||||||
<option value="">{% trans "All Makes" %}</option>
|
<option value="">{% trans "All Makes" %}</option>
|
||||||
{% for make_inv in all_makes_inventory %}
|
{% for make_inv in all_makes_inventory %}
|
||||||
<option value="{{ make_inv }}" {% if make_inv == selected_make_inventory %}selected{% endif %}>{{ make_inv }}</option>
|
<option value="{{ make_inv }}"
|
||||||
|
{% if make_inv == selected_make_inventory %}selected{% endif %}>
|
||||||
|
{{ make_inv }}
|
||||||
|
</option>
|
||||||
{% endfor %}
|
{% endfor %}
|
||||||
</select>
|
</select>
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@ -18,7 +18,9 @@
|
|||||||
<div class="card h-100 shadow-sm border-0">
|
<div class="card h-100 shadow-sm border-0">
|
||||||
<div class="card-body p-4">
|
<div class="card-body p-4">
|
||||||
<p class="text-uppercase text-muted fw-bold small mb-1">{% trans "Total Revenue from Cars" %}</p>
|
<p class="text-uppercase text-muted fw-bold small mb-1">{% trans "Total Revenue from Cars" %}</p>
|
||||||
<h4 class="fw-bolder text-primary mb-3">{{ total_revenue_from_cars|floatformat:2 }}<span class="icon-saudi_riyal"></span></h4>
|
<h4 class="fw-bolder text-primary mb-3">
|
||||||
|
{{ total_revenue_from_cars|floatformat:2 }}<span class="icon-saudi_riyal"></span>
|
||||||
|
</h4>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
@ -26,7 +28,9 @@
|
|||||||
<div class="card h-100 shadow-sm border-0">
|
<div class="card h-100 shadow-sm border-0">
|
||||||
<div class="card-body p-4">
|
<div class="card-body p-4">
|
||||||
<p class="text-uppercase text-muted fw-bold small mb-1">{% trans "Net Profit from Cars" %}</p>
|
<p class="text-uppercase text-muted fw-bold small mb-1">{% trans "Net Profit from Cars" %}</p>
|
||||||
<h4 class="fw-bolder text-success mb-3">{{ net_profit_from_cars|floatformat:2 }}<span class="icon-saudi_riyal"></span></h4>
|
<h4 class="fw-bolder text-success mb-3">
|
||||||
|
{{ net_profit_from_cars|floatformat:2 }}<span class="icon-saudi_riyal"></span>
|
||||||
|
</h4>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
@ -34,7 +38,9 @@
|
|||||||
<div class="card h-100 shadow-sm border-0">
|
<div class="card h-100 shadow-sm border-0">
|
||||||
<div class="card-body p-4">
|
<div class="card-body p-4">
|
||||||
<p class="text-uppercase text-muted fw-bold small mb-1">{% trans "Total Discount on Cars" %}</p>
|
<p class="text-uppercase text-muted fw-bold small mb-1">{% trans "Total Discount on Cars" %}</p>
|
||||||
<h4 class="fw-bolder text-primary mb-3">{{ total_discount_on_cars|floatformat:2 }}<span class="icon-saudi_riyal"></span></h4>
|
<h4 class="fw-bolder text-primary mb-3">
|
||||||
|
{{ total_discount_on_cars|floatformat:2 }}<span class="icon-saudi_riyal"></span>
|
||||||
|
</h4>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
@ -42,7 +48,9 @@
|
|||||||
<div class="card h-100 shadow-sm border-0">
|
<div class="card h-100 shadow-sm border-0">
|
||||||
<div class="card-body p-4">
|
<div class="card-body p-4">
|
||||||
<p class="text-uppercase text-muted fw-bold small mb-1">{% trans "Total Cost of Cars Sold" %}</p>
|
<p class="text-uppercase text-muted fw-bold small mb-1">{% trans "Total Cost of Cars Sold" %}</p>
|
||||||
<h4 class="fw-bolder text-primary mb-3">{{ total_cost_of_cars_sold|floatformat:2 }}<span class="icon-saudi_riyal"></span></h4>
|
<h4 class="fw-bolder text-primary mb-3">
|
||||||
|
{{ total_cost_of_cars_sold|floatformat:2 }}<span class="icon-saudi_riyal"></span>
|
||||||
|
</h4>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
@ -50,12 +58,13 @@
|
|||||||
<div class="card h-100 shadow-sm border-0">
|
<div class="card h-100 shadow-sm border-0">
|
||||||
<div class="card-body p-4">
|
<div class="card-body p-4">
|
||||||
<p class="text-uppercase text-muted fw-bold small mb-1">{% trans "Total VAT from Cars" %}</p>
|
<p class="text-uppercase text-muted fw-bold small mb-1">{% trans "Total VAT from Cars" %}</p>
|
||||||
<h4 class="fw-bolder text-primary mb-3">{{ total_vat_collected_from_cars|floatformat:2 }}<span class="icon-saudi_riyal"></span></h4>
|
<h4 class="fw-bolder text-primary mb-3">
|
||||||
|
{{ total_vat_collected_from_cars|floatformat:2 }}<span class="icon-saudi_riyal"></span>
|
||||||
|
</h4>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<h4 class="fw-bold my-4">{% trans "Sales of New Cars" %}</h4>
|
<h4 class="fw-bold my-4">{% trans "Sales of New Cars" %}</h4>
|
||||||
<div class="row g-4 mb-5">
|
<div class="row g-4 mb-5">
|
||||||
<div class="col-sm-6 col-md-4 col-lg-3">
|
<div class="col-sm-6 col-md-4 col-lg-3">
|
||||||
@ -70,7 +79,9 @@
|
|||||||
<div class="card h-100 shadow-sm border-0">
|
<div class="card h-100 shadow-sm border-0">
|
||||||
<div class="card-body p-4">
|
<div class="card-body p-4">
|
||||||
<p class="text-uppercase text-muted fw-bold small mb-1">{% trans "New Cars Revenue" %}</p>
|
<p class="text-uppercase text-muted fw-bold small mb-1">{% trans "New Cars Revenue" %}</p>
|
||||||
<h4 class="fw-bolder text-primary mb-3">{{ total_revenue_from_new_cars|floatformat:2 }}<span class="icon-saudi_riyal"></span></h4>
|
<h4 class="fw-bolder text-primary mb-3">
|
||||||
|
{{ total_revenue_from_new_cars|floatformat:2 }}<span class="icon-saudi_riyal"></span>
|
||||||
|
</h4>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
@ -78,7 +89,9 @@
|
|||||||
<div class="card h-100 shadow-sm border-0">
|
<div class="card h-100 shadow-sm border-0">
|
||||||
<div class="card-body p-4">
|
<div class="card-body p-4">
|
||||||
<p class="text-uppercase text-muted fw-bold small mb-1">{% trans "New Cars Net Profit" %}</p>
|
<p class="text-uppercase text-muted fw-bold small mb-1">{% trans "New Cars Net Profit" %}</p>
|
||||||
<h4 class="fw-bolder text-success mb-3">{{ net_profit_from_new_cars|floatformat:2 }}<span class="icon-saudi_riyal"></span></h4>
|
<h4 class="fw-bolder text-success mb-3">
|
||||||
|
{{ net_profit_from_new_cars|floatformat:2 }}<span class="icon-saudi_riyal"></span>
|
||||||
|
</h4>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
@ -86,7 +99,9 @@
|
|||||||
<div class="card h-100 shadow-sm border-0">
|
<div class="card h-100 shadow-sm border-0">
|
||||||
<div class="card-body p-4">
|
<div class="card-body p-4">
|
||||||
<p class="text-uppercase text-muted fw-bold small mb-1">{% trans "New Cars VAT" %}</p>
|
<p class="text-uppercase text-muted fw-bold small mb-1">{% trans "New Cars VAT" %}</p>
|
||||||
<h4 class="fw-bolder text-primary mb-3">{{ total_vat_collected_from_new_cars|floatformat:2 }}<span class="icon-saudi_riyal"></span></h4>
|
<h4 class="fw-bolder text-primary mb-3">
|
||||||
|
{{ total_vat_collected_from_new_cars|floatformat:2 }}<span class="icon-saudi_riyal"></span>
|
||||||
|
</h4>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
@ -94,12 +109,13 @@
|
|||||||
<div class="card h-100 shadow-sm border-0">
|
<div class="card h-100 shadow-sm border-0">
|
||||||
<div class="card-body p-4">
|
<div class="card-body p-4">
|
||||||
<p class="text-uppercase text-muted fw-bold small mb-1">{% trans "New Cars Cost" %}</p>
|
<p class="text-uppercase text-muted fw-bold small mb-1">{% trans "New Cars Cost" %}</p>
|
||||||
<h4 class="fw-bolder text-primary mb-3">{{ total_cost_of_new_cars_sold|floatformat:2 }}<span class="icon-saudi_riyal"></span></h4>
|
<h4 class="fw-bolder text-primary mb-3">
|
||||||
|
{{ total_cost_of_new_cars_sold|floatformat:2 }}<span class="icon-saudi_riyal"></span>
|
||||||
|
</h4>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<h4 class="fw-bold my-4">{% trans "Sales of Used Cars" %}</h4>
|
<h4 class="fw-bold my-4">{% trans "Sales of Used Cars" %}</h4>
|
||||||
<div class="row g-4 mb-5">
|
<div class="row g-4 mb-5">
|
||||||
<div class="col-sm-6 col-md-4 col-lg-3">
|
<div class="col-sm-6 col-md-4 col-lg-3">
|
||||||
@ -114,7 +130,9 @@
|
|||||||
<div class="card h-100 shadow-sm border-0">
|
<div class="card h-100 shadow-sm border-0">
|
||||||
<div class="card-body p-4">
|
<div class="card-body p-4">
|
||||||
<p class="text-uppercase text-muted fw-bold small mb-1">{% trans "Used Cars Revenue" %}</p>
|
<p class="text-uppercase text-muted fw-bold small mb-1">{% trans "Used Cars Revenue" %}</p>
|
||||||
<h4 class="fw-bolder text-primary mb-3">{{ total_revenue_from_used_cars|floatformat:2 }}<span class="icon-saudi_riyal"></span></h4>
|
<h4 class="fw-bolder text-primary mb-3">
|
||||||
|
{{ total_revenue_from_used_cars|floatformat:2 }}<span class="icon-saudi_riyal"></span>
|
||||||
|
</h4>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
@ -122,7 +140,9 @@
|
|||||||
<div class="card h-100 shadow-sm border-0">
|
<div class="card h-100 shadow-sm border-0">
|
||||||
<div class="card-body p-4">
|
<div class="card-body p-4">
|
||||||
<p class="text-uppercase text-muted fw-bold small mb-1">{% trans "Used Cars Net Profit" %}</p>
|
<p class="text-uppercase text-muted fw-bold small mb-1">{% trans "Used Cars Net Profit" %}</p>
|
||||||
<h4 class="fw-bolder text-success mb-3">{{ net_profit_from_used_cars|floatformat:2 }}<span class="icon-saudi_riyal"></span></h4>
|
<h4 class="fw-bolder text-success mb-3">
|
||||||
|
{{ net_profit_from_used_cars|floatformat:2 }}<span class="icon-saudi_riyal"></span>
|
||||||
|
</h4>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
@ -130,7 +150,9 @@
|
|||||||
<div class="card h-100 shadow-sm border-0">
|
<div class="card h-100 shadow-sm border-0">
|
||||||
<div class="card-body p-4">
|
<div class="card-body p-4">
|
||||||
<p class="text-uppercase text-muted fw-bold small mb-1">{% trans "Used Cars VAT" %}</p>
|
<p class="text-uppercase text-muted fw-bold small mb-1">{% trans "Used Cars VAT" %}</p>
|
||||||
<h4 class="fw-bolder text-primary mb-3">{{ total_vat_collected_from_used_cars|floatformat:2 }}<span class="icon-saudi_riyal"></span></h4>
|
<h4 class="fw-bolder text-primary mb-3">
|
||||||
|
{{ total_vat_collected_from_used_cars|floatformat:2 }}<span class="icon-saudi_riyal"></span>
|
||||||
|
</h4>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
@ -138,13 +160,14 @@
|
|||||||
<div class="card h-100 shadow-sm border-0">
|
<div class="card h-100 shadow-sm border-0">
|
||||||
<div class="card-body p-4">
|
<div class="card-body p-4">
|
||||||
<p class="text-uppercase text-muted fw-bold small mb-1">{% trans "Used Cars Cost" %}</p>
|
<p class="text-uppercase text-muted fw-bold small mb-1">{% trans "Used Cars Cost" %}</p>
|
||||||
<h4 class="fw-bolder text-primary mb-3">{{ total_cost_of_used_cars_sold|floatformat:2 }}<span class="icon-saudi_riyal"></span></h4>
|
<h4 class="fw-bolder text-primary mb-3">
|
||||||
|
{{ total_cost_of_used_cars_sold|floatformat:2 }}<span class="icon-saudi_riyal"></span>
|
||||||
|
</h4>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
{% endif %}
|
{% endif %}
|
||||||
|
|
||||||
{% if request.is_dealer or request.is_manager or request.is_inventory %}
|
{% if request.is_dealer or request.is_manager or request.is_inventory %}
|
||||||
<h3 class="fw-bold mb-3">{% trans "Inventory KPIs" %}</h3>
|
<h3 class="fw-bold mb-3">{% trans "Inventory KPIs" %}</h3>
|
||||||
<div class="row g-4 mb-5">
|
<div class="row g-4 mb-5">
|
||||||
@ -160,7 +183,9 @@
|
|||||||
<div class="card h-100 shadow-sm border-0">
|
<div class="card h-100 shadow-sm border-0">
|
||||||
<div class="card-body p-4">
|
<div class="card-body p-4">
|
||||||
<p class="text-uppercase text-muted fw-bold small mb-1">{% trans "Total Inventory Value" %}</p>
|
<p class="text-uppercase text-muted fw-bold small mb-1">{% trans "Total Inventory Value" %}</p>
|
||||||
<h4 class="fw-bolder text-primary mb-3">{{ total_inventory_value|floatformat:2 }}<span class="icon-saudi_riyal"></span></h4>
|
<h4 class="fw-bolder text-primary mb-3">
|
||||||
|
{{ total_inventory_value|floatformat:2 }}<span class="icon-saudi_riyal"></span>
|
||||||
|
</h4>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
@ -184,7 +209,9 @@
|
|||||||
<div class="card h-100 shadow-sm border-0">
|
<div class="card h-100 shadow-sm border-0">
|
||||||
<div class="card-body p-4">
|
<div class="card-body p-4">
|
||||||
<p class="text-uppercase text-muted fw-bold small mb-1">{% trans "New Cars Inventory Value" %}</p>
|
<p class="text-uppercase text-muted fw-bold small mb-1">{% trans "New Cars Inventory Value" %}</p>
|
||||||
<h4 class="fw-bolder text-primary mb-3">{{ new_car_value|floatformat:2 }}<span class="icon-saudi_riyal"></span></h4>
|
<h4 class="fw-bolder text-primary mb-3">
|
||||||
|
{{ new_car_value|floatformat:2 }}<span class="icon-saudi_riyal"></span>
|
||||||
|
</h4>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
@ -192,21 +219,28 @@
|
|||||||
<div class="card h-100 shadow-sm border-0">
|
<div class="card h-100 shadow-sm border-0">
|
||||||
<div class="card-body p-4">
|
<div class="card-body p-4">
|
||||||
<p class="text-uppercase text-muted fw-bold small mb-1">{% trans "Used Cars Inventory Value" %}</p>
|
<p class="text-uppercase text-muted fw-bold small mb-1">{% trans "Used Cars Inventory Value" %}</p>
|
||||||
<h4 class="fw-bolder text-primary mb-3">{{ used_car_value|floatformat:2 }}<span class="icon-saudi_riyal"></span></h4>
|
<h4 class="fw-bolder text-primary mb-3">
|
||||||
|
{{ used_car_value|floatformat:2 }}<span class="icon-saudi_riyal"></span>
|
||||||
|
</h4>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div class="col-sm-6 col-md-4 col-lg-3">
|
<div class="col-sm-6 col-md-4 col-lg-3">
|
||||||
<div class="card h-100 shadow-sm border-0">
|
<div class="card h-100 shadow-sm border-0">
|
||||||
<div class="card-body p-4">
|
<div class="card-body p-4">
|
||||||
<p class="text-uppercase text-danger fw-bold small mb-1"><a class="text-danger" href="{% url 'aging_inventory_list' request.dealer.slug %}">{% trans "Aging Inventory (> 60 days)" %}</a></p>
|
<p class="text-uppercase text-danger fw-bold small mb-1">
|
||||||
<h4 class="fw-bolder text-danger mb-3"><a class="text-danger" href="{% url 'aging_inventory_list' request.dealer.slug %}">{{ aging_inventory_count }}</a></h4>
|
<a class="text-danger"
|
||||||
|
href="{% url 'aging_inventory_list' request.dealer.slug %}">{% trans "Aging Inventory (> 60 days)" %}</a>
|
||||||
|
</p>
|
||||||
|
<h4 class="fw-bolder text-danger mb-3">
|
||||||
|
<a class="text-danger"
|
||||||
|
href="{% url 'aging_inventory_list' request.dealer.slug %}">{{ aging_inventory_count }}</a>
|
||||||
|
</h4>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
{% endif %}
|
{% endif %}
|
||||||
|
|
||||||
{% if request.is_dealer or request.is_manager or request.is_accountant %}
|
{% if request.is_dealer or request.is_manager or request.is_accountant %}
|
||||||
<h3 class="fw-bold mb-3">
|
<h3 class="fw-bold mb-3">
|
||||||
{% blocktrans with start_date=start_date|date:"F j, Y" end_date=end_date|date:"F j, Y" %}
|
{% blocktrans with start_date=start_date|date:"F j, Y" end_date=end_date|date:"F j, Y" %}
|
||||||
@ -218,7 +252,9 @@
|
|||||||
<div class="card h-100 shadow-sm border-0">
|
<div class="card h-100 shadow-sm border-0">
|
||||||
<div class="card-body p-4">
|
<div class="card-body p-4">
|
||||||
<p class="text-uppercase text-muted fw-bold small mb-1">{% trans "Total Revenue from Services" %}</p>
|
<p class="text-uppercase text-muted fw-bold small mb-1">{% trans "Total Revenue from Services" %}</p>
|
||||||
<h4 class="fw-bolder text-info mb-3">{{ total_revenue_from_services|floatformat:2 }}<span class="icon-saudi_riyal"></span></h4>
|
<h4 class="fw-bolder text-info mb-3">
|
||||||
|
{{ total_revenue_from_services|floatformat:2 }}<span class="icon-saudi_riyal"></span>
|
||||||
|
</h4>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
@ -226,7 +262,9 @@
|
|||||||
<div class="card h-100 shadow-sm border-0">
|
<div class="card h-100 shadow-sm border-0">
|
||||||
<div class="card-body p-4">
|
<div class="card-body p-4">
|
||||||
<p class="text-uppercase text-muted fw-bold small mb-1">{% trans "Total VAT from Services" %}</p>
|
<p class="text-uppercase text-muted fw-bold small mb-1">{% trans "Total VAT from Services" %}</p>
|
||||||
<h4 class="fw-bolder text-primary mb-3">{{ total_vat_collected_from_services|floatformat:2 }}<span class="icon-saudi_riyal"></span></h4>
|
<h4 class="fw-bolder text-primary mb-3">
|
||||||
|
{{ total_vat_collected_from_services|floatformat:2 }}<span class="icon-saudi_riyal"></span>
|
||||||
|
</h4>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
@ -234,7 +272,9 @@
|
|||||||
<div class="card h-100 shadow-sm border-0">
|
<div class="card h-100 shadow-sm border-0">
|
||||||
<div class="card-body p-4">
|
<div class="card-body p-4">
|
||||||
<p class="text-uppercase text-muted fw-bold small mb-1">{% trans "Total Revenue Generated" %}</p>
|
<p class="text-uppercase text-muted fw-bold small mb-1">{% trans "Total Revenue Generated" %}</p>
|
||||||
<h4 class="fw-bolder text-success mb-3">{{ total_revenue_generated|floatformat:2 }}<span class="icon-saudi_riyal"></span></h4>
|
<h4 class="fw-bolder text-success mb-3">
|
||||||
|
{{ total_revenue_generated|floatformat:2 }}<span class="icon-saudi_riyal"></span>
|
||||||
|
</h4>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
@ -242,7 +282,9 @@
|
|||||||
<div class="card h-100 shadow-sm border-0">
|
<div class="card h-100 shadow-sm border-0">
|
||||||
<div class="card-body p-4">
|
<div class="card-body p-4">
|
||||||
<p class="text-uppercase text-muted fw-bold small mb-1">{% trans "Total VAT Collected" %}</p>
|
<p class="text-uppercase text-muted fw-bold small mb-1">{% trans "Total VAT Collected" %}</p>
|
||||||
<h4 class="fw-bolder text-primary mb-3">{{ total_vat_collected|floatformat:2 }}<span class="icon-saudi_riyal"></span></h4>
|
<h4 class="fw-bolder text-primary mb-3">
|
||||||
|
{{ total_vat_collected|floatformat:2 }}<span class="icon-saudi_riyal"></span>
|
||||||
|
</h4>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
@ -250,7 +292,9 @@
|
|||||||
<div class="card h-100 shadow-sm border-0">
|
<div class="card h-100 shadow-sm border-0">
|
||||||
<div class="card-body p-4">
|
<div class="card-body p-4">
|
||||||
<p class="text-uppercase text-muted fw-bold small mb-1">{% trans "Total Expenses" %}</p>
|
<p class="text-uppercase text-muted fw-bold small mb-1">{% trans "Total Expenses" %}</p>
|
||||||
<h4 class="fw-bolder text-danger mb-3">{{ total_expenses|floatformat:2 }}<span class="icon-saudi_riyal"></span></h4>
|
<h4 class="fw-bolder text-danger mb-3">
|
||||||
|
{{ total_expenses|floatformat:2 }}<span class="icon-saudi_riyal"></span>
|
||||||
|
</h4>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
@ -258,7 +302,9 @@
|
|||||||
<div class="card h-100 shadow-sm border-0">
|
<div class="card h-100 shadow-sm border-0">
|
||||||
<div class="card-body p-4">
|
<div class="card-body p-4">
|
||||||
<p class="text-uppercase text-muted fw-bold small mb-1">{% trans "Gross Profit" %}</p>
|
<p class="text-uppercase text-muted fw-bold small mb-1">{% trans "Gross Profit" %}</p>
|
||||||
<h4 class="fw-bolder text-success mb-3">{{ gross_profit|floatformat:2 }}<span class="icon-saudi_riyal"></span></h4>
|
<h4 class="fw-bolder text-success mb-3">
|
||||||
|
{{ gross_profit|floatformat:2 }}<span class="icon-saudi_riyal"></span>
|
||||||
|
</h4>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@ -1,13 +1,11 @@
|
|||||||
{% extends 'base.html' %}
|
{% extends 'base.html' %}
|
||||||
{% load i18n %}
|
{% load i18n %}
|
||||||
{% load tenhal_tag %}
|
{% load tenhal_tag %}
|
||||||
|
|
||||||
{% block title %}
|
{% block title %}
|
||||||
{% trans "Dealership Dashboard"|capfirst %}
|
{% trans "Dealership Dashboard"|capfirst %}
|
||||||
{% endblock title %}
|
{% endblock title %}
|
||||||
|
|
||||||
{% block content %}
|
{% block content %}
|
||||||
<div class="main-content flex-grow-1 container-fluid mt-4 mb-3">
|
<div class="main-content flex-grow-1 container-fluid mt-4 mb-3">
|
||||||
<div class="d-flex flex-column flex-md-row justify-content-between align-items-md-center mb-5 pb-3 border-bottom">
|
<div class="d-flex flex-column flex-md-row justify-content-between align-items-md-center mb-5 pb-3 border-bottom">
|
||||||
<h2 class="h3 fw-bold mb-3 mb-md-0">
|
<h2 class="h3 fw-bold mb-3 mb-md-0">
|
||||||
{% if request.is_dealer %}
|
{% if request.is_dealer %}
|
||||||
@ -25,13 +23,21 @@
|
|||||||
<div class="row g-3">
|
<div class="row g-3">
|
||||||
<div class="col-12 col-md-4">
|
<div class="col-12 col-md-4">
|
||||||
<label for="start-date" class="form-label">{% trans "Start Date" %}</label>
|
<label for="start-date" class="form-label">{% trans "Start Date" %}</label>
|
||||||
<input type="date" class="form-control" id="start-date" name="start_date"
|
<input type="date"
|
||||||
value="{{ start_date|date:'Y-m-d' }}" required>
|
class="form-control"
|
||||||
|
id="start-date"
|
||||||
|
name="start_date"
|
||||||
|
value="{{ start_date|date:'Y-m-d' }}"
|
||||||
|
required>
|
||||||
</div>
|
</div>
|
||||||
<div class="col-12 col-md-4">
|
<div class="col-12 col-md-4">
|
||||||
<label for="end-date" class="form-label">{% trans "End Date" %}</label>
|
<label for="end-date" class="form-label">{% trans "End Date" %}</label>
|
||||||
<input type="date" class="form-control" id="end-date" name="end_date"
|
<input type="date"
|
||||||
value="{{ end_date|date:'Y-m-d' }}" required>
|
class="form-control"
|
||||||
|
id="end-date"
|
||||||
|
name="end_date"
|
||||||
|
value="{{ end_date|date:'Y-m-d' }}"
|
||||||
|
required>
|
||||||
</div>
|
</div>
|
||||||
<div class="col-12 col-md-4 d-flex align-items-end">
|
<div class="col-12 col-md-4 d-flex align-items-end">
|
||||||
<button type="submit" class="btn btn-primary w-100">{% trans "Apply Filter" %}</button>
|
<button type="submit" class="btn btn-primary w-100">{% trans "Apply Filter" %}</button>
|
||||||
@ -40,21 +46,13 @@
|
|||||||
<input type="hidden" name="make_sold" value="{{ selected_make_sales }}">
|
<input type="hidden" name="make_sold" value="{{ selected_make_sales }}">
|
||||||
</form>
|
</form>
|
||||||
</div>
|
</div>
|
||||||
|
<div class="row g-4 mb-5">{% include 'dashboards/financial_data_cards.html' %}</div>
|
||||||
<div class="row g-4 mb-5">
|
<div class="row g-4 mb-5">{% include 'dashboards/chart.html' %}</div>
|
||||||
{% include 'dashboards/financial_data_cards.html' %}
|
|
||||||
</div>
|
</div>
|
||||||
|
<script src="https://cdn.jsdelivr.net/npm/chart.js"></script>
|
||||||
<div class="row g-4 mb-5">
|
|
||||||
{% include 'dashboards/chart.html' %}
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<script src="https://cdn.jsdelivr.net/npm/chart.js"></script>
|
|
||||||
{% endblock content %}
|
{% endblock content %}
|
||||||
|
|
||||||
{% block customJS %}
|
{% block customJS %}
|
||||||
<script>
|
<script>
|
||||||
// Define a color palette that aligns with the Phoenix template
|
// Define a color palette that aligns with the Phoenix template
|
||||||
const primaryColor = '#7249b6';
|
const primaryColor = '#7249b6';
|
||||||
const secondaryColor = '#8193a6';
|
const secondaryColor = '#8193a6';
|
||||||
@ -425,5 +423,5 @@
|
|||||||
messageInventoryModel.style.display = 'flex';
|
messageInventoryModel.style.display = 'flex';
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
</script>
|
</script>
|
||||||
{% endblock %}
|
{% endblock %}
|
||||||
@ -1,21 +1,28 @@
|
|||||||
{% extends 'base.html' %}
|
{% extends 'base.html' %}
|
||||||
{% block content %}
|
{% block content %}
|
||||||
|
<div class="main-content flex-grow-1 container-fluid mt-4 mb-3">
|
||||||
<div class="main-content flex-grow-1 container-fluid mt-4 mb-3">
|
|
||||||
<div class="d-flex justify-content-between align-items-center mb-5 pb-3 border-bottom">
|
<div class="d-flex justify-content-between align-items-center mb-5 pb-3 border-bottom">
|
||||||
<h2 class="h3 fw-bold mb-0">Manager Dashboard<i class="fas fa-chart-area text-primary ms-2"></i></h2>
|
<h2 class="h3 fw-bold mb-0">
|
||||||
|
Manager Dashboard<i class="fas fa-chart-area text-primary ms-2"></i>
|
||||||
|
</h2>
|
||||||
<div class="dropdown">
|
<div class="dropdown">
|
||||||
<button class="btn btn-outline-secondary dropdown-toggle" type="button" data-bs-toggle="dropdown" aria-expanded="false">
|
<button class="btn btn-outline-secondary dropdown-toggle"
|
||||||
Last 30 Days
|
type="button"
|
||||||
</button>
|
data-bs-toggle="dropdown"
|
||||||
|
aria-expanded="false">Last 30 Days</button>
|
||||||
<ul class="dropdown-menu dropdown-menu-end shadow">
|
<ul class="dropdown-menu dropdown-menu-end shadow">
|
||||||
<li><a class="dropdown-item" href="#">Today</a></li>
|
<li>
|
||||||
<li><a class="dropdown-item" href="#">Last 7 Days</a></li>
|
<a class="dropdown-item" href="#">Today</a>
|
||||||
<li><a class="dropdown-item" href="#">Last 90 Days</a></li>
|
</li>
|
||||||
|
<li>
|
||||||
|
<a class="dropdown-item" href="#">Last 7 Days</a>
|
||||||
|
</li>
|
||||||
|
<li>
|
||||||
|
<a class="dropdown-item" href="#">Last 90 Days</a>
|
||||||
|
</li>
|
||||||
</ul>
|
</ul>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class="row g-4 mb-5">
|
<div class="row g-4 mb-5">
|
||||||
<div class="col-sm-6 col-md-4 col-lg-3">
|
<div class="col-sm-6 col-md-4 col-lg-3">
|
||||||
<div class="card h-100 shadow-sm border-0">
|
<div class="card h-100 shadow-sm border-0">
|
||||||
@ -122,7 +129,6 @@
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class="row g-4 mb-5">
|
<div class="row g-4 mb-5">
|
||||||
<div class="col-lg-8">
|
<div class="col-lg-8">
|
||||||
<div class="card h-100 shadow-sm border-0">
|
<div class="card h-100 shadow-sm border-0">
|
||||||
@ -139,20 +145,21 @@
|
|||||||
<div class="card-header bg-white border-bottom-0">
|
<div class="card-header bg-white border-bottom-0">
|
||||||
<h5 class="fw-bold mb-0 text-dark">Monthly Cars Sold</h5>
|
<h5 class="fw-bold mb-0 text-dark">Monthly Cars Sold</h5>
|
||||||
</div>
|
</div>
|
||||||
<div class="card-body d-flex align-items-center justify-content-center" style="height: 400px;">
|
<div class="card-body d-flex align-items-center justify-content-center"
|
||||||
|
style="height: 400px">
|
||||||
<canvas id="CarsSoldByMonthChart"></canvas>
|
<canvas id="CarsSoldByMonthChart"></canvas>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class="row g-4">
|
<div class="row g-4">
|
||||||
<div class="col-lg-6">
|
<div class="col-lg-6">
|
||||||
<div class="card h-100 shadow-sm border-0">
|
<div class="card h-100 shadow-sm border-0">
|
||||||
<div class="card-header bg-white border-bottom-0">
|
<div class="card-header bg-white border-bottom-0">
|
||||||
<h5 class="fw-bold mb-0 text-dark">Sales by Make</h5>
|
<h5 class="fw-bold mb-0 text-dark">Sales by Make</h5>
|
||||||
</div>
|
</div>
|
||||||
<div class="card-body d-flex align-items-center justify-content-center" style="height: 400px;">
|
<div class="card-body d-flex align-items-center justify-content-center"
|
||||||
|
style="height: 400px">
|
||||||
<canvas id="salesByBrandChart"></canvas>
|
<canvas id="salesByBrandChart"></canvas>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
@ -168,14 +175,11 @@
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
</div>
|
||||||
</div>
|
<script src="https://cdn.jsdelivr.net/npm/chart.js"></script>
|
||||||
<script src="https://cdn.jsdelivr.net/npm/chart.js"></script>
|
|
||||||
{% endblock content %}
|
{% endblock content %}
|
||||||
|
|
||||||
|
|
||||||
{% block customJS %}
|
{% block customJS %}
|
||||||
<script>
|
<script>
|
||||||
// Define a color palette that aligns with the Phoenix template
|
// Define a color palette that aligns with the Phoenix template
|
||||||
const primaryColor = '#7249b6'; // A vibrant purple
|
const primaryColor = '#7249b6'; // A vibrant purple
|
||||||
const secondaryColor = '#8193a6'; // A muted gray/blue
|
const secondaryColor = '#8193a6'; // A muted gray/blue
|
||||||
@ -363,5 +367,5 @@
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
</script>
|
</script>
|
||||||
{% endblock %}
|
{% endblock %}
|
||||||
@ -1,22 +1,30 @@
|
|||||||
{% extends 'base.html' %}
|
{% extends 'base.html' %}
|
||||||
{% load i18n %}
|
{% load i18n %}
|
||||||
|
|
||||||
{% block content %}
|
{% block content %}
|
||||||
|
<div class="main-content flex-grow-1 container-fluid mt-4 mb-3">
|
||||||
<div class="main-content flex-grow-1 container-fluid mt-4 mb-3">
|
|
||||||
<div class="d-flex flex-column flex-md-row justify-content-between align-items-md-center mb-5 pb-3 border-bottom">
|
<div class="d-flex flex-column flex-md-row justify-content-between align-items-md-center mb-5 pb-3 border-bottom">
|
||||||
<h2 class="h3 fw-bold mb-3 mb-md-0">{% trans "Sales Dashboard" %} <i class="fas fa-chart-area text-primary ms-2"></i></h2>
|
<h2 class="h3 fw-bold mb-3 mb-md-0">
|
||||||
|
{% trans "Sales Dashboard" %} <i class="fas fa-chart-area text-primary ms-2"></i>
|
||||||
|
</h2>
|
||||||
<form method="GET" class="date-filter-form">
|
<form method="GET" class="date-filter-form">
|
||||||
<div class="row g-3">
|
<div class="row g-3">
|
||||||
<div class="col-12 col-md-4">
|
<div class="col-12 col-md-4">
|
||||||
<label for="start-date" class="form-label">{% trans "Start Date" %}</label>
|
<label for="start-date" class="form-label">{% trans "Start Date" %}</label>
|
||||||
<input type="date" class="form-control" id="start-date" name="start_date"
|
<input type="date"
|
||||||
value="{{ start_date|date:'Y-m-d' }}" required>
|
class="form-control"
|
||||||
|
id="start-date"
|
||||||
|
name="start_date"
|
||||||
|
value="{{ start_date|date:'Y-m-d' }}"
|
||||||
|
required>
|
||||||
</div>
|
</div>
|
||||||
<div class="col-12 col-md-4">
|
<div class="col-12 col-md-4">
|
||||||
<label for="end-date" class="form-label">{% trans "End Date" %}</label>
|
<label for="end-date" class="form-label">{% trans "End Date" %}</label>
|
||||||
<input type="date" class="form-control" id="end-date" name="end_date"
|
<input type="date"
|
||||||
value="{{ end_date|date:'Y-m-d' }}" required>
|
class="form-control"
|
||||||
|
id="end-date"
|
||||||
|
name="end_date"
|
||||||
|
value="{{ end_date|date:'Y-m-d' }}"
|
||||||
|
required>
|
||||||
</div>
|
</div>
|
||||||
<div class="col-12 col-md-4 d-flex align-items-end">
|
<div class="col-12 col-md-4 d-flex align-items-end">
|
||||||
<button type="submit" class="btn btn-primary w-100">{% trans "Apply Filter" %}</button>
|
<button type="submit" class="btn btn-primary w-100">{% trans "Apply Filter" %}</button>
|
||||||
@ -24,7 +32,6 @@
|
|||||||
</div>
|
</div>
|
||||||
</form>
|
</form>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class="row g-4 mb-5">
|
<div class="row g-4 mb-5">
|
||||||
<h3 class="fw-bold mb-3">{% trans "Inventory KPIs" %}</h3>
|
<h3 class="fw-bold mb-3">{% trans "Inventory KPIs" %}</h3>
|
||||||
<div class="col-sm-6 col-md-4 col-lg-3">
|
<div class="col-sm-6 col-md-4 col-lg-3">
|
||||||
@ -35,7 +42,6 @@
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class="col-sm-6 col-md-4 col-lg-3">
|
<div class="col-sm-6 col-md-4 col-lg-3">
|
||||||
<div class="card h-100 shadow-sm border-0">
|
<div class="card h-100 shadow-sm border-0">
|
||||||
<div class="card-body p-4">
|
<div class="card-body p-4">
|
||||||
@ -52,50 +58,50 @@
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class="col-sm-6 col-md-4 col-lg-3">
|
<div class="col-sm-6 col-md-4 col-lg-3">
|
||||||
<div class="card h-100 shadow-sm border-0">
|
<div class="card h-100 shadow-sm border-0">
|
||||||
<div class="card-body p-4">
|
<div class="card-body p-4">
|
||||||
<p class="text-uppercase text-danger fw-bold small mb-1"><a class="text-danger" href="{% url 'aging_inventory_list' request.dealer.slug %}">{% trans "Aging Inventory (> 60 days)" %}</a></p>
|
<p class="text-uppercase text-danger fw-bold small mb-1">
|
||||||
<h4 class="fw-bolder text-danger mb-3"><a class="text-danger" href="{% url 'aging_inventory_list' request.dealer.slug %}">{{ aging_inventory_count }}</a></h4>
|
<a class="text-danger"
|
||||||
|
href="{% url 'aging_inventory_list' request.dealer.slug %}">{% trans "Aging Inventory (> 60 days)" %}</a>
|
||||||
|
</p>
|
||||||
|
<h4 class="fw-bolder text-danger mb-3">
|
||||||
|
<a class="text-danger"
|
||||||
|
href="{% url 'aging_inventory_list' request.dealer.slug %}">{{ aging_inventory_count }}</a>
|
||||||
|
</h4>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class="row g-4 mb-5">
|
<div class="row g-4 mb-5">
|
||||||
<div class="col-md-6">
|
<div class="col-md-6">
|
||||||
<div class="card h-100 shadow-sm border-0">
|
<div class="card h-100 shadow-sm border-0">
|
||||||
<div class="card-header bg-white border-bottom-0">
|
<div class="card-header bg-white border-bottom-0">
|
||||||
<h5 class="fw-bold mb-0 text-dark">{% trans "Top Lead Sources" %}</h5>
|
<h5 class="fw-bold mb-0 text-dark">{% trans "Top Lead Sources" %}</h5>
|
||||||
</div>
|
</div>
|
||||||
<div class="card-body d-flex align-items-center justify-content-center" style="height: 400px;">
|
<div class="card-body d-flex align-items-center justify-content-center"
|
||||||
|
style="height: 400px">
|
||||||
<canvas id="leadSourcesChart"></canvas>
|
<canvas id="leadSourcesChart"></canvas>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class="col-md-6">
|
<div class="col-md-6">
|
||||||
<div class="card h-100 shadow-sm border-0">
|
<div class="card h-100 shadow-sm border-0">
|
||||||
<div class="card-header bg-white border-bottom-0">
|
<div class="card-header bg-white border-bottom-0">
|
||||||
<h5 class="fw-bold mb-0 text-dark">{% trans "Lead Conversion Funnel" %}</h5>
|
<h5 class="fw-bold mb-0 text-dark">{% trans "Lead Conversion Funnel" %}</h5>
|
||||||
</div>
|
</div>
|
||||||
<div class="card-body d-flex align-items-center justify-content-center" style="height: 400px;">
|
<div class="card-body d-flex align-items-center justify-content-center"
|
||||||
|
style="height: 400px">
|
||||||
<canvas id="leadFunnelChart"></canvas>
|
<canvas id="leadFunnelChart"></canvas>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
</div>
|
</div>
|
||||||
|
</div>
|
||||||
</div>
|
<script src="https://cdn.jsdelivr.net/npm/chart.js"></script>
|
||||||
|
|
||||||
<script src="https://cdn.jsdelivr.net/npm/chart.js"></script>
|
|
||||||
{% endblock content %}
|
{% endblock content %}
|
||||||
|
{% block customJS %}
|
||||||
|
<script>
|
||||||
{% block customJS%}
|
|
||||||
<script>
|
|
||||||
// Define your color palette at the top
|
// Define your color palette at the top
|
||||||
const primaryColor = '#7249b6'; // A vibrant purple
|
const primaryColor = '#7249b6'; // A vibrant purple
|
||||||
const secondaryColor = '#8193a6'; // A muted gray/blue
|
const secondaryColor = '#8193a6'; // A muted gray/blue
|
||||||
@ -265,5 +271,5 @@
|
|||||||
leadFunnelMessage.style.display = 'flex';
|
leadFunnelMessage.style.display = 'flex';
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
</script>
|
</script>
|
||||||
{% endblock %}
|
{% endblock %}
|
||||||
@ -1,8 +1,9 @@
|
|||||||
{% extends 'base.html' %}
|
{% extends 'base.html' %}
|
||||||
{% load i18n static %}
|
{% load i18n static %}
|
||||||
{% block title %}
|
{% block title %}
|
||||||
{% trans 'Activity' %}{% endblock %}
|
{% trans 'Activity' %}
|
||||||
{% block content %}
|
{% endblock %}
|
||||||
|
{% block content %}
|
||||||
<div class="row">
|
<div class="row">
|
||||||
<div class="ol-auto pt-5 pb-9">
|
<div class="ol-auto pt-5 pb-9">
|
||||||
<div class="row-sm">
|
<div class="row-sm">
|
||||||
@ -90,4 +91,4 @@
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
{% endblock %}
|
{% endblock %}
|
||||||
|
|||||||
@ -1,8 +1,9 @@
|
|||||||
{% extends "base.html" %}
|
{% extends "base.html" %}
|
||||||
{% load i18n static %}
|
{% load i18n static %}
|
||||||
{% block title %}
|
{% block title %}
|
||||||
{% trans 'Car Makes' %}{% endblock %}
|
{% trans 'Car Makes' %}
|
||||||
{% block content %}
|
{% endblock %}
|
||||||
|
{% block content %}
|
||||||
<style>
|
<style>
|
||||||
/* Your existing CSS styles here */
|
/* Your existing CSS styles here */
|
||||||
.car-makes-grid {
|
.car-makes-grid {
|
||||||
@ -57,7 +58,8 @@
|
|||||||
}
|
}
|
||||||
</style>
|
</style>
|
||||||
<h2 class="text-center text-primary">{{ _("Select Car Makes You Sell") }}</h2>
|
<h2 class="text-center text-primary">{{ _("Select Car Makes You Sell") }}</h2>
|
||||||
<form method="post" class="mb-3"
|
<form method="post"
|
||||||
|
class="mb-3"
|
||||||
action="{% url 'assign_car_makes' request.dealer.slug %}">
|
action="{% url 'assign_car_makes' request.dealer.slug %}">
|
||||||
{% csrf_token %}
|
{% csrf_token %}
|
||||||
<div class="car-makes-grid">
|
<div class="car-makes-grid">
|
||||||
@ -66,9 +68,7 @@
|
|||||||
<input type="checkbox"
|
<input type="checkbox"
|
||||||
name="car_makes"
|
name="car_makes"
|
||||||
value="{{ car_make.pk }}"
|
value="{{ car_make.pk }}"
|
||||||
{% if car_make.pk in form.initial.car_makes or car_make.pk|stringformat:"s" in form.car_makes.value %}
|
{% if car_make.pk in form.initial.car_makes or car_make.pk|stringformat:"s" in form.car_makes.value %} checked {% endif %}>
|
||||||
checked
|
|
||||||
{% endif %}>
|
|
||||||
<div class="car-make-image-container">
|
<div class="car-make-image-container">
|
||||||
{% if car_make.logo and car_make.logo.url %}
|
{% if car_make.logo and car_make.logo.url %}
|
||||||
<img src="{{ car_make.logo.url }}"
|
<img src="{{ car_make.logo.url }}"
|
||||||
@ -88,4 +88,4 @@
|
|||||||
</button>
|
</button>
|
||||||
</div>
|
</div>
|
||||||
</form>
|
</form>
|
||||||
{% endblock %}
|
{% endblock %}
|
||||||
|
|||||||
@ -3,29 +3,41 @@
|
|||||||
{% block title %}
|
{% block title %}
|
||||||
{% trans 'Profile' %}
|
{% trans 'Profile' %}
|
||||||
{% endblock %}
|
{% endblock %}
|
||||||
|
|
||||||
{% block content %}
|
{% block content %}
|
||||||
<div class="container-fluid mb-3">
|
<div class="container-fluid mb-3">
|
||||||
<div class="row align-items-center justify-content-between g-3 mb-4">
|
<div class="row align-items-center justify-content-between g-3 mb-4">
|
||||||
<div class="col-auto">
|
<div class="col-auto">
|
||||||
<h2 class="mb-0">{% trans 'Profile' %}</h2>
|
<h2 class="mb-0">{% trans 'Profile' %}</h2>
|
||||||
</div>
|
</div>
|
||||||
<div class="col-auto">
|
<div class="col-auto">
|
||||||
<div class="dropdown">
|
<div class="dropdown">
|
||||||
<button class="btn btn-phoenix-primary dropdown-toggle" type="button" data-bs-toggle="dropdown" aria-expanded="false">
|
<button class="btn btn-phoenix-primary dropdown-toggle"
|
||||||
|
type="button"
|
||||||
|
data-bs-toggle="dropdown"
|
||||||
|
aria-expanded="false">
|
||||||
<span class="fas fa-cog me-2"></span>{{ _("Manage Profile") }}
|
<span class="fas fa-cog me-2"></span>{{ _("Manage Profile") }}
|
||||||
</button>
|
</button>
|
||||||
<ul class="dropdown-menu dropdown-menu-end">
|
<ul class="dropdown-menu dropdown-menu-end">
|
||||||
<li><a class="dropdown-item" href="{% url 'dealer_update' dealer.slug %}"><span class="fas fa-edit me-2"></span>{{ _("Edit Profile") }}</a></li>
|
<li>
|
||||||
<li><a class="dropdown-item" href="{% url 'billing_info' %}"><span class="fas fa-credit-card me-2"></span>{{ _("Billing Information") }}</a></li>
|
<a class="dropdown-item" href="{% url 'dealer_update' dealer.slug %}"><span class="fas fa-edit me-2"></span>{{ _("Edit Profile") }}</a>
|
||||||
<li><a class="dropdown-item" href="{% url 'order_list' %}"><span class="fas fa-clipboard-list me-2"></span>{{ _("Plans History") }}</a></li>
|
</li>
|
||||||
<li><hr class="dropdown-divider"></li>
|
<li>
|
||||||
<li><a class="dropdown-item text-danger" href="{% url 'account_change_password' %}"><span class="fas fa-key me-2"></span>{{ _("Change Password") }}</a></li>
|
<a class="dropdown-item" href="{% url 'billing_info' %}"><span class="fas fa-credit-card me-2"></span>{{ _("Billing Information") }}</a>
|
||||||
|
</li>
|
||||||
|
<li>
|
||||||
|
<a class="dropdown-item" href="{% url 'order_list' %}"><span class="fas fa-clipboard-list me-2"></span>{{ _("Plans History") }}</a>
|
||||||
|
</li>
|
||||||
|
<li>
|
||||||
|
<hr class="dropdown-divider">
|
||||||
|
</li>
|
||||||
|
<li>
|
||||||
|
<a class="dropdown-item text-danger"
|
||||||
|
href="{% url 'account_change_password' %}"><span class="fas fa-key me-2"></span>{{ _("Change Password") }}</a>
|
||||||
|
</li>
|
||||||
</ul>
|
</ul>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class="row g-3 mb-4">
|
<div class="row g-3 mb-4">
|
||||||
<div class="col-12">
|
<div class="col-12">
|
||||||
<div class="card shadow-sm h-100">
|
<div class="card shadow-sm h-100">
|
||||||
@ -33,34 +45,49 @@
|
|||||||
<div class="d-flex flex-column flex-sm-row align-items-sm-center g-3 g-sm-5 text-center text-sm-start">
|
<div class="d-flex flex-column flex-sm-row align-items-sm-center g-3 g-sm-5 text-center text-sm-start">
|
||||||
<div class="col-12 col-sm-auto mb-3 mb-sm-0">
|
<div class="col-12 col-sm-auto mb-3 mb-sm-0">
|
||||||
<input class="d-none" id="avatarFile" type="file" />
|
<input class="d-none" id="avatarFile" type="file" />
|
||||||
<label class="cursor-pointer avatar avatar-5xl border rounded-circle shadow-sm" for="avatarFile">
|
<label class="cursor-pointer avatar avatar-5xl border rounded-circle shadow-sm"
|
||||||
|
for="avatarFile">
|
||||||
{% if dealer.logo %}
|
{% if dealer.logo %}
|
||||||
<img src="{{ dealer.logo.url }}" alt="{{ dealer.get_local_name }}" class="rounded-circle" style="max-width: 150px" />
|
<img src="{{ dealer.logo.url }}"
|
||||||
|
alt="{{ dealer.get_local_name }}"
|
||||||
|
class="rounded-circle"
|
||||||
|
style="max-width: 150px" />
|
||||||
{% else %}
|
{% else %}
|
||||||
<img src="{% static 'images/logos/logo.png' %}" alt="{{ dealer.get_local_name }}" class="rounded-circle" style="max-width: 150px" />
|
<img src="{% static 'images/logos/logo.png' %}"
|
||||||
|
alt="{{ dealer.get_local_name }}"
|
||||||
|
class="rounded-circle"
|
||||||
|
style="max-width: 150px" />
|
||||||
{% endif %}
|
{% endif %}
|
||||||
</label>
|
</label>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class="flex-1 col-12 col-sm ms-2">
|
<div class="flex-1 col-12 col-sm ms-2">
|
||||||
<h3>{{ dealer.get_local_name }}</h3>
|
<h3>{{ dealer.get_local_name }}</h3>
|
||||||
<p class="text-body-secondary mb-1">{% trans 'Joined' %} {{ dealer.joined_at|timesince }} {% trans 'ago' %}</p>
|
<p class="text-body-secondary mb-1">{% trans 'Joined' %} {{ dealer.joined_at|timesince }} {% trans 'ago' %}</p>
|
||||||
<span class="badge bg-primary-subtle text-primary">{% trans 'Last login' %}: {{ dealer.user.last_login|date:"D M d, Y H:i" }}</span>
|
<span class="badge bg-primary-subtle text-primary">{% trans 'Last login' %}: {{ dealer.user.last_login|date:"D M d, Y H:i" }}</span>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class="col-12 col-sm-auto d-flex align-items-center justify-content-around flex-wrap mt-3 mt-sm-0">
|
<div class="col-12 col-sm-auto d-flex align-items-center justify-content-around flex-wrap mt-3 mt-sm-0">
|
||||||
<div class="text-center mx-3 mb-2 mb-sm-0">
|
<div class="text-center mx-3 mb-2 mb-sm-0">
|
||||||
<h6 class="mb-2 text-body-secondary">{% trans 'Total users'|capfirst %}</h6>
|
<h6 class="mb-2 text-body-secondary">{% trans 'Total users'|capfirst %}</h6>
|
||||||
<h4 class="fs-7 text-body-highlight mb-2">{{ dealer.staff_count }} / {{ allowed_users }}</h4>
|
<h4 class="fs-7 text-body-highlight mb-2">{{ dealer.staff_count }} / {{ allowed_users }}</h4>
|
||||||
<div class="progress" style="height: 5px; width: 100px;">
|
<div class="progress" style="height: 5px; width: 100px;">
|
||||||
<div class="progress-bar bg-success" role="progressbar" style="width: {{ dealer.staff_count|get_percentage:allowed_users }}%;" aria-valuenow="{{ dealer.staff_count|get_percentage:allowed_users }}" aria-valuemin="0" aria-valuemax="100"></div>
|
<div class="progress-bar bg-success"
|
||||||
|
role="progressbar"
|
||||||
|
style="width: {{ dealer.staff_count|get_percentage:allowed_users }}%"
|
||||||
|
aria-valuenow="{{ dealer.staff_count|get_percentage:allowed_users }}"
|
||||||
|
aria-valuemin="0"
|
||||||
|
aria-valuemax="100"></div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div class="text-center mx-3 mb-2 mb-sm-0">
|
<div class="text-center mx-3 mb-2 mb-sm-0">
|
||||||
<h6 class="mb-2 text-body-secondary">{% trans 'Total cars'|capfirst %}</h6>
|
<h6 class="mb-2 text-body-secondary">{% trans 'Total cars'|capfirst %}</h6>
|
||||||
<h4 class="fs-7 text-body-highlight mb-2">{{ cars_count }} / {{ allowed_cars }}</h4>
|
<h4 class="fs-7 text-body-highlight mb-2">{{ cars_count }} / {{ allowed_cars }}</h4>
|
||||||
<div class="progress" style="height: 5px; width: 100px;">
|
<div class="progress" style="height: 5px; width: 100px;">
|
||||||
<div class="progress-bar bg-info" role="progressbar" style="width: {{ cars_count|get_percentage:allowed_cars }}%;" aria-valuenow="{{ cars_count|get_percentage:allowed_cars }}" aria-valuemin="0" aria-valuemax="100"></div>
|
<div class="progress-bar bg-info"
|
||||||
|
role="progressbar"
|
||||||
|
style="width: {{ cars_count|get_percentage:allowed_cars }}%"
|
||||||
|
aria-valuenow="{{ cars_count|get_percentage:allowed_cars }}"
|
||||||
|
aria-valuemin="0"
|
||||||
|
aria-valuemax="100"></div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
@ -69,24 +96,53 @@
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class="row g-3">
|
<div class="row g-3">
|
||||||
<div class="col-12">
|
<div class="col-12">
|
||||||
<div class="card shadow-sm">
|
<div class="card shadow-sm">
|
||||||
<div class="card-body">
|
<div class="card-body">
|
||||||
<ul class="nav nav-tabs nav-justified" id="profileTabs" role="tablist">
|
<ul class="nav nav-tabs nav-justified" id="profileTabs" role="tablist">
|
||||||
<li class="nav-item" role="presentation">
|
<li class="nav-item" role="presentation">
|
||||||
<button class="nav-link active" id="subscription-tab" data-bs-toggle="tab" data-bs-target="#subscription-pane" type="button" role="tab" aria-controls="subscription-pane" aria-selected="true"><span class="fas fa-star me-2"></span>{{ _("Plan & Subscription") }}</button>
|
<button class="nav-link active"
|
||||||
|
id="subscription-tab"
|
||||||
|
data-bs-toggle="tab"
|
||||||
|
data-bs-target="#subscription-pane"
|
||||||
|
type="button"
|
||||||
|
role="tab"
|
||||||
|
aria-controls="subscription-pane"
|
||||||
|
aria-selected="true">
|
||||||
|
<span class="fas fa-star me-2"></span>{{ _("Plan & Subscription") }}
|
||||||
|
</button>
|
||||||
</li>
|
</li>
|
||||||
<li class="nav-item" role="presentation">
|
<li class="nav-item" role="presentation">
|
||||||
<button class="nav-link" id="contact-tab" data-bs-toggle="tab" data-bs-target="#contact-pane" type="button" role="tab" aria-controls="contact-pane" aria-selected="false"><span class="fas fa-info-circle me-2"></span>{{ _("Company Details") }}</button>
|
<button class="nav-link"
|
||||||
|
id="contact-tab"
|
||||||
|
data-bs-toggle="tab"
|
||||||
|
data-bs-target="#contact-pane"
|
||||||
|
type="button"
|
||||||
|
role="tab"
|
||||||
|
aria-controls="contact-pane"
|
||||||
|
aria-selected="false">
|
||||||
|
<span class="fas fa-info-circle me-2"></span>{{ _("Company Details") }}
|
||||||
|
</button>
|
||||||
</li>
|
</li>
|
||||||
<li class="nav-item" role="presentation">
|
<li class="nav-item" role="presentation">
|
||||||
<button class="nav-link" id="makes-tab" data-bs-toggle="tab" data-bs-target="#makes-pane" type="button" role="tab" aria-controls="makes-pane" aria-selected="false"><span class="fas fa-car me-2"></span>{{ _("Car Brands") }}</button>
|
<button class="nav-link"
|
||||||
|
id="makes-tab"
|
||||||
|
data-bs-toggle="tab"
|
||||||
|
data-bs-target="#makes-pane"
|
||||||
|
type="button"
|
||||||
|
role="tab"
|
||||||
|
aria-controls="makes-pane"
|
||||||
|
aria-selected="false">
|
||||||
|
<span class="fas fa-car me-2"></span>{{ _("Car Brands") }}
|
||||||
|
</button>
|
||||||
</li>
|
</li>
|
||||||
</ul>
|
</ul>
|
||||||
<div class="tab-content pt-4" id="profileTabsContent">
|
<div class="tab-content pt-4" id="profileTabsContent">
|
||||||
<div class="tab-pane fade show active" id="subscription-pane" role="tabpanel" aria-labelledby="subscription-tab">
|
<div class="tab-pane fade show active"
|
||||||
|
id="subscription-pane"
|
||||||
|
role="tabpanel"
|
||||||
|
aria-labelledby="subscription-tab">
|
||||||
<div class="row g-3">
|
<div class="row g-3">
|
||||||
<div class="col-12 col-lg-6">
|
<div class="col-12 col-lg-6">
|
||||||
<div class="card h-100 shadow-sm">
|
<div class="card h-100 shadow-sm">
|
||||||
@ -108,14 +164,12 @@
|
|||||||
{% trans 'Please subscribe or renew your plan to continue using our services.' %}
|
{% trans 'Please subscribe or renew your plan to continue using our services.' %}
|
||||||
{% endif %}
|
{% endif %}
|
||||||
</p>
|
</p>
|
||||||
|
|
||||||
<div class="d-flex align-items-end mb-3">
|
<div class="d-flex align-items-end mb-3">
|
||||||
<h4 class="fw-bolder me-1">
|
<h4 class="fw-bolder me-1">
|
||||||
{{ dealer.user.userplan.plan.planpricing_set.first.price }} <span class="icon-saudi_riyal"></span>
|
{{ dealer.user.userplan.plan.planpricing_set.first.price }} <span class="icon-saudi_riyal"></span>
|
||||||
</h4>
|
</h4>
|
||||||
<h5 class="fs-9 fw-normal text-body-tertiary ms-1">{{ _("Per month") }}</h5>
|
<h5 class="fs-9 fw-normal text-body-tertiary ms-1">{{ _("Per month") }}</h5>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<ul class="list-unstyled mb-4">
|
<ul class="list-unstyled mb-4">
|
||||||
{% for line in dealer.user.userplan.plan.description|splitlines %}
|
{% for line in dealer.user.userplan.plan.description|splitlines %}
|
||||||
<li class="d-flex align-items-center mb-1">
|
<li class="d-flex align-items-center mb-1">
|
||||||
@ -124,7 +178,6 @@
|
|||||||
</li>
|
</li>
|
||||||
{% endfor %}
|
{% endfor %}
|
||||||
</ul>
|
</ul>
|
||||||
|
|
||||||
{% comment %} <div class="d-flex justify-content-end gap-2">
|
{% comment %} <div class="d-flex justify-content-end gap-2">
|
||||||
{% if dealer.user.userplan.is_expired %}
|
{% if dealer.user.userplan.is_expired %}
|
||||||
<a href="{% url 'pricing_page' request.dealer.slug %}" class="btn btn-warning"><span class="fas fa-redo-alt me-2"></span>{{ _("Renew") }}</a>
|
<a href="{% url 'pricing_page' request.dealer.slug %}" class="btn btn-warning"><span class="fas fa-redo-alt me-2"></span>{{ _("Renew") }}</a>
|
||||||
@ -138,11 +191,14 @@
|
|||||||
</div> {% endcomment %}
|
</div> {% endcomment %}
|
||||||
<div class="d-flex justify-content-end gap-2">
|
<div class="d-flex justify-content-end gap-2">
|
||||||
{% if not dealer.user.userplan %}
|
{% if not dealer.user.userplan %}
|
||||||
<a href="{% url 'pricing_page' request.dealer.slug %}" class="btn btn-outline-primary"><span class="fas fa-cart-plus me-2"></span>{{ _("Subscribe Now") }}</a>
|
<a href="{% url 'pricing_page' request.dealer.slug %}"
|
||||||
|
class="btn btn-outline-primary"><span class="fas fa-cart-plus me-2"></span>{{ _("Subscribe Now") }}</a>
|
||||||
{% elif dealer.user.userplan.is_expired %}
|
{% elif dealer.user.userplan.is_expired %}
|
||||||
<a href="{% url 'pricing_page' request.dealer.slug %}" class="btn btn-outline-warning"><span class="fas fa-redo-alt me-2"></span>{{ _("Renew") }}</a>
|
<a href="{% url 'pricing_page' request.dealer.slug %}"
|
||||||
|
class="btn btn-outline-warning"><span class="fas fa-redo-alt me-2"></span>{{ _("Renew") }}</a>
|
||||||
{% elif dealer.user.userplan.plan.name != "Enterprise" %}
|
{% elif dealer.user.userplan.plan.name != "Enterprise" %}
|
||||||
<a href="{% url 'pricing_page' request.dealer.slug %}" class="btn btn-outline-primary"><span class="fas fa-rocket me-2"></span>{{ _("Upgrade Plan") }}</a>
|
<a href="{% url 'pricing_page' request.dealer.slug %}"
|
||||||
|
class="btn btn-outline-primary"><span class="fas fa-rocket me-2"></span>{{ _("Upgrade Plan") }}</a>
|
||||||
{% endif %}
|
{% endif %}
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
@ -157,7 +213,12 @@
|
|||||||
<div class="mb-4">
|
<div class="mb-4">
|
||||||
<h6 class="text-body-secondary">{{ _("Total users") }}</h6>
|
<h6 class="text-body-secondary">{{ _("Total users") }}</h6>
|
||||||
<div class="progress" style="height: 10px;">
|
<div class="progress" style="height: 10px;">
|
||||||
<div class="progress-bar bg-success" role="progressbar" style="width: {{ dealer.staff_count|get_percentage:allowed_users }}%;" aria-valuenow="{{ dealer.staff_count|get_percentage:allowed_users }}" aria-valuemin="0" aria-valuemax="100"></div>
|
<div class="progress-bar bg-success"
|
||||||
|
role="progressbar"
|
||||||
|
style="width: {{ dealer.staff_count|get_percentage:allowed_users }}%"
|
||||||
|
aria-valuenow="{{ dealer.staff_count|get_percentage:allowed_users }}"
|
||||||
|
aria-valuemin="0"
|
||||||
|
aria-valuemax="100"></div>
|
||||||
</div>
|
</div>
|
||||||
<div class="d-flex justify-content-between text-body-secondary fs-9 mt-2">
|
<div class="d-flex justify-content-between text-body-secondary fs-9 mt-2">
|
||||||
<span>{{ _("Used") }}: {{ dealer.staff_count }}</span>
|
<span>{{ _("Used") }}: {{ dealer.staff_count }}</span>
|
||||||
@ -167,7 +228,12 @@
|
|||||||
<div class="mb-4">
|
<div class="mb-4">
|
||||||
<h6 class="text-body-secondary">{{ _("Total cars") }}</h6>
|
<h6 class="text-body-secondary">{{ _("Total cars") }}</h6>
|
||||||
<div class="progress" style="height: 10px;">
|
<div class="progress" style="height: 10px;">
|
||||||
<div class="progress-bar bg-info" role="progressbar" style="width: {{ cars_count|get_percentage:allowed_cars }}%;" aria-valuenow="{{ cars_count|get_percentage:allowed_cars }}" aria-valuemin="0" aria-valuemax="100"></div>
|
<div class="progress-bar bg-info"
|
||||||
|
role="progressbar"
|
||||||
|
style="width: {{ cars_count|get_percentage:allowed_cars }}%"
|
||||||
|
aria-valuenow="{{ cars_count|get_percentage:allowed_cars }}"
|
||||||
|
aria-valuemin="0"
|
||||||
|
aria-valuemax="100"></div>
|
||||||
</div>
|
</div>
|
||||||
<div class="d-flex justify-content-between text-body-secondary fs-9 mt-2">
|
<div class="d-flex justify-content-between text-body-secondary fs-9 mt-2">
|
||||||
<span>{{ _("Used") }}: {{ cars_count }}</span>
|
<span>{{ _("Used") }}: {{ cars_count }}</span>
|
||||||
@ -180,8 +246,10 @@
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
<div class="tab-pane fade"
|
||||||
<div class="tab-pane fade" id="contact-pane" role="tabpanel" aria-labelledby="contact-tab">
|
id="contact-pane"
|
||||||
|
role="tabpanel"
|
||||||
|
aria-labelledby="contact-tab">
|
||||||
<div class="row g-3">
|
<div class="row g-3">
|
||||||
<div class="col-12 col-lg-6">
|
<div class="col-12 col-lg-6">
|
||||||
<div class="card h-100 shadow-sm">
|
<div class="card h-100 shadow-sm">
|
||||||
@ -215,18 +283,23 @@
|
|||||||
<div class="card h-100 shadow-sm">
|
<div class="card h-100 shadow-sm">
|
||||||
<div class="card-body">
|
<div class="card-body">
|
||||||
<h5 class="mb-3">{{ _("VAT Information") }}</h5>
|
<h5 class="mb-3">{{ _("VAT Information") }}</h5>
|
||||||
<form action="{% url 'dealer_vat_rate_update' request.dealer.slug %}" method="post">
|
<form action="{% url 'dealer_vat_rate_update' request.dealer.slug %}"
|
||||||
|
method="post">
|
||||||
{% csrf_token %}
|
{% csrf_token %}
|
||||||
{{ vatform|crispy }}
|
{{ vatform|crispy }}
|
||||||
<button class="btn btn-phoenix-primary mt-3" type="submit"><i class="fa-solid fa-pen-to-square me-1"></i>{% trans 'Update VAT' %}</button>
|
<button class="btn btn-phoenix-primary mt-3" type="submit">
|
||||||
|
<i class="fa-solid fa-pen-to-square me-1"></i>{% trans 'Update VAT' %}
|
||||||
|
</button>
|
||||||
</form>
|
</form>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
<div class="tab-pane fade"
|
||||||
<div class="tab-pane fade" id="makes-pane" role="tabpanel" aria-labelledby="makes-tab">
|
id="makes-pane"
|
||||||
|
role="tabpanel"
|
||||||
|
aria-labelledby="makes-tab">
|
||||||
<div class="card h-100 shadow-sm">
|
<div class="card h-100 shadow-sm">
|
||||||
<div class="card-body">
|
<div class="card-body">
|
||||||
<h5 class="mb-4">{{ _("Makes you are selling") }}</h5>
|
<h5 class="mb-4">{{ _("Makes you are selling") }}</h5>
|
||||||
@ -234,7 +307,12 @@
|
|||||||
{% for make in car_makes %}
|
{% for make in car_makes %}
|
||||||
<div class="text-center p-2 border rounded-3">
|
<div class="text-center p-2 border rounded-3">
|
||||||
{% if make.logo %}
|
{% if make.logo %}
|
||||||
<img src="{{ make.logo.url }}" alt="{{ make.get_local_name }}" class="rounded" style="height: 48px; width: auto; background-color:white;" />
|
<img src="{{ make.logo.url }}"
|
||||||
|
alt="{{ make.get_local_name }}"
|
||||||
|
class="rounded"
|
||||||
|
style="height: 48px;
|
||||||
|
width: auto;
|
||||||
|
background-color:white" />
|
||||||
{% endif %}
|
{% endif %}
|
||||||
<p class="fs-8 text-body-secondary mt-1 mb-0">{{ make.get_local_name }}</p>
|
<p class="fs-8 text-body-secondary mt-1 mb-0">{{ make.get_local_name }}</p>
|
||||||
</div>
|
</div>
|
||||||
@ -242,7 +320,9 @@
|
|||||||
<p class="text-body-secondary">{{ _("No car makes selected.") }}</p>
|
<p class="text-body-secondary">{{ _("No car makes selected.") }}</p>
|
||||||
{% endfor %}
|
{% endfor %}
|
||||||
</div>
|
</div>
|
||||||
<a class="btn btn-phoenix-warning" href="{% url 'assign_car_makes' request.dealer.slug %}"><span class="fas fa-plus me-2"></span>{{ _("Select Makes") }}</a>
|
<a class="btn btn-phoenix-warning"
|
||||||
|
href="{% url 'assign_car_makes' request.dealer.slug %}"><span class="fas fa-plus me-2"></span>{{ _("Select Makes") }}</a>
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
@ -251,5 +331,4 @@
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
|
||||||
{% endblock %}
|
{% endblock %}
|
||||||
@ -5,7 +5,7 @@
|
|||||||
{{ _("Update Dealer Information") }}
|
{{ _("Update Dealer Information") }}
|
||||||
{% endblock title %}
|
{% endblock title %}
|
||||||
{% block content %}
|
{% block content %}
|
||||||
<main class="d-flex align-items-center justify-content-center min-vh-80 py-5">
|
<main class="d-flex align-items-center justify-content-center min-vh-80 py-5">
|
||||||
<div class="col-md-8">
|
<div class="col-md-8">
|
||||||
<div class="card shadow-lg border-0 rounded-4 overflow-hidden animate__animated animate__fadeInUp">
|
<div class="card shadow-lg border-0 rounded-4 overflow-hidden animate__animated animate__fadeInUp">
|
||||||
<div class="card-header bg-gradient py-4 border-0 rounded-top-4">
|
<div class="card-header bg-gradient py-4 border-0 rounded-top-4">
|
||||||
@ -15,7 +15,11 @@
|
|||||||
</h3>
|
</h3>
|
||||||
</div>
|
</div>
|
||||||
<div class="card-body p-4 p-md-5">
|
<div class="card-body p-4 p-md-5">
|
||||||
<form hx-boost="false" method="post" enctype="multipart/form-data" class="needs-validation" novalidate>
|
<form hx-boost="false"
|
||||||
|
method="post"
|
||||||
|
enctype="multipart/form-data"
|
||||||
|
class="needs-validation"
|
||||||
|
novalidate>
|
||||||
{% csrf_token %}
|
{% csrf_token %}
|
||||||
{{ form|crispy }}
|
{{ form|crispy }}
|
||||||
<hr class="my-4">
|
<hr class="my-4">
|
||||||
@ -34,5 +38,5 @@
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</main>
|
</main>
|
||||||
{% endblock %}
|
{% endblock %}
|
||||||
@ -170,10 +170,7 @@
|
|||||||
valign="top"
|
valign="top"
|
||||||
style="font-family: Open Sans, Helvetica, Arial, sans-serif;
|
style="font-family: Open Sans, Helvetica, Arial, sans-serif;
|
||||||
padding-bottom: 30px">
|
padding-bottom: 30px">
|
||||||
<p style="color: #ffffff;
|
<p style="color: #ffffff; font-size: 14px; line-height: 24px; margin: 0">{% trans 'Thank you for choosing us.' %}</p>
|
||||||
font-size: 14px;
|
|
||||||
line-height: 24px;
|
|
||||||
margin: 0">{% trans 'Thank you for choosing us.' %}</p>
|
|
||||||
</td>
|
</td>
|
||||||
</tr>
|
</tr>
|
||||||
</table>
|
</table>
|
||||||
|
|||||||
@ -1,18 +1,18 @@
|
|||||||
<!DOCTYPE html>
|
<!DOCTYPE html>
|
||||||
<html>
|
<html>
|
||||||
<body style="font-family: 'Segoe UI', Tahoma, sans-serif; direction: rtl;">
|
<body style="font-family: 'Segoe UI', Tahoma, sans-serif; direction: rtl;">
|
||||||
<p>مرحباً {{ user.get_full_name }}،</p>
|
<p>مرحباً {{ user.get_full_name }}،</p>
|
||||||
|
|
||||||
<p>
|
<p>
|
||||||
اشتراكك في <strong>{{ plan.name }}</strong> سينتهي خلال
|
اشتراكك في <strong>{{ plan.name }}</strong> سينتهي خلال
|
||||||
{{ days_until_expire }} يوم في {{ expiration_date|date:"j F Y" }}.
|
{{ days_until_expire }} يوم في {{ expiration_date|date:"j F Y" }}.
|
||||||
</p>
|
</p>
|
||||||
|
|
||||||
<p>
|
<p>
|
||||||
<a href="{{ RENEWAL_URL }}">جدد اشتراكك الآن</a> لمواصلة الخدمة.
|
<a href="{{ RENEWAL_URL }}">جدد اشتراكك الآن</a> لمواصلة الخدمة.
|
||||||
</p>
|
</p>
|
||||||
|
<p>
|
||||||
<p>مع أطيب التحيات،<br>
|
مع أطيب التحيات،
|
||||||
فريق تنحل</p>
|
<br>
|
||||||
</body>
|
فريق تنحل
|
||||||
|
</p>
|
||||||
|
</body>
|
||||||
</html>
|
</html>
|
||||||
@ -1,14 +1,18 @@
|
|||||||
<!DOCTYPE html>
|
<!DOCTYPE html>
|
||||||
<html>
|
<html>
|
||||||
<body style="font-family: Arial, sans-serif; direction: {{ direction }};">
|
<body style="font-family: Arial, sans-serif; direction: {{ direction }};">
|
||||||
<p>Hello {{ user.get_full_name }},</p>
|
<p>Hello {{ user.get_full_name }},</p>
|
||||||
|
<p>
|
||||||
<p>Your <strong>{{ plan.name }}</strong> subscription will expire
|
Your <strong>{{ plan.name }}</strong> subscription will expire
|
||||||
in {{ days_until_expire }} days on {{ expiration_date|date:"F j, Y" }}.</p>
|
in {{ days_until_expire }} days on {{ expiration_date|date:"F j, Y" }}.
|
||||||
|
</p>
|
||||||
<p><a href="{{ RENEWAL_URL }}">Renew now</a> to continue service.</p>
|
<p>
|
||||||
|
<a href="{{ RENEWAL_URL }}">Renew now</a> to continue service.
|
||||||
<p>Best regards,<br>
|
</p>
|
||||||
The Team at Tenhal</p>
|
<p>
|
||||||
</body>
|
Best regards,
|
||||||
|
<br>
|
||||||
|
The Team at Tenhal
|
||||||
|
</p>
|
||||||
|
</body>
|
||||||
</html>
|
</html>
|
||||||
@ -1,6 +1,6 @@
|
|||||||
<!DOCTYPE html>
|
<!DOCTYPE html>
|
||||||
<html>
|
<html>
|
||||||
<head>
|
<head>
|
||||||
<meta charset="utf-8">
|
<meta charset="utf-8">
|
||||||
<style>
|
<style>
|
||||||
body { font-family: Arial, sans-serif; background-color: #f4f4f4; margin: 0; padding: 0; }
|
body { font-family: Arial, sans-serif; background-color: #f4f4f4; margin: 0; padding: 0; }
|
||||||
@ -10,17 +10,26 @@
|
|||||||
.footer { text-align: center; margin-top: 20px; font-size: 0.8em; color: #888888; }
|
.footer { text-align: center; margin-top: 20px; font-size: 0.8em; color: #888888; }
|
||||||
.highlight { font-weight: bold; color: #007bff; }
|
.highlight { font-weight: bold; color: #007bff; }
|
||||||
</style>
|
</style>
|
||||||
</head>
|
</head>
|
||||||
<body>
|
<body>
|
||||||
<div class="container">
|
<div class="container">
|
||||||
<h2>Hello {{ user_name }},</h2>
|
<h2>Hello {{ user_name }},</h2>
|
||||||
<p>{% trans "This is a friendly reminder for your upcoming schedule" %}:</p>
|
<p>{% trans "This is a friendly reminder for your upcoming schedule" %}:</p>
|
||||||
<p>
|
<p>
|
||||||
<span class="highlight">{% trans "Purpose" %}:</span> {{ schedule_purpose }}<br>
|
<span class="highlight">{% trans "Purpose" %}:</span> {{ schedule_purpose }}
|
||||||
<span class="highlight">{% trans "Scheduled At" %}:</span> {{ scheduled_at }}<br>
|
<br>
|
||||||
<span class="highlight">{% trans "Type" %}:</span> {{ schedule_type }}<br>
|
<span class="highlight">{% trans "Scheduled At" %}:</span> {{ scheduled_at }}
|
||||||
{% if customer_name != 'N/A' %}<span class="highlight">{% trans "Customer" %}:</span> {{ customer_name }}<br>{% endif %}
|
<br>
|
||||||
{% if notes %}<span class="highlight">{% trans "Notes" %}:</span> {{ notes }}<br>{% endif %}
|
<span class="highlight">{% trans "Type" %}:</span> {{ schedule_type }}
|
||||||
|
<br>
|
||||||
|
{% if customer_name != 'N/A' %}
|
||||||
|
<span class="highlight">{% trans "Customer" %}:</span> {{ customer_name }}
|
||||||
|
<br>
|
||||||
|
{% endif %}
|
||||||
|
{% if notes %}
|
||||||
|
<span class="highlight">{% trans "Notes" %}:</span> {{ notes }}
|
||||||
|
<br>
|
||||||
|
{% endif %}
|
||||||
</p>
|
</p>
|
||||||
<p>{% trans "Please be prepared for your schedule" %}.</p>
|
<p>{% trans "Please be prepared for your schedule" %}.</p>
|
||||||
<p>{% trans "Thank you" %}!</p>
|
<p>{% trans "Thank you" %}!</p>
|
||||||
@ -28,7 +37,6 @@
|
|||||||
<div class="footer">
|
<div class="footer">
|
||||||
<p>{% trans "This is an automated reminder. Please do not reply to this email." %}</p>
|
<p>{% trans "This is an automated reminder. Please do not reply to this email." %}</p>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
</div>
|
</div>
|
||||||
</body>
|
</body>
|
||||||
</html>
|
</html>
|
||||||
@ -1,6 +1,5 @@
|
|||||||
{% load static %}
|
{% load static %}
|
||||||
{% load i18n %}
|
{% load i18n %}
|
||||||
|
|
||||||
<style>
|
<style>
|
||||||
|
|
||||||
.empty-state-container {
|
.empty-state-container {
|
||||||
@ -36,35 +35,28 @@
|
|||||||
}
|
}
|
||||||
/* No specific styles for .btn-add-new or .message-box are needed here as per previous updates */
|
/* No specific styles for .btn-add-new or .message-box are needed here as per previous updates */
|
||||||
</style>
|
</style>
|
||||||
|
<div class="empty-state-container">
|
||||||
|
|
||||||
<div class="empty-state-container">
|
|
||||||
<!-- Empty State Illustration -->
|
<!-- Empty State Illustration -->
|
||||||
|
|
||||||
{% if image %}
|
{% if image %}
|
||||||
{% static image as final_image_path %}
|
{% static image as final_image_path %}
|
||||||
{% else %}
|
{% else %}
|
||||||
{% static 'images/no_content/no_item.jpg' as final_image_path %}
|
{% static 'images/no_content/no_item.jpg' as final_image_path %}
|
||||||
{% endif %}
|
{% endif %}
|
||||||
<p class="sm">
|
<p class="sm">
|
||||||
<img src="{{ final_image_path }}" alt="No-empty-state-image" class="empty-state-image">
|
<img src="{{ final_image_path }}"
|
||||||
|
alt="No-empty-state-image"
|
||||||
|
class="empty-state-image">
|
||||||
<p>
|
<p>
|
||||||
|
|
||||||
<!-- Title -->
|
<!-- Title -->
|
||||||
<h3 class="empty-state-title">
|
<h3 class="empty-state-title">{% blocktrans %}No {{ value}} Yet{% endblocktrans %}</h3>
|
||||||
{% blocktrans %}No {{ value}} Yet{% endblocktrans %}
|
|
||||||
</h3>
|
|
||||||
|
|
||||||
<!-- Description -->
|
<!-- Description -->
|
||||||
<p class="empty-state-text">
|
<p class="empty-state-text">
|
||||||
{% blocktrans %}It looks like you haven't added any {{ value }} to your account.
|
{% blocktrans %}It looks like you haven't added any {{ value }} to your account.
|
||||||
Click the button below to get started and add your first {{ value }}!{% endblocktrans %}
|
Click the button below to get started and add your first {{ value }}!{% endblocktrans %}
|
||||||
</p>
|
</p>
|
||||||
|
|
||||||
<!-- Call to Action Button -->
|
<!-- Call to Action Button -->
|
||||||
<a class="btn btn-lg btn-primary" href="{{ url }}">
|
<a class="btn btn-lg btn-primary" href="{{ url }}">
|
||||||
<i class="fa fa-plus me-2"></i>
|
<i class="fa fa-plus me-2"></i>
|
||||||
{% blocktrans %}Create New {{value}}{% endblocktrans %}
|
{% blocktrans %}Create New {{value}}{% endblocktrans %}
|
||||||
|
|
||||||
</a>
|
</a>
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@ -15,7 +15,6 @@
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</footer> {% endcomment %}
|
</footer> {% endcomment %}
|
||||||
|
|
||||||
{% comment %} <footer class="footer position-absolute fs-9 bg-info-subtle">
|
{% comment %} <footer class="footer position-absolute fs-9 bg-info-subtle">
|
||||||
<div class="row g-0 justify-content-between align-items-center h-100">
|
<div class="row g-0 justify-content-between align-items-center h-100">
|
||||||
<div class="col-12 col-sm-auto text-center text-warning">
|
<div class="col-12 col-sm-auto text-center text-warning">
|
||||||
@ -32,7 +31,6 @@
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</footer> {% endcomment %}
|
</footer> {% endcomment %}
|
||||||
|
|
||||||
{% comment %} <footer class="footer position-absolute fs-9 bg-white text-secondary">
|
{% comment %} <footer class="footer position-absolute fs-9 bg-white text-secondary">
|
||||||
<div class="row g-0 justify-content-between align-items-center h-100">
|
<div class="row g-0 justify-content-between align-items-center h-100">
|
||||||
<div class="col-12 col-sm-auto text-center">
|
<div class="col-12 col-sm-auto text-center">
|
||||||
@ -51,11 +49,7 @@
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</footer> {% endcomment %}
|
</footer> {% endcomment %}
|
||||||
|
<style>
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
<style>
|
|
||||||
|
|
||||||
|
|
||||||
.improved-footer {
|
.improved-footer {
|
||||||
@ -96,19 +90,16 @@
|
|||||||
color: var(--text-color);
|
color: var(--text-color);
|
||||||
opacity: 0.6;
|
opacity: 0.6;
|
||||||
}
|
}
|
||||||
</style>
|
</style>
|
||||||
|
<footer class="improved-footer">
|
||||||
|
|
||||||
|
|
||||||
<footer class="improved-footer">
|
|
||||||
<div class="container">
|
<div class="container">
|
||||||
<div class="row g-0 justify-content-between align-items-center h-100">
|
<div class="row g-0 justify-content-between align-items-center h-100">
|
||||||
<div class="col-12 col-sm-auto text-center">
|
<div class="col-12 col-sm-auto text-center">
|
||||||
<span class="text-body"> © 2025 All rights reserved</span>
|
<span class="text-body">© 2025 All rights reserved</span>
|
||||||
<span class="fw-bold">Haikal</span> | <span class="fw-bold">هيكل</span>
|
<span class="fw-bold">Haikal</span> | <span class="fw-bold">هيكل</span>
|
||||||
</div>
|
</div>
|
||||||
<div class="col-12 col-sm-auto text-center">
|
<div class="col-12 col-sm-auto text-center">
|
||||||
<span class="text-body">Powered by </span>
|
<span class="text-body">Powered by</span>
|
||||||
<span>
|
<span>
|
||||||
<a class="mx-1 text-secondary" href="https://tenhal.sa">
|
<a class="mx-1 text-secondary" href="https://tenhal.sa">
|
||||||
<span>TENHAL</span> | <span>تنحل</span>
|
<span>TENHAL</span> | <span>تنحل</span>
|
||||||
@ -118,6 +109,4 @@
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</footer>
|
</footer>
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
@ -10,7 +10,7 @@
|
|||||||
{% endif %}
|
{% endif %}
|
||||||
{% endblock %}
|
{% endblock %}
|
||||||
{% block content %}
|
{% block content %}
|
||||||
<main class="d-flex align-items-center justify-content-center min-vh-80 py-5">
|
<main class="d-flex align-items-center justify-content-center min-vh-80 py-5">
|
||||||
<div class="col-12 col-sm-10 col-md-8 col-lg-6 col-xl-5">
|
<div class="col-12 col-sm-10 col-md-8 col-lg-6 col-xl-5">
|
||||||
<div class="card shadow-lg border-0 rounded-4 overflow-hidden animate__animated animate__fadeInUp">
|
<div class="card shadow-lg border-0 rounded-4 overflow-hidden animate__animated animate__fadeInUp">
|
||||||
<div class="card-header bg-gradient py-4 border-0 rounded-top-4">
|
<div class="card-header bg-gradient py-4 border-0 rounded-top-4">
|
||||||
@ -29,24 +29,26 @@
|
|||||||
{% csrf_token %}
|
{% csrf_token %}
|
||||||
{{ redirect_field }}
|
{{ redirect_field }}
|
||||||
{{ form|crispy }}
|
{{ form|crispy }}
|
||||||
|
|
||||||
{% if form.errors %}
|
{% if form.errors %}
|
||||||
<div class="alert alert-danger mt-4" role="alert">
|
<div class="alert alert-danger mt-4" role="alert">
|
||||||
<h4 class="alert-heading small">{% trans "Please correct the following errors:" %}</h4>
|
<h4 class="alert-heading small">{% trans "Please correct the following errors:" %}</h4>
|
||||||
<ul class="mb-0">
|
<ul class="mb-0">
|
||||||
{% for field, errors in form.errors.items %}
|
{% for field, errors in form.errors.items %}
|
||||||
<li><strong>{{ field|capfirst }}:</strong> {% for error in errors %}{{ error }}{% endfor %}</li>
|
<li>
|
||||||
|
<strong>{{ field|capfirst }}:</strong>
|
||||||
|
{% for error in errors %}{{ error }}{% endfor %}
|
||||||
|
</li>
|
||||||
{% endfor %}
|
{% endfor %}
|
||||||
</ul>
|
</ul>
|
||||||
</div>
|
</div>
|
||||||
{% endif %}
|
{% endif %}
|
||||||
|
|
||||||
<hr class="my-4">
|
<hr class="my-4">
|
||||||
<div class="d-grid gap-2 d-md-flex justify-content-md-center">
|
<div class="d-grid gap-2 d-md-flex justify-content-md-center">
|
||||||
<button class="btn btn-phoenix-primary btn-lg md-me-2" type="submit">
|
<button class="btn btn-phoenix-primary btn-lg md-me-2" type="submit">
|
||||||
<i class="fa-solid fa-floppy-disk me-1"></i>{% trans "Save" %}
|
<i class="fa-solid fa-floppy-disk me-1"></i>{% trans "Save" %}
|
||||||
</button>
|
</button>
|
||||||
<a href="{% url 'group_list' request.dealer.slug %}" class="btn btn-phoenix-secondary btn-lg">
|
<a href="{% url 'group_list' request.dealer.slug %}"
|
||||||
|
class="btn btn-phoenix-secondary btn-lg">
|
||||||
<i class="fa-solid fa-ban me-1"></i>{% trans "Cancel" %}
|
<i class="fa-solid fa-ban me-1"></i>{% trans "Cancel" %}
|
||||||
</a>
|
</a>
|
||||||
</div>
|
</div>
|
||||||
@ -54,5 +56,5 @@
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</main>
|
</main>
|
||||||
{% endblock %}
|
{% endblock %}
|
||||||
@ -6,12 +6,14 @@
|
|||||||
{% trans "Groups" %}
|
{% trans "Groups" %}
|
||||||
{% endblock title %}
|
{% endblock title %}
|
||||||
{% block content %}
|
{% block content %}
|
||||||
<main class="py-5">
|
<main class="py-5">
|
||||||
<div class="container">
|
<div class="container">
|
||||||
{% if groups or request.GET.q %}
|
{% if groups or request.GET.q %}
|
||||||
<div class="card border-0 rounded-4 animate__animated animate__fadeInUp">
|
<div class="card border-0 rounded-4 animate__animated animate__fadeInUp">
|
||||||
<div class="card-header border-bottom d-flex flex-column flex-md-row justify-content-between align-items-md-center p-4">
|
<div class="card-header border-bottom d-flex flex-column flex-md-row justify-content-between align-items-md-center p-4">
|
||||||
<h5 class="card-title mb-2 mb-md-0 me-md-4 fw-bold"> <i class="fa-solid fa-user-group fs-3 me-1 text-primary "></i>{% trans "Groups" %}</h5>
|
<h5 class="card-title mb-2 mb-md-0 me-md-4 fw-bold">
|
||||||
|
<i class="fa-solid fa-user-group fs-3 me-1 text-primary "></i>{% trans "Groups" %}
|
||||||
|
</h5>
|
||||||
<div class="d-flex gap-2">
|
<div class="d-flex gap-2">
|
||||||
<a href="{% url 'group_create' request.dealer.slug %}"
|
<a href="{% url 'group_create' request.dealer.slug %}"
|
||||||
class="btn btn-phoenix-primary">
|
class="btn btn-phoenix-primary">
|
||||||
@ -32,7 +34,10 @@
|
|||||||
<th scope="col" class="text-secondary text-uppercase fw-bold ps-4">{% trans 'name'|capfirst %}</th>
|
<th scope="col" class="text-secondary text-uppercase fw-bold ps-4">{% trans 'name'|capfirst %}</th>
|
||||||
<th scope="col" class="text-secondary text-uppercase fw-bold">{% trans 'total Users'|capfirst %}</th>
|
<th scope="col" class="text-secondary text-uppercase fw-bold">{% trans 'total Users'|capfirst %}</th>
|
||||||
<th scope="col" class="text-secondary text-uppercase fw-bold">{% trans 'total permission'|capfirst %}</th>
|
<th scope="col" class="text-secondary text-uppercase fw-bold">{% trans 'total permission'|capfirst %}</th>
|
||||||
<th scope="col" class="text-secondary text-uppercase fw-bold text-end pe-4">{% trans 'actions'|capfirst %}</th>
|
<th scope="col"
|
||||||
|
class="text-secondary text-uppercase fw-bold text-end pe-4">
|
||||||
|
{% trans 'actions'|capfirst %}
|
||||||
|
</th>
|
||||||
</tr>
|
</tr>
|
||||||
</thead>
|
</thead>
|
||||||
<tbody>
|
<tbody>
|
||||||
@ -60,9 +65,7 @@
|
|||||||
</div>
|
</div>
|
||||||
{% if page_obj.paginator.num_pages > 1 %}
|
{% if page_obj.paginator.num_pages > 1 %}
|
||||||
<div class="card-footer bg-light border-top">
|
<div class="card-footer bg-light border-top">
|
||||||
<div class="d-flex justify-content-end">
|
<div class="d-flex justify-content-end">{% include 'partials/pagination.html' %}</div>
|
||||||
{% include 'partials/pagination.html' %}
|
|
||||||
</div>
|
|
||||||
</div>
|
</div>
|
||||||
{% endif %}
|
{% endif %}
|
||||||
</div>
|
</div>
|
||||||
@ -71,5 +74,5 @@
|
|||||||
{% include "empty-illustration-page.html" with value="group" url=create_group_url %}
|
{% include "empty-illustration-page.html" with value="group" url=create_group_url %}
|
||||||
{% endif %}
|
{% endif %}
|
||||||
</div>
|
</div>
|
||||||
</main>
|
</main>
|
||||||
{% endblock %}
|
{% endblock %}
|
||||||
@ -24,12 +24,13 @@
|
|||||||
<div>
|
<div>
|
||||||
<form method="post" novalidate>
|
<form method="post" novalidate>
|
||||||
{% csrf_token %}
|
{% csrf_token %}
|
||||||
|
|
||||||
<div class="row mb-4">
|
<div class="row mb-4">
|
||||||
<div class="col-md-6">
|
<div class="col-md-6">
|
||||||
<div class="input-group">
|
<div class="input-group">
|
||||||
<span class="input-group-text"><i class="fas fa-search"></i></span>
|
<span class="input-group-text"><i class="fas fa-search"></i></span>
|
||||||
<input type="text" class="form-control" id="permissionSearch"
|
<input type="text"
|
||||||
|
class="form-control"
|
||||||
|
id="permissionSearch"
|
||||||
placeholder="{% trans 'Search permissions...' %}">
|
placeholder="{% trans 'Search permissions...' %}">
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
@ -40,10 +41,11 @@
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
<div class="row row-cols-1 row-cols-md-2 row-cols-lg-3 g-4"
|
||||||
<div class="row row-cols-1 row-cols-md-2 row-cols-lg-3 g-4" id="permissionsGrid">
|
id="permissionsGrid">
|
||||||
{% for app_label, models in grouped_permissions.items %}
|
{% for app_label, models in grouped_permissions.items %}
|
||||||
<div class="col"> {# This div opens for each app_label #}
|
<div class="col">
|
||||||
|
{# This div opens for each app_label #}
|
||||||
<div class="card h-100 border-{% if app_label in group_permission_apps %}primary{% else %}light{% endif %}">
|
<div class="card h-100 border-{% if app_label in group_permission_apps %}primary{% else %}light{% endif %}">
|
||||||
<div class="card-header bg-{% if app_label in group_permission_apps %}primary text-white{% else %}light{% endif %}">
|
<div class="card-header bg-{% if app_label in group_permission_apps %}primary text-white{% else %}light{% endif %}">
|
||||||
<div class="d-flex justify-content-between align-items-center">
|
<div class="d-flex justify-content-between align-items-center">
|
||||||
@ -60,7 +62,8 @@
|
|||||||
<div class="accordion" id="accordion-{{ app_label|slugify }}">
|
<div class="accordion" id="accordion-{{ app_label|slugify }}">
|
||||||
{% for model, perms in models.items %}
|
{% for model, perms in models.items %}
|
||||||
<div class="accordion-item border-0 mb-2">
|
<div class="accordion-item border-0 mb-2">
|
||||||
<h6 class="accordion-header" id="heading-{{ app_label|slugify }}-{{ model|slugify }}">
|
<h6 class="accordion-header"
|
||||||
|
id="heading-{{ app_label|slugify }}-{{ model|slugify }}">
|
||||||
<button class="accordion-button collapsed bg-white shadow-none py-2"
|
<button class="accordion-button collapsed bg-white shadow-none py-2"
|
||||||
type="button"
|
type="button"
|
||||||
data-bs-toggle="collapse"
|
data-bs-toggle="collapse"
|
||||||
@ -115,21 +118,14 @@
|
|||||||
</div>
|
</div>
|
||||||
{% endfor %}
|
{% endfor %}
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
|
||||||
<div class="row mt-4 mb-4">
|
<div class="row mt-4 mb-4">
|
||||||
<div class="col">
|
<div class="col">
|
||||||
<div class="d-flex justify-content-between align-items-center">
|
<div class="d-flex justify-content-between align-items-center">
|
||||||
<div>
|
<div>
|
||||||
<span class="badge bg-primary rounded-pill me-2">
|
<span class="badge bg-primary rounded-pill me-2">{{ group_permission_ids|length }} {% trans "selected" %}</span>
|
||||||
{{ group_permission_ids|length }} {% trans "selected" %}
|
<span class="text-muted">{% trans "Permissions will be updated immediately" %}</span>
|
||||||
</span>
|
|
||||||
<span class="text-muted">
|
|
||||||
{% trans "Permissions will be updated immediately" %}
|
|
||||||
</span>
|
|
||||||
</div>
|
</div>
|
||||||
<div>
|
<div>
|
||||||
|
|
||||||
<button type="submit" class="btn btn-lg btn-primary me-2">
|
<button type="submit" class="btn btn-lg btn-primary me-2">
|
||||||
<i class="fas fa-save me-1"></i>{% trans "Save Changes" %}
|
<i class="fas fa-save me-1"></i>{% trans "Save Changes" %}
|
||||||
</button>
|
</button>
|
||||||
@ -138,9 +134,8 @@
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</form>
|
</form>
|
||||||
</div>
|
</div>
|
||||||
|
<style>
|
||||||
<style>
|
|
||||||
.bg-light-primary {
|
.bg-light-primary {
|
||||||
background-color: rgba(13, 110, 253, 0.1);
|
background-color: rgba(13, 110, 253, 0.1);
|
||||||
}
|
}
|
||||||
@ -155,10 +150,9 @@
|
|||||||
box-shadow: none;
|
box-shadow: none;
|
||||||
border-color: rgba(0,0,0,.125);
|
border-color: rgba(0,0,0,.125);
|
||||||
}
|
}
|
||||||
</style>
|
</style>
|
||||||
|
<script>
|
||||||
<script>
|
document.addEventListener('DOMContentLoaded', function() {
|
||||||
document.addEventListener('DOMContentLoaded', function() {
|
|
||||||
// Initialize all accordions
|
// Initialize all accordions
|
||||||
document.querySelectorAll('.accordion-button').forEach(button => {
|
document.querySelectorAll('.accordion-button').forEach(button => {
|
||||||
button.addEventListener('click', function() {
|
button.addEventListener('click', function() {
|
||||||
|
|||||||
@ -71,20 +71,12 @@
|
|||||||
<div class="d-flex gap-3">
|
<div class="d-flex gap-3">
|
||||||
<span id="clearChatBtn"
|
<span id="clearChatBtn"
|
||||||
class="translate-middle-y cursor-pointer"
|
class="translate-middle-y cursor-pointer"
|
||||||
title="{% if LANGUAGE_CODE == 'ar' %}
|
title="{% if LANGUAGE_CODE == 'ar' %} مسح المحادثة {% else %} Clear Chat {% endif %}">
|
||||||
مسح المحادثة
|
|
||||||
{% else %}
|
|
||||||
Clear Chat
|
|
||||||
{% endif %}">
|
|
||||||
<i class="fas fa-trash-alt text-danger"></i>
|
<i class="fas fa-trash-alt text-danger"></i>
|
||||||
</span>
|
</span>
|
||||||
<span id="exportChatBtn"
|
<span id="exportChatBtn"
|
||||||
class="translate-middle-y cursor-pointer"
|
class="translate-middle-y cursor-pointer"
|
||||||
title="{% if LANGUAGE_CODE == 'ar' %}
|
title="{% if LANGUAGE_CODE == 'ar' %} تصدير المحادثة {% else %} Export Chat {% endif %}">
|
||||||
تصدير المحادثة
|
|
||||||
{% else %}
|
|
||||||
Export Chat
|
|
||||||
{% endif %}">
|
|
||||||
<i class="fas fa-download text-success"></i>
|
<i class="fas fa-download text-success"></i>
|
||||||
</span>
|
</span>
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@ -3,35 +3,54 @@
|
|||||||
<nav class="navbar navbar-vertical navbar-expand-lg ">
|
<nav class="navbar navbar-vertical navbar-expand-lg ">
|
||||||
<div class="collapse navbar-collapse" id="navbarVerticalCollapse">
|
<div class="collapse navbar-collapse" id="navbarVerticalCollapse">
|
||||||
<div class="navbar-vertical-content d-flex flex-column">
|
<div class="navbar-vertical-content d-flex flex-column">
|
||||||
<ul class="navbar-nav flex-column" id="navbarVerticalNav" hx-boost="false" hx-target="#main_content" hx-select="#main_content" hx-swap="outerHTML" hx-select-oob="#toast-container" hx-indicator="#spinner">
|
<ul class="navbar-nav flex-column"
|
||||||
|
id="navbarVerticalNav"
|
||||||
|
hx-boost="false"
|
||||||
|
hx-target="#main_content"
|
||||||
|
hx-select="#main_content"
|
||||||
|
hx-swap="outerHTML"
|
||||||
|
hx-select-oob="#toast-container"
|
||||||
|
hx-indicator="#spinner">
|
||||||
<li class="nav-item">
|
<li class="nav-item">
|
||||||
{% comment %} <p class="navbar-vertical-label text-primary fs-8 text-truncate">{{request.dealer|default:"Apps"}}</p>
|
{% comment %} <p class="navbar-vertical-label text-primary fs-8 text-truncate">{{request.dealer|default:"Apps"}}</p>
|
||||||
<hr class="navbar-vertical-line"> {% endcomment %}
|
<hr class="navbar-vertical-line"> {% endcomment %}
|
||||||
{% if perms.inventory.can_view_inventory %}
|
{% if perms.inventory.can_view_inventory %}
|
||||||
<div class="nav-item-wrapper">
|
<div class="nav-item-wrapper">
|
||||||
<a id="inventory-nav" class="nav-link dropdown-indicator label-1 inventory-nav" href="#nv-inventory" role="button" data-bs-toggle="collapse" aria-expanded="false" aria-controls="nv-inventory">
|
<a id="inventory-nav"
|
||||||
|
class="nav-link dropdown-indicator label-1 inventory-nav"
|
||||||
|
href="#nv-inventory"
|
||||||
|
role="button"
|
||||||
|
data-bs-toggle="collapse"
|
||||||
|
aria-expanded="false"
|
||||||
|
aria-controls="nv-inventory">
|
||||||
<div class="d-flex align-items-center ">
|
<div class="d-flex align-items-center ">
|
||||||
<div class="dropdown-indicator-icon-wrapper"><span class="fas fa-caret-right dropdown-indicator-icon"></span></div>
|
<div class="dropdown-indicator-icon-wrapper">
|
||||||
|
<span class="fas fa-caret-right dropdown-indicator-icon"></span>
|
||||||
|
</div>
|
||||||
<span class="nav-link-icon "><span class="fas fa-warehouse"></span></span><span class="nav-link-text ">{% trans "Inventory"|capfirst %}</span>
|
<span class="nav-link-icon "><span class="fas fa-warehouse"></span></span><span class="nav-link-text ">{% trans "Inventory"|capfirst %}</span>
|
||||||
</div>
|
</div>
|
||||||
</a>
|
</a>
|
||||||
<div class="parent-wrapper label-1">
|
<div class="parent-wrapper label-1">
|
||||||
<ul class="nav collapse parent" data-bs-parent="#navbarVerticalCollapse" id="nv-inventory">
|
<ul class="nav collapse parent"
|
||||||
|
data-bs-parent="#navbarVerticalCollapse"
|
||||||
|
id="nv-inventory">
|
||||||
<li class="collapsed-nav-item-title d-none">{% trans "Inventory"|capfirst %}</li>
|
<li class="collapsed-nav-item-title d-none">{% trans "Inventory"|capfirst %}</li>
|
||||||
{% if perms.inventory.add_car %}
|
{% if perms.inventory.add_car %}
|
||||||
<li class="nav-item">
|
<li class="nav-item">
|
||||||
<a hx-boost="false" id="btn-add-car" class="nav-link btn-add-car" href="{% url 'car_add' request.dealer.slug %}">
|
<a hx-boost="false"
|
||||||
|
id="btn-add-car"
|
||||||
|
class="nav-link btn-add-car"
|
||||||
|
href="{% url 'car_add' request.dealer.slug %}">
|
||||||
<div class="d-flex align-items-center">
|
<div class="d-flex align-items-center">
|
||||||
<span class="nav-link-icon"><span class="fas fa-plus-circle"></span></span><span class="nav-link-text">{% trans "add car"|capfirst %}</span>
|
<span class="nav-link-icon"><span class="fas fa-plus-circle"></span></span><span class="nav-link-text">{% trans "add car"|capfirst %}</span>
|
||||||
</div>
|
</div>
|
||||||
</a>
|
</a>
|
||||||
</li>
|
</li>
|
||||||
|
|
||||||
{% endif %}
|
{% endif %}
|
||||||
|
{% if perms.inventory.view_car %}
|
||||||
{% if perms.inventory.view_car%}
|
|
||||||
<li class="nav-item">
|
<li class="nav-item">
|
||||||
<a class="nav-link" href="{% url 'inventory_stats' request.dealer.slug %}">
|
<a class="nav-link"
|
||||||
|
href="{% url 'inventory_stats' request.dealer.slug %}">
|
||||||
<div class="d-flex align-items-center">
|
<div class="d-flex align-items-center">
|
||||||
<span class="nav-link-icon"><span class="fas fa-car-side"></span></span><span class="nav-link-text">{% trans 'Cars'|capfirst %}</span>
|
<span class="nav-link-icon"><span class="fas fa-car-side"></span></span><span class="nav-link-text">{% trans 'Cars'|capfirst %}</span>
|
||||||
</div>
|
</div>
|
||||||
@ -45,7 +64,6 @@
|
|||||||
</a>
|
</a>
|
||||||
</li>
|
</li>
|
||||||
{% endif %}
|
{% endif %}
|
||||||
|
|
||||||
{% comment %} {% if perms.inventory.add_car %}
|
{% comment %} {% if perms.inventory.add_car %}
|
||||||
<li class="nav-item">
|
<li class="nav-item">
|
||||||
<a class="nav-link" href="{% url 'upload_cars' request.dealer.slug %}">
|
<a class="nav-link" href="{% url 'upload_cars' request.dealer.slug %}">
|
||||||
@ -57,36 +75,45 @@
|
|||||||
{% endif %} {% endcomment %}
|
{% endif %} {% endcomment %}
|
||||||
{% if perms.django_ledger.view_purchaseordermodel %}
|
{% if perms.django_ledger.view_purchaseordermodel %}
|
||||||
<li class="nav-item">
|
<li class="nav-item">
|
||||||
<a class="nav-link" href="{% url 'purchase_order_list' request.dealer.slug request.dealer.entity.slug %}">
|
<a class="nav-link"
|
||||||
|
href="{% url 'purchase_order_list' request.dealer.slug request.dealer.entity.slug %}">
|
||||||
<div class="d-flex align-items-center">
|
<div class="d-flex align-items-center">
|
||||||
<span class="nav-link-icon"><span class="fas fa-warehouse"></span></span><span class="nav-link-text">{% trans "purchase Orders"|capfirst %}</span>
|
<span class="nav-link-icon"><span class="fas fa-warehouse"></span></span><span class="nav-link-text">{% trans "purchase Orders"|capfirst %}</span>
|
||||||
</div>
|
</div>
|
||||||
</a>
|
</a>
|
||||||
</li>
|
</li>
|
||||||
<li class="nav-item">
|
<li class="nav-item">
|
||||||
<a class="nav-link" href="{% url 'inventory_list' request.dealer.slug request.dealer.entity.slug %}">
|
<a class="nav-link"
|
||||||
|
href="{% url 'inventory_list' request.dealer.slug request.dealer.entity.slug %}">
|
||||||
<div class="d-flex align-items-center">
|
<div class="d-flex align-items-center">
|
||||||
<span class="nav-link-icon"><span class="fas fa-boxes"></span></span><span class="nav-link-text">{% trans "Inventory List"|capfirst %}</span>
|
<span class="nav-link-icon"><span class="fas fa-boxes"></span></span><span class="nav-link-text">{% trans "Inventory List"|capfirst %}</span>
|
||||||
</div>
|
</div>
|
||||||
</a>
|
</a>
|
||||||
</li>
|
</li>
|
||||||
{% endif %}
|
{% endif %}
|
||||||
|
|
||||||
</ul>
|
</ul>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
{% endif %}
|
{% endif %}
|
||||||
|
|
||||||
{% if perms.inventory.can_view_crm %}
|
{% if perms.inventory.can_view_crm %}
|
||||||
<div class="nav-item-wrapper">
|
<div class="nav-item-wrapper">
|
||||||
<a class="nav-link dropdown-indicator label-1" href="#nv-crm" role="button" data-bs-toggle="collapse" aria-expanded="false" aria-controls="nv-crm">
|
<a class="nav-link dropdown-indicator label-1"
|
||||||
|
href="#nv-crm"
|
||||||
|
role="button"
|
||||||
|
data-bs-toggle="collapse"
|
||||||
|
aria-expanded="false"
|
||||||
|
aria-controls="nv-crm">
|
||||||
<div class="d-flex align-items-center">
|
<div class="d-flex align-items-center">
|
||||||
<div class="dropdown-indicator-icon-wrapper"><span class="fas fa-caret-right dropdown-indicator-icon"></span></div>
|
<div class="dropdown-indicator-icon-wrapper">
|
||||||
|
<span class="fas fa-caret-right dropdown-indicator-icon"></span>
|
||||||
|
</div>
|
||||||
<span class="nav-link-icon"><span data-feather="phone"></span></span><span class="nav-link-text">{% trans 'crm'|upper %}</span>
|
<span class="nav-link-icon"><span data-feather="phone"></span></span><span class="nav-link-text">{% trans 'crm'|upper %}</span>
|
||||||
</div>
|
</div>
|
||||||
</a>
|
</a>
|
||||||
<div class="parent-wrapper label-1">
|
<div class="parent-wrapper label-1">
|
||||||
<ul class="nav collapse parent" data-bs-parent="#navbarVerticalCollapse" id="nv-crm">
|
<ul class="nav collapse parent"
|
||||||
|
data-bs-parent="#navbarVerticalCollapse"
|
||||||
|
id="nv-crm">
|
||||||
<li class="collapsed-nav-item-title d-none">{% trans 'crm'|upper %}</li>
|
<li class="collapsed-nav-item-title d-none">{% trans 'crm'|upper %}</li>
|
||||||
{% if perms.inventory.view_lead %}
|
{% if perms.inventory.view_lead %}
|
||||||
<li class="nav-item">
|
<li class="nav-item">
|
||||||
@ -104,9 +131,10 @@
|
|||||||
</a>
|
</a>
|
||||||
</li>
|
</li>
|
||||||
{% endif %}
|
{% endif %}
|
||||||
{%if perms.inventory.view_opportunity %}
|
{% if perms.inventory.view_opportunity %}
|
||||||
<li class="nav-item">
|
<li class="nav-item">
|
||||||
<a class="nav-link" href="{% url 'opportunity_list' request.dealer.slug %}">
|
<a class="nav-link"
|
||||||
|
href="{% url 'opportunity_list' request.dealer.slug %}">
|
||||||
<div class="d-flex align-items-center">
|
<div class="d-flex align-items-center">
|
||||||
<span class="nav-link-icon"><span data-feather="users"></span></span><span class="nav-link-text">{% trans 'Opportunity'|capfirst %}</span>
|
<span class="nav-link-icon"><span data-feather="users"></span></span><span class="nav-link-text">{% trans 'Opportunity'|capfirst %}</span>
|
||||||
</div>
|
</div>
|
||||||
@ -124,7 +152,8 @@
|
|||||||
{% endif %}
|
{% endif %}
|
||||||
{% if perms.inventory.view_organization %}
|
{% if perms.inventory.view_organization %}
|
||||||
<li class="nav-item">
|
<li class="nav-item">
|
||||||
<a class="nav-link" href="{% url 'organization_list' request.dealer.slug %}">
|
<a class="nav-link"
|
||||||
|
href="{% url 'organization_list' request.dealer.slug %}">
|
||||||
<div class="d-flex align-items-center">
|
<div class="d-flex align-items-center">
|
||||||
<span class="nav-link-icon"><span class="fas fa-city"></span></span><span class="nav-link-text">{% trans "Organizations"|capfirst %}</span>
|
<span class="nav-link-icon"><span class="fas fa-city"></span></span><span class="nav-link-text">{% trans "Organizations"|capfirst %}</span>
|
||||||
</div>
|
</div>
|
||||||
@ -149,21 +178,31 @@
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
{% endif %}
|
{% endif %}
|
||||||
|
|
||||||
{% if perms.django_ledger.can_view_sales %}
|
{% if perms.django_ledger.can_view_sales %}
|
||||||
<div class="nav-item-wrapper">
|
<div class="nav-item-wrapper">
|
||||||
<a class="nav-link dropdown-indicator label-1" href="#nv-sales" role="button" data-bs-toggle="collapse" aria-expanded="false" aria-controls="nv-sales">
|
<a class="nav-link dropdown-indicator label-1"
|
||||||
|
href="#nv-sales"
|
||||||
|
role="button"
|
||||||
|
data-bs-toggle="collapse"
|
||||||
|
aria-expanded="false"
|
||||||
|
aria-controls="nv-sales">
|
||||||
<div class="d-flex align-items-center">
|
<div class="d-flex align-items-center">
|
||||||
<div class="dropdown-indicator-icon-wrapper"><span class="fas fa-caret-right dropdown-indicator-icon"></span></div>
|
<div class="dropdown-indicator-icon-wrapper">
|
||||||
|
<span class="fas fa-caret-right dropdown-indicator-icon"></span>
|
||||||
|
</div>
|
||||||
<span class="nav-link-icon"><span data-feather="shopping-cart"></span></span><span class="nav-link-text">{% trans 'sales'|capfirst %}</span>
|
<span class="nav-link-icon"><span data-feather="shopping-cart"></span></span><span class="nav-link-text">{% trans 'sales'|capfirst %}</span>
|
||||||
</div>
|
</div>
|
||||||
</a>
|
</a>
|
||||||
<div class="parent-wrapper label-1">
|
<div class="parent-wrapper label-1">
|
||||||
<ul class="nav collapse parent" data-bs-parent="#navbarVerticalCollapse" id="nv-sales">
|
<ul class="nav collapse parent"
|
||||||
|
data-bs-parent="#navbarVerticalCollapse"
|
||||||
|
id="nv-sales">
|
||||||
<li class="collapsed-nav-item-title d-none">{% trans 'sales'|capfirst %}</li>
|
<li class="collapsed-nav-item-title d-none">{% trans 'sales'|capfirst %}</li>
|
||||||
{% if perms.django_ledger.add_estimatemodel %}
|
{% if perms.django_ledger.add_estimatemodel %}
|
||||||
<li class="nav-item">
|
<li class="nav-item">
|
||||||
<a hx-boost="false" class="nav-link" href="{% url 'estimate_create' request.dealer.slug %}">
|
<a hx-boost="false"
|
||||||
|
class="nav-link"
|
||||||
|
href="{% url 'estimate_create' request.dealer.slug %}">
|
||||||
<div class="d-flex align-items-center">
|
<div class="d-flex align-items-center">
|
||||||
<span class="nav-link-icon"><span class="fas fa-handshake"></span></span><span class="nav-link-text">{% trans "create quotation"|capfirst %}</span>
|
<span class="nav-link-icon"><span class="fas fa-handshake"></span></span><span class="nav-link-text">{% trans "create quotation"|capfirst %}</span>
|
||||||
</div>
|
</div>
|
||||||
@ -179,7 +218,7 @@
|
|||||||
</a>
|
</a>
|
||||||
</li>
|
</li>
|
||||||
{% endif %}
|
{% endif %}
|
||||||
{%if perms.inventory.view_saleorder%}
|
{% if perms.inventory.view_saleorder %}
|
||||||
<li class="nav-item">
|
<li class="nav-item">
|
||||||
<a class="nav-link" href="{% url 'sales_list' request.dealer.slug %}">
|
<a class="nav-link" href="{% url 'sales_list' request.dealer.slug %}">
|
||||||
<div class="d-flex align-items-center">
|
<div class="d-flex align-items-center">
|
||||||
@ -188,7 +227,6 @@
|
|||||||
</a>
|
</a>
|
||||||
</li>
|
</li>
|
||||||
{% endif %}
|
{% endif %}
|
||||||
|
|
||||||
{% if perms.django_ledger.view_invoicemodel %}
|
{% if perms.django_ledger.view_invoicemodel %}
|
||||||
<li class="nav-item">
|
<li class="nav-item">
|
||||||
<a class="nav-link" href="{% url 'invoice_list' request.dealer.slug %}">
|
<a class="nav-link" href="{% url 'invoice_list' request.dealer.slug %}">
|
||||||
@ -202,21 +240,30 @@
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
{% endif %}
|
{% endif %}
|
||||||
|
|
||||||
{% if perms.django_ledger.can_view_financials %}
|
{% if perms.django_ledger.can_view_financials %}
|
||||||
<div class="nav-item-wrapper">
|
<div class="nav-item-wrapper">
|
||||||
<a class="nav-link dropdown-indicator label-1" href="#nv-financial" role="button" data-bs-toggle="collapse" aria-expanded="false" aria-controls="nv-financial">
|
<a class="nav-link dropdown-indicator label-1"
|
||||||
|
href="#nv-financial"
|
||||||
|
role="button"
|
||||||
|
data-bs-toggle="collapse"
|
||||||
|
aria-expanded="false"
|
||||||
|
aria-controls="nv-financial">
|
||||||
<div class="d-flex align-items-center">
|
<div class="d-flex align-items-center">
|
||||||
<div class="dropdown-indicator-icon-wrapper"><span class="fas fa-caret-right dropdown-indicator-icon"></span></div>
|
<div class="dropdown-indicator-icon-wrapper">
|
||||||
|
<span class="fas fa-caret-right dropdown-indicator-icon"></span>
|
||||||
|
</div>
|
||||||
<span class="nav-link-icon"><span class="fas fa-money-check-alt"></span></span><span class="nav-link-text">{% trans 'Financials' %}</span>
|
<span class="nav-link-icon"><span class="fas fa-money-check-alt"></span></span><span class="nav-link-text">{% trans 'Financials' %}</span>
|
||||||
</div>
|
</div>
|
||||||
</a>
|
</a>
|
||||||
<div class="parent-wrapper label-1">
|
<div class="parent-wrapper label-1">
|
||||||
<ul class="nav collapse parent" data-bs-parent="#navbarVerticalCollapse" id="nv-financial">
|
<ul class="nav collapse parent"
|
||||||
|
data-bs-parent="#navbarVerticalCollapse"
|
||||||
|
id="nv-financial">
|
||||||
<li class="collapsed-nav-item-title d-none">{% trans 'Financials' %}</li>
|
<li class="collapsed-nav-item-title d-none">{% trans 'Financials' %}</li>
|
||||||
{% if perms.django_ledger.view_accountmodel %}
|
{% if perms.django_ledger.view_accountmodel %}
|
||||||
<li class="nav-item">
|
<li class="nav-item">
|
||||||
<a class="nav-link" href="{% url 'coa-list' request.dealer.slug request.entity.slug %}">
|
<a class="nav-link"
|
||||||
|
href="{% url 'coa-list' request.dealer.slug request.entity.slug %}">
|
||||||
<div class="d-flex align-items-center">
|
<div class="d-flex align-items-center">
|
||||||
<span class="nav-link-icon"><span class="fas fa-book-open"></span></span><span class="nav-link-text">{% trans 'Chart of Accounts'|capfirst %}</span>
|
<span class="nav-link-icon"><span class="fas fa-book-open"></span></span><span class="nav-link-text">{% trans 'Chart of Accounts'|capfirst %}</span>
|
||||||
</div>
|
</div>
|
||||||
@ -225,37 +272,38 @@
|
|||||||
{% endif %}
|
{% endif %}
|
||||||
{% if perms.django_ledger.view_bankaccountmodel %}
|
{% if perms.django_ledger.view_bankaccountmodel %}
|
||||||
<li class="nav-item">
|
<li class="nav-item">
|
||||||
<a class="nav-link" href="{% url 'bank_account_list' request.dealer.slug %}">
|
<a class="nav-link"
|
||||||
|
href="{% url 'bank_account_list' request.dealer.slug %}">
|
||||||
<div class="d-flex align-items-center">
|
<div class="d-flex align-items-center">
|
||||||
<span class="nav-link-icon"><span data-feather="credit-card"></span></span><span class="nav-link-text">{% trans 'Bank Accounts'|capfirst %}</span>
|
<span class="nav-link-icon"><span data-feather="credit-card"></span></span><span class="nav-link-text">{% trans 'Bank Accounts'|capfirst %}</span>
|
||||||
</div>
|
</div>
|
||||||
</a>
|
</a>
|
||||||
</li>
|
</li>
|
||||||
{% endif %}
|
{% endif %}
|
||||||
|
|
||||||
{% if perms.django_ledger.view_journalentrymodel %}
|
{% if perms.django_ledger.view_journalentrymodel %}
|
||||||
<li class="nav-item">
|
<li class="nav-item">
|
||||||
<a class="nav-link" href="{% url 'ledger_list' request.dealer.slug request.dealer.entity.slug %}">
|
<a class="nav-link"
|
||||||
|
href="{% url 'ledger_list' request.dealer.slug request.dealer.entity.slug %}">
|
||||||
<div class="d-flex align-items-center">
|
<div class="d-flex align-items-center">
|
||||||
<span class="nav-link-icon"><span class="fas fa-book"></span></span><span class="nav-link-text">{% trans "Ledgers"|capfirst %}</span>
|
<span class="nav-link-icon"><span class="fas fa-book"></span></span><span class="nav-link-text">{% trans "Ledgers"|capfirst %}</span>
|
||||||
</div>
|
</div>
|
||||||
</a>
|
</a>
|
||||||
</li>
|
</li>
|
||||||
{% endif %}
|
{% endif %}
|
||||||
|
|
||||||
{% if perms.inventory.view_additionalservices %}
|
{% if perms.inventory.view_additionalservices %}
|
||||||
<li class="nav-item">
|
<li class="nav-item">
|
||||||
<a class="nav-link" href="{% url 'item_service_list' request.dealer.slug %}">
|
<a class="nav-link"
|
||||||
|
href="{% url 'item_service_list' request.dealer.slug %}">
|
||||||
<div class="d-flex align-items-center">
|
<div class="d-flex align-items-center">
|
||||||
<span class="nav-link-icon"><span data-feather="activity"></span></span><span class="nav-link-text">{% trans "Services"|capfirst %}</span>
|
<span class="nav-link-icon"><span data-feather="activity"></span></span><span class="nav-link-text">{% trans "Services"|capfirst %}</span>
|
||||||
</div>
|
</div>
|
||||||
</a>
|
</a>
|
||||||
</li>
|
</li>
|
||||||
{% endif %}
|
{% endif %}
|
||||||
|
|
||||||
{% if perms.django_ledger.view_itemmodel %}
|
{% if perms.django_ledger.view_itemmodel %}
|
||||||
<li class="nav-item">
|
<li class="nav-item">
|
||||||
<a class="nav-link" href="{% url 'item_expense_list' request.dealer.slug %}">
|
<a class="nav-link"
|
||||||
|
href="{% url 'item_expense_list' request.dealer.slug %}">
|
||||||
<div class="d-flex align-items-center">
|
<div class="d-flex align-items-center">
|
||||||
<span class="nav-link-icon"><span class="fas fa-users-cog"></span></span><span class="nav-link-text">{% trans "Expenses"|capfirst %}</span>
|
<span class="nav-link-icon"><span class="fas fa-users-cog"></span></span><span class="nav-link-text">{% trans "Expenses"|capfirst %}</span>
|
||||||
</div>
|
</div>
|
||||||
@ -294,49 +342,51 @@
|
|||||||
</div>
|
</div>
|
||||||
{% endif %}
|
{% endif %}
|
||||||
<!---->
|
<!---->
|
||||||
|
|
||||||
{% if perms.django_ledger.can_view_reports %}
|
{% if perms.django_ledger.can_view_reports %}
|
||||||
<div class="nav-item-wrapper">
|
<div class="nav-item-wrapper">
|
||||||
<a class="nav-link dropdown-indicator label-1" href="#nv-reports" role="button" data-bs-toggle="collapse" aria-expanded="false" aria-controls="nv-reports">
|
<a class="nav-link dropdown-indicator label-1"
|
||||||
|
href="#nv-reports"
|
||||||
|
role="button"
|
||||||
|
data-bs-toggle="collapse"
|
||||||
|
aria-expanded="false"
|
||||||
|
aria-controls="nv-reports">
|
||||||
<div class="d-flex align-items-center">
|
<div class="d-flex align-items-center">
|
||||||
<div class="dropdown-indicator-icon-wrapper"><span class="fas fa-caret-right dropdown-indicator-icon"></span></div>
|
<div class="dropdown-indicator-icon-wrapper">
|
||||||
|
<span class="fas fa-caret-right dropdown-indicator-icon"></span>
|
||||||
|
</div>
|
||||||
<span class="nav-link-icon"><i class="fa-solid fa-book-open"></i></span><span class="nav-link-text">{% trans 'Reports' %}</span>
|
<span class="nav-link-icon"><i class="fa-solid fa-book-open"></i></span><span class="nav-link-text">{% trans 'Reports' %}</span>
|
||||||
</div>
|
</div>
|
||||||
</a>
|
</a>
|
||||||
|
|
||||||
<div class="parent-wrapper label-1">
|
<div class="parent-wrapper label-1">
|
||||||
<ul class="nav collapse parent" data-bs-parent="#navbarVerticalCollapse" id="nv-reports">
|
<ul class="nav collapse parent"
|
||||||
|
data-bs-parent="#navbarVerticalCollapse"
|
||||||
|
id="nv-reports">
|
||||||
<li class="collapsed-nav-item-title d-none">{% trans 'Financials' %}</li>
|
<li class="collapsed-nav-item-title d-none">{% trans 'Financials' %}</li>
|
||||||
{% if perms.django_ledger.view_accountmodel %}
|
{% if perms.django_ledger.view_accountmodel %}
|
||||||
<li class="nav-item">
|
<li class="nav-item">
|
||||||
|
<a class="nav-link"
|
||||||
<a class="nav-link" href="{% url 'entity-cf' request.dealer.slug request.dealer.entity.slug %}">
|
href="{% url 'entity-cf' request.dealer.slug request.dealer.entity.slug %}">
|
||||||
<div class="d-flex align-items-center">
|
<div class="d-flex align-items-center">
|
||||||
<span class="nav-link-icon"><span class="fas fa-solid fa-sack-dollar"></span></span><span class="nav-link-text">{% trans 'Cash Flow'|capfirst %}</span>
|
<span class="nav-link-icon"><span class="fas fa-solid fa-sack-dollar"></span></span><span class="nav-link-text">{% trans 'Cash Flow'|capfirst %}</span>
|
||||||
</div>
|
</div>
|
||||||
</a>
|
</a>
|
||||||
|
|
||||||
|
|
||||||
</li>
|
</li>
|
||||||
|
|
||||||
<li class="nav-item">
|
<li class="nav-item">
|
||||||
<a class="nav-link" href="{% url 'entity-ic' request.dealer.slug request.dealer.entity.slug %}">
|
<a class="nav-link"
|
||||||
|
href="{% url 'entity-ic' request.dealer.slug request.dealer.entity.slug %}">
|
||||||
<div class="d-flex align-items-center">
|
<div class="d-flex align-items-center">
|
||||||
<span class="nav-link-icon"><span class="fa-solid fa-sheet-plastic"></span></span><span class="nav-link-text">{% trans 'Income Statement'|capfirst %}</span>
|
<span class="nav-link-icon"><span class="fa-solid fa-sheet-plastic"></span></span><span class="nav-link-text">{% trans 'Income Statement'|capfirst %}</span>
|
||||||
</div>
|
</div>
|
||||||
</a>
|
</a>
|
||||||
</li>
|
</li>
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
<li class="nav-item">
|
<li class="nav-item">
|
||||||
<a class="nav-link" href="{% url 'entity-bs' request.dealer.slug request.dealer.entity.slug %}">
|
<a class="nav-link"
|
||||||
|
href="{% url 'entity-bs' request.dealer.slug request.dealer.entity.slug %}">
|
||||||
<div class="d-flex align-items-center">
|
<div class="d-flex align-items-center">
|
||||||
<span class="nav-link-icon"><span class="fas fa-solid fa-scale-balanced"></span></span><span class="nav-link-text">{% trans 'Balance Sheet'|capfirst %}</span>
|
<span class="nav-link-icon"><span class="fas fa-solid fa-scale-balanced"></span></span><span class="nav-link-text">{% trans 'Balance Sheet'|capfirst %}</span>
|
||||||
</div>
|
</div>
|
||||||
</a>
|
</a>
|
||||||
</li>
|
</li>
|
||||||
|
|
||||||
<li class="nav-item">
|
<li class="nav-item">
|
||||||
<!--car purchase report-->
|
<!--car purchase report-->
|
||||||
<a class="nav-link" href="{% url 'po-report' request.dealer.slug %}">
|
<a class="nav-link" href="{% url 'po-report' request.dealer.slug %}">
|
||||||
@ -345,29 +395,25 @@
|
|||||||
</div>
|
</div>
|
||||||
</a>
|
</a>
|
||||||
</li>
|
</li>
|
||||||
|
|
||||||
<li class="nav-item">
|
<li class="nav-item">
|
||||||
<!--car sale report-->
|
<!--car sale report-->
|
||||||
<a class="nav-link" href="{% url 'car-sale-report' request.dealer.slug %}">
|
<a class="nav-link"
|
||||||
|
href="{% url 'car-sale-report' request.dealer.slug %}">
|
||||||
<div class="d-flex align-items-center">
|
<div class="d-flex align-items-center">
|
||||||
<span class="nav-link-icon"><span class="fas fa-chart-pie"></span></span><span class="nav-link-text">{% trans 'Car Sale Report'|capfirst %}</span>
|
<span class="nav-link-icon"><span class="fas fa-chart-pie"></span></span><span class="nav-link-text">{% trans 'Car Sale Report'|capfirst %}</span>
|
||||||
</div>
|
</div>
|
||||||
</a>
|
</a>
|
||||||
</li>
|
</li>
|
||||||
{% endif %}
|
{% endif %}
|
||||||
|
|
||||||
|
|
||||||
</ul>
|
</ul>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
{% endif %}
|
{% endif %}
|
||||||
<!---->
|
<!---->
|
||||||
|
|
||||||
</li>
|
</li>
|
||||||
</ul>
|
</ul>
|
||||||
{# --- Support & Contact Section (New) --- #}
|
{# --- Support & Contact Section (New) --- #}
|
||||||
<div class="mt-auto">
|
<div class="mt-auto">
|
||||||
|
|
||||||
<ul class="navbar-nav flex-column">
|
<ul class="navbar-nav flex-column">
|
||||||
<li class="nav-item">
|
<li class="nav-item">
|
||||||
<a class="nav-link" href="#">
|
<a class="nav-link" href="#">
|
||||||
@ -385,7 +431,6 @@
|
|||||||
</div>
|
</div>
|
||||||
</a>
|
</a>
|
||||||
</li>
|
</li>
|
||||||
|
|
||||||
<li class="nav-item mb-4">
|
<li class="nav-item mb-4">
|
||||||
<a class="nav-link" href="#">
|
<a class="nav-link" href="#">
|
||||||
<div class="d-flex align-items-center">
|
<div class="d-flex align-items-center">
|
||||||
@ -394,7 +439,6 @@
|
|||||||
</div>
|
</div>
|
||||||
</a>
|
</a>
|
||||||
</li>
|
</li>
|
||||||
|
|
||||||
<li class="nav-item ">
|
<li class="nav-item ">
|
||||||
<a class="nav-link" href="#">
|
<a class="nav-link" href="#">
|
||||||
<div class="d-flex align-items-center">
|
<div class="d-flex align-items-center">
|
||||||
@ -405,30 +449,21 @@
|
|||||||
</div>
|
</div>
|
||||||
</a>
|
</a>
|
||||||
</li>
|
</li>
|
||||||
|
|
||||||
|
|
||||||
</ul>
|
</ul>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
{% endif %}
|
{% endif %}
|
||||||
|
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class="navbar-vertical-footer">
|
<div class="navbar-vertical-footer">
|
||||||
<button class="btn navbar-vertical-toggle border-0 fw-semibold w-100 white-space-nowrap d-flex align-items-center">
|
<button class="btn navbar-vertical-toggle border-0 fw-semibold w-100 white-space-nowrap d-flex align-items-center">
|
||||||
<span class="fas fa-angle-double-left fs-8"></span><span class="fas fa-angle-double-right fs-8"></span><span class="navbar-vertical-footer-text ms-2">Collapsed View</span>
|
<span class="fas fa-angle-double-left fs-8"></span><span class="fas fa-angle-double-right fs-8"></span><span class="navbar-vertical-footer-text ms-2">Collapsed View</span>
|
||||||
</button>
|
</button>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
</nav>
|
</nav>
|
||||||
|
|
||||||
|
|
||||||
<nav class="navbar navbar-top fixed-top navbar-expand" id="navbarDefault">
|
<nav class="navbar navbar-top fixed-top navbar-expand" id="navbarDefault">
|
||||||
<div class="collapse navbar-collapse justify-content-between">
|
<div class="collapse navbar-collapse justify-content-between">
|
||||||
<div class="navbar-logo">
|
<div class="navbar-logo">
|
||||||
<button
|
<button class="btn navbar-toggler navbar-toggler-humburger-icon hover-bg-transparent"
|
||||||
class="btn navbar-toggler navbar-toggler-humburger-icon hover-bg-transparent"
|
|
||||||
type="button"
|
type="button"
|
||||||
data-bs-toggle="collapse"
|
data-bs-toggle="collapse"
|
||||||
data-bs-target="#navbarVerticalCollapse"
|
data-bs-target="#navbarVerticalCollapse"
|
||||||
@ -437,54 +472,94 @@
|
|||||||
aria-label="Toggle Navigation">
|
aria-label="Toggle Navigation">
|
||||||
<span class="navbar-toggle-icon"><span class="toggle-line"></span></span>
|
<span class="navbar-toggle-icon"><span class="toggle-line"></span></span>
|
||||||
</button>
|
</button>
|
||||||
<a class="navbar-brand me-1 me-sm-3" href="{% url 'home'%}">
|
<a class="navbar-brand me-1 me-sm-3" href="{% url 'home' %}">
|
||||||
<div class="d-flex align-items-center">
|
<div class="d-flex align-items-center">
|
||||||
<img class="logo-img d-dark-none" src="{% static 'images/logos/logo-d.png' %}" alt="haikal" width="27" />
|
<img class="logo-img d-dark-none"
|
||||||
<img class="logo-img d-light-none" src="{% static 'images/logos/logo.png' %}" alt="haikal" width="27" />
|
src="{% static 'images/logos/logo-d.png' %}"
|
||||||
|
alt="haikal"
|
||||||
|
width="27" />
|
||||||
|
<img class="logo-img d-light-none"
|
||||||
|
src="{% static 'images/logos/logo.png' %}"
|
||||||
|
alt="haikal"
|
||||||
|
width="27" />
|
||||||
<h5 class="logo-text ms-2 d-none d-sm-block">{% trans 'Haikal' %}</h5>
|
<h5 class="logo-text ms-2 d-none d-sm-block">{% trans 'Haikal' %}</h5>
|
||||||
</div>
|
</div>
|
||||||
</a>
|
</a>
|
||||||
</div>
|
</div>
|
||||||
{% if request.user.is_authenticated%}
|
{% if request.user.is_authenticated %}
|
||||||
|
|
||||||
<div class="navbar-logo">
|
<div class="navbar-logo">
|
||||||
<div class="d-flex align-items-center">
|
<div class="d-flex align-items-center">
|
||||||
{% with name_to_display=request.user.first_name|default:request.dealer.name %}
|
{% with name_to_display=request.user.first_name|default:request.dealer.name %}
|
||||||
<h6 class="text-gray-600 ms-2 d-none d-sm-block fs-8"
|
<h6 class="text-gray-600 ms-2 d-none d-sm-block fs-8"
|
||||||
data-bs-toggle="tooltip"
|
data-bs-toggle="tooltip"
|
||||||
data-bs-placement="bottom"
|
data-bs-placement="bottom"
|
||||||
title="{% trans 'Logged in as ' %}{{request.user.username }}">
|
title="{% trans 'Logged in as ' %}{{ request.user.username }}">
|
||||||
{% trans 'Hello, ' %}{{ name_to_display }}
|
{% trans 'Hello, ' %}{{ name_to_display }}
|
||||||
</h6>
|
</h6>
|
||||||
{% endwith %}
|
{% endwith %}
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
{% endif %}
|
{% endif %}
|
||||||
|
|
||||||
<ul class="navbar-nav navbar-nav-icons flex-row gap-2" hx-boost="false">
|
<ul class="navbar-nav navbar-nav-icons flex-row gap-2" hx-boost="false">
|
||||||
<li class="nav-item">
|
<li class="nav-item">
|
||||||
<div class="theme-control-toggle fa-icon-wait">
|
<div class="theme-control-toggle fa-icon-wait">
|
||||||
<input class="form-check-input ms-0 theme-control-toggle-input" type="checkbox" data-theme-control="phoenixTheme" value="dark" id="themeControlToggleSm" />
|
<input class="form-check-input ms-0 theme-control-toggle-input"
|
||||||
<label class="mb-0 theme-control-toggle-label theme-control-toggle-light" for="themeControlToggleSm" data-bs-toggle="tooltip" data-bs-placement="bottom" data-bs-title="{{ _("Switch theme")}}" style="height:32px;width:32px;"><span class="icon" data-feather="moon"></span></label>
|
type="checkbox"
|
||||||
<label class="mb-0 theme-control-toggle-label theme-control-toggle-dark" for="themeControlToggleSm" data-bs-toggle="tooltip" data-bs-placement="bottom" data-bs-title="{{ _("Switch theme")}}" style="height:32px;width:32px;"><span class="icon" data-feather="sun"></span></label>
|
data-theme-control="phoenixTheme"
|
||||||
|
value="dark"
|
||||||
|
id="themeControlToggleSm" />
|
||||||
|
<label class="mb-0 theme-control-toggle-label theme-control-toggle-light"
|
||||||
|
for="themeControlToggleSm"
|
||||||
|
data-bs-toggle="tooltip"
|
||||||
|
data-bs-placement="bottom"
|
||||||
|
data-bs-title="{{ _("Switch theme") }}"
|
||||||
|
style="height:32px;
|
||||||
|
width:32px">
|
||||||
|
<span class="icon" data-feather="moon"></span>
|
||||||
|
</label>
|
||||||
|
<label class="mb-0 theme-control-toggle-label theme-control-toggle-dark"
|
||||||
|
for="themeControlToggleSm"
|
||||||
|
data-bs-toggle="tooltip"
|
||||||
|
data-bs-placement="bottom"
|
||||||
|
data-bs-title="{{ _("Switch theme") }}"
|
||||||
|
style="height:32px;
|
||||||
|
width:32px">
|
||||||
|
<span class="icon" data-feather="sun"></span>
|
||||||
|
</label>
|
||||||
</div>
|
</div>
|
||||||
</li>
|
</li>
|
||||||
<li class="nav-item dropdown">
|
<li class="nav-item dropdown">
|
||||||
<a class="nav-link lh-1 pe-0" href="#" id="languageDropdown" role="button" data-bs-toggle="dropdown" aria-expanded="false" data-bs-auto-close="outside" aria-haspopup="true" >
|
<a class="nav-link lh-1 pe-0"
|
||||||
<span class="me-1" data-feather="globe" ></span>
|
href="#"
|
||||||
|
id="languageDropdown"
|
||||||
|
role="button"
|
||||||
|
data-bs-toggle="dropdown"
|
||||||
|
aria-expanded="false"
|
||||||
|
data-bs-auto-close="outside"
|
||||||
|
aria-haspopup="true">
|
||||||
|
<span class="me-1" data-feather="globe"></span>
|
||||||
</a>
|
</a>
|
||||||
<div class="dropdown-menu dropdown-menu-end navbar-dropdown-caret py-0 shadow border" aria-labelledby="languageDropdown">
|
<div class="dropdown-menu dropdown-menu-end navbar-dropdown-caret py-0 shadow border"
|
||||||
<a class="dropdown-item fw-lighter" href="{% url 'switch_language' %}?language=en">English</a>
|
aria-labelledby="languageDropdown">
|
||||||
<a class="dropdown-item fw-lighter" href="{% url 'switch_language' %}?language=ar">عربي</a>
|
<a class="dropdown-item fw-lighter"
|
||||||
|
href="{% url 'switch_language' %}?language=en">English</a>
|
||||||
|
<a class="dropdown-item fw-lighter"
|
||||||
|
href="{% url 'switch_language' %}?language=ar">عربي</a>
|
||||||
</div>
|
</div>
|
||||||
</li>
|
</li>
|
||||||
{% if user.is_authenticated %}
|
{% if user.is_authenticated %}
|
||||||
{% include "notifications.html" %}
|
{% include "notifications.html" %}
|
||||||
{% endif %}
|
{% endif %}
|
||||||
|
|
||||||
{% if user.is_authenticated and request.is_dealer or request.is_staff %}
|
{% if user.is_authenticated and request.is_dealer or request.is_staff %}
|
||||||
<li class="nav-item dropdown">
|
<li class="nav-item dropdown">
|
||||||
<a hx-boost="false" class="nav-link lh-1 pe-0" id="navbarDropdownUser" role="button" data-bs-toggle="dropdown" data-bs-auto-close="outside" aria-haspopup="true" aria-expanded="false">
|
<a hx-boost="false"
|
||||||
|
class="nav-link lh-1 pe-0"
|
||||||
|
id="navbarDropdownUser"
|
||||||
|
role="button"
|
||||||
|
data-bs-toggle="dropdown"
|
||||||
|
data-bs-auto-close="outside"
|
||||||
|
aria-haspopup="true"
|
||||||
|
aria-expanded="false">
|
||||||
<div class="avatar avatar-l text-center align-middle">
|
<div class="avatar avatar-l text-center align-middle">
|
||||||
{% if request.is_dealer and user.dealer.logo %}
|
{% if request.is_dealer and user.dealer.logo %}
|
||||||
<img class="rounded-circle" src="{{ user.dealer.logo.url }}" alt="" />
|
<img class="rounded-circle" src="{{ user.dealer.logo.url }}" alt="" />
|
||||||
@ -495,7 +570,8 @@
|
|||||||
{% endif %}
|
{% endif %}
|
||||||
</div>
|
</div>
|
||||||
</a>
|
</a>
|
||||||
<div class="dropdown-menu dropdown-menu-end navbar-dropdown-caret py-0 dropdown-profile shadow border" aria-labelledby="navbarDropdownUser">
|
<div class="dropdown-menu dropdown-menu-end navbar-dropdown-caret py-0 dropdown-profile shadow border"
|
||||||
|
aria-labelledby="navbarDropdownUser">
|
||||||
<div class="card position-relative border-0">
|
<div class="card position-relative border-0">
|
||||||
<div class="card-body p-0">
|
<div class="card-body p-0">
|
||||||
<div class="text-center pt-4 pb-3">
|
<div class="text-center pt-4 pb-3">
|
||||||
@ -508,7 +584,6 @@
|
|||||||
<span class="fa fa-user text-body-tertiary fa-2x" style="width: 32px;"></span>
|
<span class="fa fa-user text-body-tertiary fa-2x" style="width: 32px;"></span>
|
||||||
{% endif %}
|
{% endif %}
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
{% if request.is_dealer %}
|
{% if request.is_dealer %}
|
||||||
<h6 class="mt-2 text-body-emphasis">{{ user.dealer.get_local_name }}</h6>
|
<h6 class="mt-2 text-body-emphasis">{{ user.dealer.get_local_name }}</h6>
|
||||||
{% else %}
|
{% else %}
|
||||||
@ -520,38 +595,46 @@
|
|||||||
<ul class="nav d-flex flex-column mb-2 pb-1">
|
<ul class="nav d-flex flex-column mb-2 pb-1">
|
||||||
{% if request.is_dealer %}
|
{% if request.is_dealer %}
|
||||||
<li class="nav-item">
|
<li class="nav-item">
|
||||||
<a class="nav-link px-3 d-block" href="{% url 'dealer_detail' request.dealer.slug %}"> <span class="me-2 text-body align-bottom" data-feather="user"></span><span>{% translate 'profile'|capfirst %}</span></a>
|
<a class="nav-link px-3 d-block"
|
||||||
|
href="{% url 'dealer_detail' request.dealer.slug %}"> <span class="me-2 text-body align-bottom" data-feather="user"></span><span>{% translate 'profile'|capfirst %}</span></a>
|
||||||
</li>
|
</li>
|
||||||
{% else %}
|
{% else %}
|
||||||
<li class="nav-item">
|
<li class="nav-item">
|
||||||
<a hx-boost="false" class="nav-link px-3 d-block" href="{% url 'staff_detail' request.dealer.slug request.staff.slug %}"> <span class="me-2 text-body align-bottom" data-feather="user"></span><span>{% translate 'profile'|capfirst %}</span></a>
|
<a hx-boost="false"
|
||||||
|
class="nav-link px-3 d-block"
|
||||||
|
href="{% url 'staff_detail' request.dealer.slug request.staff.slug %}"> <span class="me-2 text-body align-bottom" data-feather="user"></span><span>{% translate 'profile'|capfirst %}</span></a>
|
||||||
</li>
|
</li>
|
||||||
{% endif %}
|
{% endif %}
|
||||||
{% if request.is_dealer %}
|
{% if request.is_dealer %}
|
||||||
<li class="nav-item">
|
<li class="nav-item">
|
||||||
<a class="nav-link px-3 d-block" href="{% url 'user_list' request.dealer.slug %}"><span class="me-2 text-body align-bottom" data-feather="users"></span>{{ _("Staff & Groups") }}</a>
|
<a class="nav-link px-3 d-block"
|
||||||
|
href="{% url 'user_list' request.dealer.slug %}"><span class="me-2 text-body align-bottom" data-feather="users"></span>{{ _("Staff & Groups") }}</a>
|
||||||
</li>
|
</li>
|
||||||
{% comment %} <li class="nav-item">
|
{% comment %} <li class="nav-item">
|
||||||
<a class="nav-link px-3 d-block" href="{% url 'dealer_activity' request.dealer.slug %}"> <span class="me-2 text-body align-bottom" data-feather="lock"></span>{{ _("Activities") }}</a>
|
<a class="nav-link px-3 d-block" href="{% url 'dealer_activity' request.dealer.slug %}"> <span class="me-2 text-body align-bottom" data-feather="lock"></span>{{ _("Activities") }}</a>
|
||||||
</li> {% endcomment %}
|
</li> {% endcomment %}
|
||||||
{% endif %}
|
{% endif %}
|
||||||
<li class="nav-item">
|
<li class="nav-item">
|
||||||
|
|
||||||
{% if request.is_dealer %}
|
{% if request.is_dealer %}
|
||||||
<a class="nav-link px-3 d-block" href="{% url 'dealer_settings' request.dealer.slug %}"> <span class="me-2 text-body align-bottom" data-feather="settings"></span>{{ _("Settings") }}</a>
|
<a class="nav-link px-3 d-block"
|
||||||
|
href="{% url 'dealer_settings' request.dealer.slug %}"> <span class="me-2 text-body align-bottom" data-feather="settings"></span>{{ _("Settings") }}</a>
|
||||||
{% endif %}
|
{% endif %}
|
||||||
</li>
|
</li>
|
||||||
<li class="nav-item">
|
<li class="nav-item">
|
||||||
{% if request.is_dealer %}
|
{% if request.is_dealer %}
|
||||||
<a class="nav-link px-3 d-block" href="{% url 'management' request.dealer.slug %}"> <span class="me-2 text-body align-bottom" data-feather="shield"></span>{{ _("Admin Managemnet") }}</a>
|
<a class="nav-link px-3 d-block"
|
||||||
|
href="{% url 'management' request.dealer.slug %}"> <span class="me-2 text-body align-bottom" data-feather="shield"></span>{{ _("Admin Managemnet") }}</a>
|
||||||
{% endif %}
|
{% endif %}
|
||||||
</li>
|
</li>
|
||||||
<li class="nav-item">
|
<li class="nav-item">
|
||||||
<a class="nav-link px-3 d-block" href="{% url 'ticket_list' request.dealer.slug %}"> <span class="me-2 text-body align-bottom" data-feather="help-circle"></span>{{ _("Help Center") }}</a>
|
<a class="nav-link px-3 d-block"
|
||||||
|
href="{% url 'ticket_list' request.dealer.slug %}"> <span class="me-2 text-body align-bottom" data-feather="help-circle"></span>{{ _("Help Center") }}</a>
|
||||||
</li>
|
</li>
|
||||||
{% if request.is_staff %}
|
{% if request.is_staff %}
|
||||||
<li class="nav-item">
|
<li class="nav-item">
|
||||||
<a hx-boost="false" class="nav-link px-3 d-block" href="{% url 'schedule_calendar' request.dealer.slug%}"> <span class="me-2 text-body align-bottom" data-feather="calendar"></span>{{ _("My Calendar") }}</a>
|
<a hx-boost="false"
|
||||||
|
class="nav-link px-3 d-block"
|
||||||
|
href="{% url 'schedule_calendar' request.dealer.slug %}"> <span class="me-2 text-body align-bottom" data-feather="calendar"></span>{{ _("My Calendar") }}</a>
|
||||||
</li>
|
</li>
|
||||||
{% endif %}
|
{% endif %}
|
||||||
<!--<li class="nav-item"><a class="nav-link px-3 d-block" href=""> Language</a></li>-->
|
<!--<li class="nav-item"><a class="nav-link px-3 d-block" href=""> Language</a></li>-->
|
||||||
@ -565,23 +648,26 @@
|
|||||||
</ul>
|
</ul>
|
||||||
</hr>
|
</hr>
|
||||||
<div class="px-3">
|
<div class="px-3">
|
||||||
<a class="btn btn-sm btn-phoenix-danger d-flex flex-center w-100" href="{% url 'account_logout' %}"> <span class="fas fa-power-off me-2"> </span>{% trans 'Sign Out' %}</a>
|
<a class="btn btn-sm btn-phoenix-danger d-flex flex-center w-100"
|
||||||
|
href="{% url 'account_logout' %}"> <span class="fas fa-power-off me-2"></span>{% trans 'Sign Out' %}</a>
|
||||||
</div>
|
</div>
|
||||||
<div class="my-2 text-center fw-bold fs-10 text-body-quaternary">
|
<div class="my-2 text-center fw-bold fs-10 text-body-quaternary">
|
||||||
<a class="text-body-quaternary me-1" href="">{% trans 'Privacy policy' %}</a>•<a class="text-body-quaternary mx-1" href="">{% trans 'Terms' %}</a>•<a class="text-body-quaternary ms-1" href="">Cookies</a>
|
<a class="text-body-quaternary me-1" href="">{% trans 'Privacy policy' %}</a>•<a class="text-body-quaternary mx-1" href="">{% trans 'Terms' %}</a>•<a class="text-body-quaternary ms-1" href="">Cookies</a>
|
||||||
</div>
|
</div>
|
||||||
{% else %}
|
{% else %}
|
||||||
<div class="px-3">
|
<div class="px-3">
|
||||||
<a class="btn btn-phoenix-succes d-flex flex-center w-100" href="{% url 'account_login' %}"> <span class="me-2" data-feather="log-in"> </span>{% trans 'Sign In' %}</a>
|
<a class="btn btn-phoenix-succes d-flex flex-center w-100"
|
||||||
|
href="{% url 'account_login' %}"> <span class="me-2" data-feather="log-in"></span>{% trans 'Sign In' %}</a>
|
||||||
</div>
|
</div>
|
||||||
<div class="px-3">
|
<div class="px-3">
|
||||||
<a class="btn btn-phoenix-primary d-flex flex-center w-100" href="{% url 'account_signup' %}"> <span class="me-2" data-feather="user-plus"> </span>{% trans 'Sign Up' %}</a>
|
<a class="btn btn-phoenix-primary d-flex flex-center w-100"
|
||||||
|
href="{% url 'account_signup' %}"> <span class="me-2" data-feather="user-plus"></span>{% trans 'Sign Up' %}</a>
|
||||||
</div>
|
</div>
|
||||||
{% endif %}
|
{% endif %}
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</li>
|
</li>
|
||||||
</ul>
|
</ul>
|
||||||
</div>
|
</div>
|
||||||
</nav>
|
</nav>
|
||||||
|
|||||||
@ -3,11 +3,7 @@
|
|||||||
{% block content %}
|
{% block content %}
|
||||||
{% if request.user.is_authenticated %}
|
{% if request.user.is_authenticated %}
|
||||||
<div id="dashboard-content"
|
<div id="dashboard-content"
|
||||||
hx-get="{% if request.is_sales and not request.is_manager %}
|
hx-get="{% if request.is_sales and not request.is_manager %} {% url 'sales_dashboard' request.dealer.slug %} {% else %} {% url 'general_dashboard' request.dealer.slug %} {% endif %}"
|
||||||
{% url 'sales_dashboard' request.dealer.slug %}
|
|
||||||
{% else %}
|
|
||||||
{% url 'general_dashboard' request.dealer.slug %}
|
|
||||||
{% endif %}"
|
|
||||||
hx-trigger="load"
|
hx-trigger="load"
|
||||||
hx-target="#dashboard-content"
|
hx-target="#dashboard-content"
|
||||||
hx-swap="innerHTML">
|
hx-swap="innerHTML">
|
||||||
@ -17,6 +13,4 @@
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
{% endif %}
|
{% endif %}
|
||||||
|
|
||||||
|
|
||||||
{% endblock %}
|
{% endblock %}
|
||||||
|
|||||||
@ -1,7 +1,8 @@
|
|||||||
{% extends "base.html" %}
|
{% extends "base.html" %}
|
||||||
{% load i18n %}
|
{% load i18n %}
|
||||||
{%block title%} {%trans 'Add Colors'%} {% endblock%}
|
{% block title %}
|
||||||
{% block content %}
|
{% trans 'Add Colors' %} {% endblock %}
|
||||||
|
{% block content %}
|
||||||
<div class="row mt-4 mb-3">
|
<div class="row mt-4 mb-3">
|
||||||
<h3 class="text-center">{% trans "Add Colors" %}</h3>
|
<h3 class="text-center">{% trans "Add Colors" %}</h3>
|
||||||
<p class="text-center">
|
<p class="text-center">
|
||||||
@ -19,8 +20,8 @@
|
|||||||
<input class="color-radio"
|
<input class="color-radio"
|
||||||
type="radio"
|
type="radio"
|
||||||
name="exterior"
|
name="exterior"
|
||||||
value="{{ color.id }}" {% if color.id == form.instance.exterior.id %}checked{% endif %}>
|
value="{{ color.id }}"
|
||||||
|
{% if color.id == form.instance.exterior.id %}checked{% endif %}>
|
||||||
<div class="card-body color-display"
|
<div class="card-body color-display"
|
||||||
style="background-color: rgb({{ color.rgb }})">
|
style="background-color: rgb({{ color.rgb }})">
|
||||||
<div class="">
|
<div class="">
|
||||||
@ -40,7 +41,8 @@
|
|||||||
<input class="color-radio"
|
<input class="color-radio"
|
||||||
type="radio"
|
type="radio"
|
||||||
name="interior"
|
name="interior"
|
||||||
value="{{ color.id }}" {% if color.id == form.instance.interior.id %}checked{% endif %}>
|
value="{{ color.id }}"
|
||||||
|
{% if color.id == form.instance.interior.id %}checked{% endif %}>
|
||||||
<div class="card-body color-display"
|
<div class="card-body color-display"
|
||||||
style="background-color: rgb({{ color.rgb }})">
|
style="background-color: rgb({{ color.rgb }})">
|
||||||
<div class="">
|
<div class="">
|
||||||
@ -52,12 +54,12 @@
|
|||||||
</div>
|
</div>
|
||||||
{% endfor %}
|
{% endfor %}
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class="d-flex justify-content-center mt-4">
|
<div class="d-flex justify-content-center mt-4">
|
||||||
<button class="btn btn-lg btn-phoenix-primary me-2" type="submit">
|
<button class="btn btn-lg btn-phoenix-primary me-2" type="submit">
|
||||||
<i class="fa-solid fa-floppy-disk me-1"></i>{{ _("Save") }}
|
<i class="fa-solid fa-floppy-disk me-1"></i>{{ _("Save") }}
|
||||||
</button>
|
</button>
|
||||||
<a href="{% url 'car_detail' request.dealer.slug car.slug %}" class="btn btn-lg btn-phoenix-secondary"><i class="fa-solid fa-ban me-1"></i>{% trans "Cancel" %}</a>
|
<a href="{% url 'car_detail' request.dealer.slug car.slug %}"
|
||||||
|
class="btn btn-lg btn-phoenix-secondary"><i class="fa-solid fa-ban me-1"></i>{% trans "Cancel" %}</a>
|
||||||
</div>
|
</div>
|
||||||
</form>
|
</form>
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@ -1,41 +1,34 @@
|
|||||||
{% extends 'base.html' %}
|
{% extends 'base.html' %}
|
||||||
{% load i18n %}
|
{% load i18n %}
|
||||||
|
|
||||||
{% block title %}Delete Car{% endblock %}
|
{% block title %}Delete Car{% endblock %}
|
||||||
|
|
||||||
{% block content %}
|
{% block content %}
|
||||||
<main class="d-flex align-items-center justify-content-center min-vh-50 py-5">
|
<main class="d-flex align-items-center justify-content-center min-vh-50 py-5">
|
||||||
<div class="col-md-6 ">
|
<div class="col-md-6 ">
|
||||||
<div class="card shadow-lg border-0 rounded-4 overflow-hidden animate__animated animate__fadeInUp">
|
<div class="card shadow-lg border-0 rounded-4 overflow-hidden animate__animated animate__fadeInUp">
|
||||||
|
|
||||||
<div class="card-body p-4 p-md-5 text-center bg-gradient">
|
<div class="card-body p-4 p-md-5 text-center bg-gradient">
|
||||||
<div class="mb-4">
|
<div class="mb-4">
|
||||||
<i class="fa-solid fa-triangle-exclamation text-danger" style="font-size: 2rem;"></i>
|
<i class="fa-solid fa-triangle-exclamation text-danger"
|
||||||
|
style="font-size: 2rem"></i>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<h1 class="card-title fw-bold mb-3 fs-4">{% trans 'Confirm Deletion' %}</h1>
|
<h1 class="card-title fw-bold mb-3 fs-4">{% trans 'Confirm Deletion' %}</h1>
|
||||||
|
<p class="fs-7 mb-4">{% trans "Are you absolutely sure you want to delete the car" %}</p>
|
||||||
<p class="fs-7 mb-4">
|
|
||||||
{% trans "Are you absolutely sure you want to delete the car" %}
|
|
||||||
</p>
|
|
||||||
<p class="fs-6 mb-4">
|
<p class="fs-6 mb-4">
|
||||||
"<strong class="">{{ car }}</strong>"?
|
"<strong class="">{{ car }}</strong>"?
|
||||||
</p>
|
</p>
|
||||||
<p class="fs-7 mb-4">
|
<p class="fs-7 mb-4">{% trans "This action is permanent and cannot be undone." %}</p>
|
||||||
{% trans "This action is permanent and cannot be undone." %}
|
<form method="post"
|
||||||
</p>
|
class="d-grid gap-3 d-sm-flex justify-content-sm-center">
|
||||||
|
|
||||||
<form method="post" class="d-grid gap-3 d-sm-flex justify-content-sm-center">
|
|
||||||
{% csrf_token %}
|
{% csrf_token %}
|
||||||
<button type="submit" class="btn btn-phoenix-danger btn-lg px-5">
|
<button type="submit" class="btn btn-phoenix-danger btn-lg px-5">
|
||||||
<i class="fa-solid fa-trash-can me-2"></i>{% trans 'Confirm Delete' %}
|
<i class="fa-solid fa-trash-can me-2"></i>{% trans 'Confirm Delete' %}
|
||||||
</button>
|
</button>
|
||||||
<a href="{% url 'car_list' request.dealer.slug %}" class="btn btn-phoenix-secondary btn-lg px-5">
|
<a href="{% url 'car_list' request.dealer.slug %}"
|
||||||
|
class="btn btn-phoenix-secondary btn-lg px-5">
|
||||||
<i class="fa-solid fa-ban me-2"></i>{% trans 'Cancel' %}
|
<i class="fa-solid fa-ban me-2"></i>{% trans 'Cancel' %}
|
||||||
</a>
|
</a>
|
||||||
</form>
|
</form>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</main>
|
</main>
|
||||||
{% endblock %}
|
{% endblock %}
|
||||||
@ -76,8 +76,6 @@
|
|||||||
{% if perms.inventory.view_car %}
|
{% if perms.inventory.view_car %}
|
||||||
<div class="row-fluid {% if car.status == 'sold' %}disabled{% endif %}">
|
<div class="row-fluid {% if car.status == 'sold' %}disabled{% endif %}">
|
||||||
<div class="row g-3 justify-content-between">
|
<div class="row g-3 justify-content-between">
|
||||||
|
|
||||||
|
|
||||||
<div class="col-md-6">
|
<div class="col-md-6">
|
||||||
<div class="row mb-2">
|
<div class="row mb-2">
|
||||||
<div class="col-md-4">
|
<div class="col-md-4">
|
||||||
@ -87,20 +85,16 @@
|
|||||||
alt="{{ car.vin }}" />
|
alt="{{ car.vin }}" />
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class="col-md-8">
|
<div class="col-md-8">
|
||||||
<div class="card rounded shadow d-flex align-content-center
|
<div class="card rounded shadow d-flex align-content-center {% if car.get_transfer %}transfer{% endif %}">
|
||||||
{% if car.get_transfer %}transfer{% endif %}">
|
|
||||||
<p class="card-header rounded-top fw-bold">{% trans 'Financial Details' %}</p>
|
<p class="card-header rounded-top fw-bold">{% trans 'Financial Details' %}</p>
|
||||||
<div class="card-body">
|
<div class="card-body">
|
||||||
<div class="table-responsive scrollbar mb-3">
|
<div class="table-responsive scrollbar mb-3">
|
||||||
<table class="table table-sm fs-9 mb-0 overflow-hidden">
|
<table class="table table-sm fs-9 mb-0 overflow-hidden">
|
||||||
{% if car.marked_price %}
|
{% if car.marked_price %}
|
||||||
<tr>
|
<tr>
|
||||||
|
|
||||||
<th>{% trans "Cost Price"|capfirst %}</th>
|
<th>{% trans "Cost Price"|capfirst %}</th>
|
||||||
<td>{{ car.cost_price|floatformat:2 }}</td>
|
<td>{{ car.cost_price|floatformat:2 }}</td>
|
||||||
|
|
||||||
</tr>
|
</tr>
|
||||||
<tr>
|
<tr>
|
||||||
<th>{% trans "Marked Price"|capfirst %}</th>
|
<th>{% trans "Marked Price"|capfirst %}</th>
|
||||||
@ -134,7 +128,6 @@
|
|||||||
<th>{% trans "Total"|capfirst %}</th>
|
<th>{% trans "Total"|capfirst %}</th>
|
||||||
<td>{{ car.finances.total_vat|floatformat:2 }}</td>
|
<td>{{ car.finances.total_vat|floatformat:2 }}</td>
|
||||||
</tr> {% endcomment %}
|
</tr> {% endcomment %}
|
||||||
|
|
||||||
<tr>
|
<tr>
|
||||||
<td colspan="2">
|
<td colspan="2">
|
||||||
{% if not car.get_transfer %}
|
{% if not car.get_transfer %}
|
||||||
@ -145,27 +138,19 @@
|
|||||||
{% endif %}
|
{% endif %}
|
||||||
</td>
|
</td>
|
||||||
</tr>
|
</tr>
|
||||||
|
|
||||||
{% else %}
|
{% else %}
|
||||||
<p>{% trans "No finance details available." %}</p>
|
<p>{% trans "No finance details available." %}</p>
|
||||||
|
|
||||||
<a href="{% url 'car_finance_update' request.dealer.slug car.slug %}"
|
<a href="{% url 'car_finance_update' request.dealer.slug car.slug %}"
|
||||||
class="btn btn-phoenix-success btn-sm mb-3">{% trans "Add" %}</a>
|
class="btn btn-phoenix-success btn-sm mb-3">{% trans "Add" %}</a>
|
||||||
|
|
||||||
{% endif %}
|
{% endif %}
|
||||||
</table>
|
</table>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
{% if perms.inventory.view_carcolors %}
|
{% if perms.inventory.view_carcolors %}
|
||||||
<div class="card rounded shadow d-flex align-content-center mt-3
|
<div class="card rounded shadow d-flex align-content-center mt-3 {% if car.get_transfer %}transfer{% endif %}">
|
||||||
{% if car.get_transfer %}transfer{% endif %}">
|
|
||||||
<p class="card-header rounded-top fw-bold">{% trans 'Colors Details' %}</p>
|
<p class="card-header rounded-top fw-bold">{% trans 'Colors Details' %}</p>
|
||||||
<div class="card-body">
|
<div class="card-body">
|
||||||
<div class="table-responsive scrollbar mb-3">
|
<div class="table-responsive scrollbar mb-3">
|
||||||
@ -223,7 +208,6 @@
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
{% endif %}
|
{% endif %}
|
||||||
|
|
||||||
{% if car.status != 'transfer' %}
|
{% if car.status != 'transfer' %}
|
||||||
{% if perms.inventory.view_carreservation %}
|
{% if perms.inventory.view_carreservation %}
|
||||||
<div class="card rounded shadow d-flex align-content-center mt-3 h-full w-100">
|
<div class="card rounded shadow d-flex align-content-center mt-3 h-full w-100">
|
||||||
@ -293,9 +277,6 @@
|
|||||||
</div>
|
</div>
|
||||||
{% endif %}
|
{% endif %}
|
||||||
{% endif %}
|
{% endif %}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
<!-- Transfer Table -->
|
<!-- Transfer Table -->
|
||||||
{% if car.status == 'transfer' and car.get_transfer %}
|
{% if car.status == 'transfer' and car.get_transfer %}
|
||||||
<div class="card rounded shadow d-flex align-content-center mt-3">
|
<div class="card rounded shadow d-flex align-content-center mt-3">
|
||||||
@ -346,10 +327,8 @@
|
|||||||
</div>
|
</div>
|
||||||
{% endif %}
|
{% endif %}
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class="col-md-6">
|
<div class="col-md-6">
|
||||||
<div class="card mb-3 rounded shadow d-flex align-content-center
|
<div class="card mb-3 rounded shadow d-flex align-content-center {% if car.get_transfer %}disabled{% endif %}">
|
||||||
{% if car.get_transfer %}disabled{% endif %}">
|
|
||||||
<p class="card-header rounded-top fw-bold">{% trans 'Car Details' %}</p>
|
<p class="card-header rounded-top fw-bold">{% trans 'Car Details' %}</p>
|
||||||
<div class="card-body">
|
<div class="card-body">
|
||||||
<div class="table-responsive scrollbar mb-3">
|
<div class="table-responsive scrollbar mb-3">
|
||||||
@ -516,9 +495,6 @@
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
{% if car.status == 'sold' %}
|
{% if car.status == 'sold' %}
|
||||||
<img class="car_status"
|
<img class="car_status"
|
||||||
src="{% static 'images/sold.png' %}"
|
src="{% static 'images/sold.png' %}"
|
||||||
@ -527,7 +503,6 @@
|
|||||||
alt="">
|
alt="">
|
||||||
{% endif %}
|
{% endif %}
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<!-- Custom Card Modal -->
|
<!-- Custom Card Modal -->
|
||||||
<div class="modal fade"
|
<div class="modal fade"
|
||||||
id="customCardModal"
|
id="customCardModal"
|
||||||
@ -631,9 +606,8 @@
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
{% endif %}
|
{% endif %}
|
||||||
{% endblock %}
|
{% endblock %}
|
||||||
|
{% block customJS %}
|
||||||
{% block customJS %}
|
|
||||||
<script>
|
<script>
|
||||||
document.addEventListener("DOMContentLoaded", function () {
|
document.addEventListener("DOMContentLoaded", function () {
|
||||||
const csrftoken = getCookie("csrftoken");
|
const csrftoken = getCookie("csrftoken");
|
||||||
|
|||||||
@ -6,7 +6,7 @@
|
|||||||
{% trans 'Edit Car' %}
|
{% trans 'Edit Car' %}
|
||||||
{% endblock %}
|
{% endblock %}
|
||||||
{% block content %}
|
{% block content %}
|
||||||
<main class="d-flex align-items-center justify-content-center min-vh-80 py-5">
|
<main class="d-flex align-items-center justify-content-center min-vh-80 py-5">
|
||||||
<div class="col-12 col-sm-10 col-md-8 col-lg-6 col-xl-5">
|
<div class="col-12 col-sm-10 col-md-8 col-lg-6 col-xl-5">
|
||||||
<div class="card shadow-lg border-0 rounded-4 overflow-hidden animate__animated animate__fadeInUp">
|
<div class="card shadow-lg border-0 rounded-4 overflow-hidden animate__animated animate__fadeInUp">
|
||||||
<div class="card-header bg-gradient py-4 border-0 rounded-top-4">
|
<div class="card-header bg-gradient py-4 border-0 rounded-top-4">
|
||||||
@ -35,5 +35,5 @@
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</main>
|
</main>
|
||||||
{% endblock %}
|
{% endblock %}
|
||||||
@ -6,16 +6,16 @@
|
|||||||
{% trans "Car Finance Details" %}
|
{% trans "Car Finance Details" %}
|
||||||
{% endblock %}
|
{% endblock %}
|
||||||
{% block content %}
|
{% block content %}
|
||||||
<main class="d-flex align-items-center justify-content-center min-vh-80 py-5">
|
<main class="d-flex align-items-center justify-content-center min-vh-80 py-5">
|
||||||
<div class="col-md-8">
|
<div class="col-md-8">
|
||||||
<div class="card shadow-lg border-0 rounded-4 overflow-hidden animate__animated animate__fadeInUp">
|
<div class="card shadow-lg border-0 rounded-4 overflow-hidden animate__animated animate__fadeInUp">
|
||||||
<div class="card-header bg-gradient py-4 border-0 rounded-top-4">
|
<div class="card-header bg-gradient py-4 border-0 rounded-top-4">
|
||||||
<h3 class="mb-0 fs-4 fw-bold text-center">
|
<h3 class="mb-0 fs-4 fw-bold text-center">
|
||||||
{% trans "Finance Details for" %}
|
{% trans "Finance Details for" %}
|
||||||
<span class="text-gray-300">{{car.id_car_make.name}} {{car.id_car_model.name}} {{car.id_car_serie.serie}} {{car.year}}</span>
|
<span class="text-gray-300">{{ car.id_car_make.name }} {{ car.id_car_model.name }} {{ car.id_car_serie.serie }} {{ car.year }}</span>
|
||||||
<i class="fas fa-solid fa-car ms-2"></i>
|
<i class="fas fa-solid fa-car ms-2"></i>
|
||||||
</h3>
|
</h3>
|
||||||
<p class="text-center mt-1">VIN: {{car.vin}}</p>
|
<p class="text-center mt-1">VIN: {{ car.vin }}</p>
|
||||||
</div>
|
</div>
|
||||||
<div class="card-body p-4 p-md-5">
|
<div class="card-body p-4 p-md-5">
|
||||||
{% if form.errors %}
|
{% if form.errors %}
|
||||||
@ -23,7 +23,10 @@
|
|||||||
<h4 class="alert-heading small">{% trans "Please correct the following errors:" %}</h4>
|
<h4 class="alert-heading small">{% trans "Please correct the following errors:" %}</h4>
|
||||||
<ul class="mb-0">
|
<ul class="mb-0">
|
||||||
{% for field, errors in form.errors.items %}
|
{% for field, errors in form.errors.items %}
|
||||||
<li><strong>{{ field|capfirst }}:</strong> {% for error in errors %}{{ error }}{% endfor %}</li>
|
<li>
|
||||||
|
<strong>{{ field|capfirst }}:</strong>
|
||||||
|
{% for error in errors %}{{ error }}{% endfor %}
|
||||||
|
</li>
|
||||||
{% endfor %}
|
{% endfor %}
|
||||||
</ul>
|
</ul>
|
||||||
</div>
|
</div>
|
||||||
@ -41,7 +44,8 @@
|
|||||||
<i class="fa-solid fa-floppy-disk me-1"></i>
|
<i class="fa-solid fa-floppy-disk me-1"></i>
|
||||||
{{ _("Save") }}
|
{{ _("Save") }}
|
||||||
</button>
|
</button>
|
||||||
<a href="{% url 'car_detail' request.dealer.slug car.slug %}" class="btn btn-phoenix-secondary btn-lg">
|
<a href="{% url 'car_detail' request.dealer.slug car.slug %}"
|
||||||
|
class="btn btn-phoenix-secondary btn-lg">
|
||||||
<i class="fa-solid fa-ban me-1"></i>
|
<i class="fa-solid fa-ban me-1"></i>
|
||||||
{% trans "Cancel" %}
|
{% trans "Cancel" %}
|
||||||
</a>
|
</a>
|
||||||
@ -50,5 +54,5 @@
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</main>
|
</main>
|
||||||
{% endblock %}
|
{% endblock %}
|
||||||
@ -1,7 +1,8 @@
|
|||||||
{% extends "base.html" %}
|
{% extends "base.html" %}
|
||||||
{% load i18n static custom_filters %}
|
{% load i18n static custom_filters %}
|
||||||
{% block title %}
|
{% block title %}
|
||||||
{% trans 'Add New Car' %} {% endblock %}
|
{% trans 'Add New Car' %}
|
||||||
|
{% endblock %}
|
||||||
{% block content %}
|
{% block content %}
|
||||||
<style>
|
<style>
|
||||||
#video {
|
#video {
|
||||||
@ -23,7 +24,8 @@
|
|||||||
{% include "empty-illustration-page.html" with value="Vendor" url=create_vendor_url %}
|
{% include "empty-illustration-page.html" with value="Vendor" url=create_vendor_url %}
|
||||||
{% endif %}
|
{% endif %}
|
||||||
<!---->
|
<!---->
|
||||||
<div class="row justify-content-center mt-5 mb-3 {% if not vendor_exists %}d-none{% endif %}" hx-boost="false">
|
<div class="row justify-content-center mt-5 mb-3 {% if not vendor_exists %}d-none{% endif %}"
|
||||||
|
hx-boost="false">
|
||||||
<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">
|
||||||
@ -132,9 +134,7 @@
|
|||||||
id="specification-btn"
|
id="specification-btn"
|
||||||
data-bs-toggle="modal"
|
data-bs-toggle="modal"
|
||||||
data-bs-target="#specificationsModal"
|
data-bs-target="#specificationsModal"
|
||||||
disabled>
|
disabled>{% trans 'specifications'|capfirst %}</button>
|
||||||
{% trans 'specifications'|capfirst %}
|
|
||||||
</button>
|
|
||||||
<button type="button"
|
<button type="button"
|
||||||
class="btn btn-phoenix-warning"
|
class="btn btn-phoenix-warning"
|
||||||
id="options-btn"
|
id="options-btn"
|
||||||
@ -328,9 +328,9 @@
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<!---->
|
<!---->
|
||||||
{% endblock content %}
|
{% endblock content %}
|
||||||
{% block customJS %}
|
{% block customJS %}
|
||||||
<script>
|
<script>
|
||||||
/*
|
/*
|
||||||
// Global variables
|
// Global variables
|
||||||
let codeReader;
|
let codeReader;
|
||||||
@ -1259,5 +1259,5 @@ function notify(tag, msg) {
|
|||||||
|
|
||||||
// Reinitialize after HTMX swaps
|
// Reinitialize after HTMX swaps
|
||||||
document.addEventListener('htmx:afterSwap', initVinDecoder);
|
document.addEventListener('htmx:afterSwap', initVinDecoder);
|
||||||
</script>
|
</script>
|
||||||
{% endblock customJS %}
|
{% endblock customJS %}
|
||||||
@ -1,8 +1,9 @@
|
|||||||
{% extends "base.html" %}
|
{% extends "base.html" %}
|
||||||
{% load static i18n custom_filters humanize %}
|
{% load static i18n custom_filters humanize %}
|
||||||
{% block title %}
|
{% block title %}
|
||||||
{% trans 'Inventory' %} {% endblock %}
|
{% trans 'Inventory' %}
|
||||||
{% block customCSS %}
|
{% endblock %}
|
||||||
|
{% block customCSS %}
|
||||||
<style>
|
<style>
|
||||||
.htmx-indicator {
|
.htmx-indicator {
|
||||||
opacity: 0;
|
opacity: 0;
|
||||||
@ -22,9 +23,9 @@
|
|||||||
transition: all ease-in 1s;
|
transition: all ease-in 1s;
|
||||||
}
|
}
|
||||||
</style>
|
</style>
|
||||||
{% endblock customCSS %}
|
{% endblock customCSS %}
|
||||||
{% block content %}
|
{% block content %}
|
||||||
{% if cars or request.GET.q%}
|
{% if cars or request.GET.q %}
|
||||||
<div class="container-fluid" id="projectSummary">
|
<div class="container-fluid" id="projectSummary">
|
||||||
<div class="row g-3 justify-content-between align-items-end mb-4">
|
<div class="row g-3 justify-content-between align-items-end mb-4">
|
||||||
<div class="col-12 col-sm-auto">
|
<div class="col-12 col-sm-auto">
|
||||||
@ -188,12 +189,10 @@
|
|||||||
hx-on::before-request="on_before_request()"
|
hx-on::before-request="on_before_request()"
|
||||||
hx-on::after-request="on_after_request()"></div>
|
hx-on::after-request="on_after_request()"></div>
|
||||||
<div class="w-100 list table-responsive">
|
<div class="w-100 list table-responsive">
|
||||||
|
|
||||||
{% for car in cars %}
|
{% for car in cars %}
|
||||||
<div class="card border mb-3 py-0 px-0" id="project-list-table-body">
|
<div class="card border mb-3 py-0 px-0" id="project-list-table-body">
|
||||||
<div class="card-body">
|
<div class="card-body">
|
||||||
<div class="row align-items-center">
|
<div class="row align-items-center">
|
||||||
|
|
||||||
<!-- Vehicle Image/Icon -->
|
<!-- Vehicle Image/Icon -->
|
||||||
<div class="col-auto">
|
<div class="col-auto">
|
||||||
<div class="avatar avatar-3xl">
|
<div class="avatar avatar-3xl">
|
||||||
@ -302,12 +301,8 @@
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
{% else %}
|
{% else %}
|
||||||
{% url "car_add" request.dealer.slug as create_car_url %}
|
{% url "car_add" request.dealer.slug as create_car_url %}
|
||||||
{% include "empty-illustration-page.html" with value="car" url=create_car_url %}
|
{% include "empty-illustration-page.html" with value="car" url=create_car_url %}
|
||||||
{% endif %}
|
{% endif %}
|
||||||
|
{% endblock %}
|
||||||
|
|
||||||
{% endblock %}
|
|
||||||
|
|
||||||
|
|||||||
Some files were not shown because too many files have changed in this diff Show More
Loading…
x
Reference in New Issue
Block a user