Merge remote-tracking branch 'origin/main'
This commit is contained in:
commit
5cfe6d6093
3
.gitignore
vendored
3
.gitignore
vendored
@ -163,8 +163,11 @@ GitHub.sublime-settings
|
|||||||
.history
|
.history
|
||||||
|
|
||||||
|
|
||||||
|
static-copy
|
||||||
static
|
static
|
||||||
|
static/*
|
||||||
staticfiles
|
staticfiles
|
||||||
media
|
media
|
||||||
tmp
|
tmp
|
||||||
logs
|
logs
|
||||||
|
static/testdir
|
||||||
@ -17,40 +17,24 @@ import django
|
|||||||
|
|
||||||
django.setup()
|
django.setup()
|
||||||
|
|
||||||
|
|
||||||
from django.urls import path
|
from django.urls import path
|
||||||
from channels.routing import ProtocolTypeRouter, URLRouter
|
from channels.routing import ProtocolTypeRouter, URLRouter
|
||||||
from whitenoise import WhiteNoise
|
|
||||||
from channels.auth import AuthMiddlewareStack
|
from channels.auth import AuthMiddlewareStack
|
||||||
from api import routing
|
|
||||||
from inventory.notifications.sse import NotificationSSEApp
|
from inventory.notifications.sse import NotificationSSEApp
|
||||||
from django.urls import re_path
|
from django.urls import re_path
|
||||||
from django.core.asgi import get_asgi_application
|
from django.core.asgi import get_asgi_application
|
||||||
from django.contrib.staticfiles.handlers import ASGIStaticFilesHandler
|
|
||||||
from pathlib import Path
|
|
||||||
# application = ProtocolTypeRouter(
|
|
||||||
# {
|
|
||||||
# "http": get_asgi_application(),
|
|
||||||
# # "websocket": AuthMiddlewareStack(URLRouter(routing.websocket_urlpatterns)),
|
|
||||||
# }
|
|
||||||
# )
|
|
||||||
os.environ.setdefault("DJANGO_SETTINGS_MODULE", "car_inventory.settings")
|
|
||||||
django.setup()
|
|
||||||
|
|
||||||
BASE_DIR = Path(__file__).resolve().parent.parent
|
|
||||||
|
# BASE_DIR = Path(__file__).resolve().parent.parent
|
||||||
app = get_asgi_application()
|
app = get_asgi_application()
|
||||||
|
|
||||||
# app = WhiteNoise(app, root=str(BASE_DIR / 'staticfiles'))
|
|
||||||
|
|
||||||
application = ProtocolTypeRouter(
|
application = ProtocolTypeRouter(
|
||||||
{
|
{
|
||||||
"http": AuthMiddlewareStack(
|
"http": AuthMiddlewareStack(
|
||||||
URLRouter(
|
URLRouter(
|
||||||
[
|
[
|
||||||
path("sse/notifications/", NotificationSSEApp()),
|
path("sse/notifications/", NotificationSSEApp()),
|
||||||
re_path(
|
re_path(r"", app),
|
||||||
r"", app
|
|
||||||
), # All other routes go to Django
|
|
||||||
]
|
]
|
||||||
)
|
)
|
||||||
),
|
),
|
||||||
@ -58,5 +42,5 @@ application = ProtocolTypeRouter(
|
|||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
if django.conf.settings.DEBUG:
|
# if django.conf.settings.DEBUG:
|
||||||
application = ASGIStaticFilesHandler(app)
|
# application = ASGIStaticFilesHandler(app)
|
||||||
|
|||||||
@ -5,7 +5,7 @@ from django.urls import path, include
|
|||||||
from schema_graph.views import Schema
|
from schema_graph.views import Schema
|
||||||
from django.conf.urls.static import static
|
from django.conf.urls.static import static
|
||||||
from django.conf.urls.i18n import i18n_patterns
|
from django.conf.urls.i18n import i18n_patterns
|
||||||
from inventory.notifications.sse import NotificationSSEApp
|
# from inventory.notifications.sse import NotificationSSEApp
|
||||||
|
|
||||||
# import debug_toolbar
|
# import debug_toolbar
|
||||||
# from two_factor.urls import urlpatterns as tf_urls
|
# from two_factor.urls import urlpatterns as tf_urls
|
||||||
@ -33,5 +33,6 @@ urlpatterns += i18n_patterns(
|
|||||||
# path('', include(tf_urls)),
|
# path('', include(tf_urls)),
|
||||||
)
|
)
|
||||||
|
|
||||||
# if not settings.DEBUG:
|
if settings.DEBUG:
|
||||||
urlpatterns += static(settings.MEDIA_URL, document_root=settings.MEDIA_ROOT)
|
urlpatterns += static(settings.MEDIA_URL, document_root=settings.MEDIA_ROOT)
|
||||||
|
urlpatterns += static(settings.STATIC_URL, document_root=settings.STATIC_ROOT)
|
||||||
|
|||||||
@ -3,6 +3,7 @@ from django.contrib import admin
|
|||||||
from . import models
|
from . import models
|
||||||
from django_ledger import models as ledger_models
|
from django_ledger import models as ledger_models
|
||||||
from django.contrib import messages
|
from django.contrib import messages
|
||||||
|
|
||||||
# from django_pdf_actions.actions import export_to_pdf_landscape, export_to_pdf_portrait
|
# from django_pdf_actions.actions import export_to_pdf_landscape, export_to_pdf_portrait
|
||||||
# from appointment import models as appointment_models
|
# from appointment import models as appointment_models
|
||||||
from import_export.admin import ExportMixin
|
from import_export.admin import ExportMixin
|
||||||
@ -177,55 +178,52 @@ class CarOptionAdmin(admin.ModelAdmin):
|
|||||||
# actions = [export_to_pdf_landscape, export_to_pdf_portrait]
|
# actions = [export_to_pdf_landscape, export_to_pdf_portrait]
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
@admin.register(models.UserRegistration)
|
@admin.register(models.UserRegistration)
|
||||||
class UserRegistrationAdmin(admin.ModelAdmin):
|
class UserRegistrationAdmin(admin.ModelAdmin):
|
||||||
# Fields to display in the list view
|
# Fields to display in the list view
|
||||||
list_display = [
|
list_display = [
|
||||||
'name',
|
"name",
|
||||||
'arabic_name',
|
"arabic_name",
|
||||||
'email',
|
"email",
|
||||||
'crn',
|
"crn",
|
||||||
'vrn',
|
"vrn",
|
||||||
'phone_number',
|
"phone_number",
|
||||||
'is_created',
|
"is_created",
|
||||||
'created_at',
|
"created_at",
|
||||||
]
|
]
|
||||||
|
|
||||||
# Filters in the right sidebar
|
# Filters in the right sidebar
|
||||||
list_filter = [
|
list_filter = [
|
||||||
'is_created',
|
"is_created",
|
||||||
'created_at',
|
"created_at",
|
||||||
]
|
]
|
||||||
|
|
||||||
# Searchable fields
|
# Searchable fields
|
||||||
search_fields = [
|
search_fields = ["name", "arabic_name", "email", "crn", "vrn", "phone_number"]
|
||||||
'name', 'arabic_name', 'email', 'crn', 'vrn', 'phone_number'
|
|
||||||
]
|
|
||||||
|
|
||||||
# Read-only fields in detail view
|
# Read-only fields in detail view
|
||||||
readonly_fields = [
|
readonly_fields = ["created_at", "updated_at", "is_created", "password"]
|
||||||
'created_at', 'updated_at', 'is_created', 'password'
|
|
||||||
]
|
|
||||||
|
|
||||||
# Organize form layout
|
# Organize form layout
|
||||||
fieldsets = [
|
fieldsets = [
|
||||||
('Account Information', {
|
(
|
||||||
'fields': ('name', 'arabic_name', 'email', 'phone_number')
|
"Account Information",
|
||||||
}),
|
{"fields": ("name", "arabic_name", "email", "phone_number")},
|
||||||
('Business Details', {
|
),
|
||||||
'fields': ('crn', 'vrn', 'address')
|
("Business Details", {"fields": ("crn", "vrn", "address")}),
|
||||||
}),
|
(
|
||||||
('Status', {
|
"Status",
|
||||||
'fields': ('is_created', 'password', 'created_at', 'updated_at'),
|
{
|
||||||
'classes': ('collapse',)
|
"fields": ("is_created", "password", "created_at", "updated_at"),
|
||||||
}),
|
"classes": ("collapse",),
|
||||||
|
},
|
||||||
|
),
|
||||||
]
|
]
|
||||||
|
|
||||||
# Custom action to create accounts
|
# Custom action to create accounts
|
||||||
actions = ['create_dealer_accounts']
|
actions = ["create_dealer_accounts"]
|
||||||
|
|
||||||
@admin.action(description='Create dealer account(s) for selected registrations')
|
@admin.action(description="Create dealer account(s) for selected registrations")
|
||||||
def create_dealer_accounts(self, request, queryset):
|
def create_dealer_accounts(self, request, queryset):
|
||||||
created_count = 0
|
created_count = 0
|
||||||
already_created_count = 0
|
already_created_count = 0
|
||||||
@ -242,7 +240,7 @@ class UserRegistrationAdmin(admin.ModelAdmin):
|
|||||||
self.message_user(
|
self.message_user(
|
||||||
request,
|
request,
|
||||||
f"Error creating account for {registration.name}: {str(e)}",
|
f"Error creating account for {registration.name}: {str(e)}",
|
||||||
level=messages.ERROR
|
level=messages.ERROR,
|
||||||
)
|
)
|
||||||
failed_count += 1
|
failed_count += 1
|
||||||
|
|
||||||
@ -251,17 +249,17 @@ class UserRegistrationAdmin(admin.ModelAdmin):
|
|||||||
self.message_user(
|
self.message_user(
|
||||||
request,
|
request,
|
||||||
f"Successfully created {created_count} account(s).",
|
f"Successfully created {created_count} account(s).",
|
||||||
level=messages.SUCCESS
|
level=messages.SUCCESS,
|
||||||
)
|
)
|
||||||
if already_created_count > 0:
|
if already_created_count > 0:
|
||||||
self.message_user(
|
self.message_user(
|
||||||
request,
|
request,
|
||||||
f"{already_created_count} registration(s) were already created.",
|
f"{already_created_count} registration(s) were already created.",
|
||||||
level=messages.INFO
|
level=messages.INFO,
|
||||||
)
|
)
|
||||||
if failed_count > 0:
|
if failed_count > 0:
|
||||||
self.message_user(
|
self.message_user(
|
||||||
request,
|
request,
|
||||||
f"Failed to create {failed_count} account(s). Check logs.",
|
f"Failed to create {failed_count} account(s). Check logs.",
|
||||||
level=messages.ERROR
|
level=messages.ERROR,
|
||||||
)
|
)
|
||||||
@ -2,6 +2,7 @@ from django.core.cache import cache
|
|||||||
from datetime import datetime
|
from datetime import datetime
|
||||||
from luhnchecker.luhn import Luhn
|
from luhnchecker.luhn import Luhn
|
||||||
from django.contrib.auth.models import Permission
|
from django.contrib.auth.models import Permission
|
||||||
|
|
||||||
# from appointment.models import Service
|
# from appointment.models import Service
|
||||||
from django.core.validators import MinLengthValidator
|
from django.core.validators import MinLengthValidator
|
||||||
from django import forms
|
from django import forms
|
||||||
@ -57,7 +58,7 @@ from .models import (
|
|||||||
Tasks,
|
Tasks,
|
||||||
Recall,
|
Recall,
|
||||||
Ticket,
|
Ticket,
|
||||||
UserRegistration
|
UserRegistration,
|
||||||
)
|
)
|
||||||
from django_ledger import models as ledger_models
|
from django_ledger import models as ledger_models
|
||||||
from django.forms import (
|
from django.forms import (
|
||||||
@ -364,7 +365,14 @@ class CarForm(
|
|||||||
"receiving_date",
|
"receiving_date",
|
||||||
"vendor",
|
"vendor",
|
||||||
]
|
]
|
||||||
required_fields = ["vin","id_car_make", "id_car_model", "id_car_serie", "id_car_trim", "vendor"]
|
required_fields = [
|
||||||
|
"vin",
|
||||||
|
"id_car_make",
|
||||||
|
"id_car_model",
|
||||||
|
"id_car_serie",
|
||||||
|
"id_car_trim",
|
||||||
|
"vendor",
|
||||||
|
]
|
||||||
widgets = {
|
widgets = {
|
||||||
"id_car_make": forms.Select(attrs={"class": "form-select form-select-sm"}),
|
"id_car_make": forms.Select(attrs={"class": "form-select form-select-sm"}),
|
||||||
"receiving_date": forms.DateTimeInput(attrs={"type": "datetime-local"}),
|
"receiving_date": forms.DateTimeInput(attrs={"type": "datetime-local"}),
|
||||||
@ -2123,8 +2131,7 @@ class AdditionalFinancesForm(forms.Form):
|
|||||||
for field in self.fields.values():
|
for field in self.fields.values():
|
||||||
if isinstance(field, forms.ModelMultipleChoiceField):
|
if isinstance(field, forms.ModelMultipleChoiceField):
|
||||||
field.widget.choices = [
|
field.widget.choices = [
|
||||||
(obj.pk, f"{obj.name} - {obj.price:.2f}")
|
(obj.pk, f"{obj.name} - {obj.price:.2f}") for obj in field.queryset
|
||||||
for obj in field.queryset
|
|
||||||
]
|
]
|
||||||
|
|
||||||
|
|
||||||
@ -2141,6 +2148,7 @@ class VatRateForm(forms.ModelForm):
|
|||||||
model = VatRate
|
model = VatRate
|
||||||
fields = ["rate"]
|
fields = ["rate"]
|
||||||
|
|
||||||
|
|
||||||
class CustomSetPasswordForm(SetPasswordForm):
|
class CustomSetPasswordForm(SetPasswordForm):
|
||||||
new_password1 = forms.CharField(
|
new_password1 = forms.CharField(
|
||||||
label="New Password",
|
label="New Password",
|
||||||
@ -2258,17 +2266,25 @@ class TicketResolutionForm(forms.ModelForm):
|
|||||||
self.fields["status"].choices = [("resolved", "Resolved"), ("closed", "Closed")]
|
self.fields["status"].choices = [("resolved", "Resolved"), ("closed", "Closed")]
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
class CarDealershipRegistrationForm(forms.ModelForm):
|
class CarDealershipRegistrationForm(forms.ModelForm):
|
||||||
# Add additional fields for the registration form
|
# Add additional fields for the registration form
|
||||||
|
|
||||||
class Meta:
|
class Meta:
|
||||||
model = UserRegistration
|
model = UserRegistration
|
||||||
fields = ("name","arabic_name", "email","phone_number", "crn", "vrn", "address")
|
fields = (
|
||||||
|
"name",
|
||||||
|
"arabic_name",
|
||||||
|
"email",
|
||||||
|
"phone_number",
|
||||||
|
"crn",
|
||||||
|
"vrn",
|
||||||
|
"address",
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
class CarDetailsEstimateCreate(forms.Form):
|
class CarDetailsEstimateCreate(forms.Form):
|
||||||
customer = forms.ModelChoiceField(
|
customer = forms.ModelChoiceField(
|
||||||
queryset=Customer.objects.all(),
|
queryset=Customer.objects.filter(active=True),
|
||||||
required=True,
|
required=True,
|
||||||
label="Customer",
|
label="Customer",
|
||||||
widget=forms.Select(attrs={"class": "form-control"}),
|
widget=forms.Select(attrs={"class": "form-control"}),
|
||||||
|
|||||||
@ -18,8 +18,8 @@ def check_create_coa_accounts(task):
|
|||||||
logger.warning("Account creation task failed, checking status...")
|
logger.warning("Account creation task failed, checking status...")
|
||||||
|
|
||||||
try:
|
try:
|
||||||
dealer_id = task.kwargs.get('dealer_id',None)
|
dealer_id = task.kwargs.get("dealer_id", None)
|
||||||
coa_slug = task.kwargs.get('coa_slug', None)
|
coa_slug = task.kwargs.get("coa_slug", None)
|
||||||
logger.info(f"Checking accounts for dealer {dealer_id}")
|
logger.info(f"Checking accounts for dealer {dealer_id}")
|
||||||
logger.info(f"COA slug: {coa_slug}")
|
logger.info(f"COA slug: {coa_slug}")
|
||||||
if not dealer_id:
|
if not dealer_id:
|
||||||
@ -37,7 +37,9 @@ def check_create_coa_accounts(task):
|
|||||||
try:
|
try:
|
||||||
coa = entity.get_coa_model_qs().get(slug=coa_slug)
|
coa = entity.get_coa_model_qs().get(slug=coa_slug)
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
logger.error(f"COA with slug {coa_slug} not found for entity {entity.pk}: {e}")
|
logger.error(
|
||||||
|
f"COA with slug {coa_slug} not found for entity {entity.pk}: {e}"
|
||||||
|
)
|
||||||
else:
|
else:
|
||||||
coa = entity.get_default_coa()
|
coa = entity.get_default_coa()
|
||||||
if not coa:
|
if not coa:
|
||||||
@ -49,7 +51,11 @@ def check_create_coa_accounts(task):
|
|||||||
|
|
||||||
missing_accounts = []
|
missing_accounts = []
|
||||||
for account_data in get_accounts_data():
|
for account_data in get_accounts_data():
|
||||||
if not entity.get_all_accounts().filter(coa_model=coa,code=account_data["code"]).exists():
|
if (
|
||||||
|
not entity.get_all_accounts()
|
||||||
|
.filter(coa_model=coa, code=account_data["code"])
|
||||||
|
.exists()
|
||||||
|
):
|
||||||
missing_accounts.append(account_data)
|
missing_accounts.append(account_data)
|
||||||
logger.info(f"Missing account: {account_data['code']}")
|
logger.info(f"Missing account: {account_data['code']}")
|
||||||
|
|
||||||
@ -62,6 +68,8 @@ def check_create_coa_accounts(task):
|
|||||||
|
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
logger.error(f"Error in check_create_coa_accounts hook: {e}")
|
logger.error(f"Error in check_create_coa_accounts hook: {e}")
|
||||||
|
|
||||||
|
|
||||||
# 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"]
|
||||||
|
|||||||
@ -8,19 +8,23 @@ from django.core.management.base import BaseCommand
|
|||||||
|
|
||||||
User = get_user_model()
|
User = get_user_model()
|
||||||
|
|
||||||
|
|
||||||
class Command(BaseCommand):
|
class Command(BaseCommand):
|
||||||
help = "Deactivates expired user plans"
|
help = "Deactivates expired user plans"
|
||||||
|
|
||||||
def handle(self, *args, **options):
|
def handle(self, *args, **options):
|
||||||
users_without_plan = User.objects.filter(
|
users_without_plan = User.objects.filter(
|
||||||
is_active=True, userplan=None, dealer__isnull=False, date_joined__lte=timezone.now()-timedelta(days=7)
|
is_active=True,
|
||||||
|
userplan=None,
|
||||||
|
dealer__isnull=False,
|
||||||
|
date_joined__lte=timezone.now() - timedelta(days=7),
|
||||||
)
|
)
|
||||||
|
|
||||||
count = users_without_plan.count()
|
count = users_without_plan.count()
|
||||||
for user in users_without_plan:
|
for user in users_without_plan:
|
||||||
user.is_active = False
|
user.is_active = False
|
||||||
user.save()
|
user.save()
|
||||||
subject = 'Your account has been deactivated'
|
subject = "Your account has been deactivated"
|
||||||
message = """
|
message = """
|
||||||
Hello {},\n
|
Hello {},\n
|
||||||
Your account has been deactivated, please contact us at {} if you have any questions.
|
Your account has been deactivated, please contact us at {} if you have any questions.
|
||||||
@ -30,7 +34,7 @@ class Command(BaseCommand):
|
|||||||
""".format(user.dealer.name, settings.DEFAULT_FROM_EMAIL)
|
""".format(user.dealer.name, settings.DEFAULT_FROM_EMAIL)
|
||||||
from_email = settings.DEFAULT_FROM_EMAIL
|
from_email = settings.DEFAULT_FROM_EMAIL
|
||||||
recipient_list = user.email
|
recipient_list = user.email
|
||||||
send_email(from_email, recipient_list,subject, message)
|
send_email(from_email, recipient_list, subject, message)
|
||||||
|
|
||||||
self.stdout.write(
|
self.stdout.write(
|
||||||
self.style.SUCCESS(
|
self.style.SUCCESS(
|
||||||
|
|||||||
@ -63,7 +63,7 @@ class Command(BaseCommand):
|
|||||||
|
|
||||||
for plan in expired_plans:
|
for plan in expired_plans:
|
||||||
# try:
|
# try:
|
||||||
if dealer := getattr(plan.user,"dealer", None):
|
if dealer := getattr(plan.user, "dealer", None):
|
||||||
dealer.user.is_active = False
|
dealer.user.is_active = False
|
||||||
dealer.user.save()
|
dealer.user.save()
|
||||||
for staff in dealer.get_staff():
|
for staff in dealer.get_staff():
|
||||||
|
|||||||
@ -7,16 +7,16 @@ from django_ledger.models import EstimateModel, BillModel, AccountModel, LedgerM
|
|||||||
|
|
||||||
class Command(BaseCommand):
|
class Command(BaseCommand):
|
||||||
def handle(self, *args, **kwargs):
|
def handle(self, *args, **kwargs):
|
||||||
Permission.objects.get_or_create(
|
# Permission.objects.get_or_create(
|
||||||
name="Can view crm",
|
# name="Can view crm",
|
||||||
codename="can_view_crm",
|
# codename="can_view_crm",
|
||||||
content_type=ContentType.objects.get_for_model(Lead),
|
# content_type=ContentType.objects.get_for_model(Lead),
|
||||||
)
|
# )
|
||||||
Permission.objects.get_or_create(
|
# Permission.objects.get_or_create(
|
||||||
name="Can reassign lead",
|
# name="Can reassign lead",
|
||||||
codename="can_reassign_lead",
|
# codename="can_reassign_lead",
|
||||||
content_type=ContentType.objects.get_for_model(Lead),
|
# content_type=ContentType.objects.get_for_model(Lead),
|
||||||
)
|
# )
|
||||||
Permission.objects.get_or_create(
|
Permission.objects.get_or_create(
|
||||||
name="Can view sales",
|
name="Can view sales",
|
||||||
codename="can_view_sales",
|
codename="can_view_sales",
|
||||||
@ -47,4 +47,3 @@ class Command(BaseCommand):
|
|||||||
codename="can_approve_estimatemodel",
|
codename="can_approve_estimatemodel",
|
||||||
content_type=ContentType.objects.get_for_model(EstimateModel),
|
content_type=ContentType.objects.get_for_model(EstimateModel),
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|||||||
@ -23,19 +23,19 @@ class Command(BaseCommand):
|
|||||||
# Note: Deleting plans and quotas should cascade to related objects like PlanQuota and PlanPricing.
|
# Note: Deleting plans and quotas should cascade to related objects like PlanQuota and PlanPricing.
|
||||||
self.stdout.write(self.style.SUCCESS("Data reset complete."))
|
self.stdout.write(self.style.SUCCESS("Data reset complete."))
|
||||||
else:
|
else:
|
||||||
self.stdout.write(self.style.NOTICE("Creating or updating default plans and quotas..."))
|
self.stdout.write(
|
||||||
|
self.style.NOTICE("Creating or updating default plans and quotas...")
|
||||||
|
)
|
||||||
|
|
||||||
# Create or get quotas
|
# Create or get quotas
|
||||||
users_quota, created_u = Quota.objects.get_or_create(
|
users_quota, created_u = Quota.objects.get_or_create(
|
||||||
codename="Users",
|
codename="Users", defaults={"name": "Users", "unit": "number"}
|
||||||
defaults={"name": "Users", "unit": "number"}
|
|
||||||
)
|
)
|
||||||
if created_u:
|
if created_u:
|
||||||
self.stdout.write(self.style.SUCCESS('Created quota: "Users"'))
|
self.stdout.write(self.style.SUCCESS('Created quota: "Users"'))
|
||||||
|
|
||||||
cars_quota, created_c = Quota.objects.get_or_create(
|
cars_quota, created_c = Quota.objects.get_or_create(
|
||||||
codename="Cars",
|
codename="Cars", defaults={"name": "Cars", "unit": "number"}
|
||||||
defaults={"name": "Cars", "unit": "number"}
|
|
||||||
)
|
)
|
||||||
if created_c:
|
if created_c:
|
||||||
self.stdout.write(self.style.SUCCESS('Created quota: "Cars"'))
|
self.stdout.write(self.style.SUCCESS('Created quota: "Cars"'))
|
||||||
@ -43,90 +43,81 @@ class Command(BaseCommand):
|
|||||||
# Create or get plans
|
# Create or get plans
|
||||||
basic_plan, created_bp = Plan.objects.get_or_create(
|
basic_plan, created_bp = Plan.objects.get_or_create(
|
||||||
name="Basic",
|
name="Basic",
|
||||||
defaults={"description": "basic plan", "available": True, "visible": True}
|
defaults={"description": "basic plan", "available": True, "visible": True},
|
||||||
)
|
)
|
||||||
if created_bp:
|
if created_bp:
|
||||||
self.stdout.write(self.style.SUCCESS('Created plan: "Basic"'))
|
self.stdout.write(self.style.SUCCESS('Created plan: "Basic"'))
|
||||||
|
|
||||||
pro_plan, created_pp = Plan.objects.get_or_create(
|
pro_plan, created_pp = Plan.objects.get_or_create(
|
||||||
name="Pro",
|
name="Pro",
|
||||||
defaults={"description": "Pro plan", "available": True, "visible": True}
|
defaults={"description": "Pro plan", "available": True, "visible": True},
|
||||||
)
|
)
|
||||||
if created_pp:
|
if created_pp:
|
||||||
self.stdout.write(self.style.SUCCESS('Created plan: "Pro"'))
|
self.stdout.write(self.style.SUCCESS('Created plan: "Pro"'))
|
||||||
|
|
||||||
enterprise_plan, created_ep = Plan.objects.get_or_create(
|
enterprise_plan, created_ep = Plan.objects.get_or_create(
|
||||||
name="Enterprise",
|
name="Enterprise",
|
||||||
defaults={"description": "Enterprise plan", "available": True, "visible": True}
|
defaults={
|
||||||
|
"description": "Enterprise plan",
|
||||||
|
"available": True,
|
||||||
|
"visible": True,
|
||||||
|
},
|
||||||
)
|
)
|
||||||
if created_ep:
|
if created_ep:
|
||||||
self.stdout.write(self.style.SUCCESS('Created plan: "Enterprise"'))
|
self.stdout.write(self.style.SUCCESS('Created plan: "Enterprise"'))
|
||||||
|
|
||||||
# Assign quotas to plans using get_or_create to prevent duplicates
|
# Assign quotas to plans using get_or_create to prevent duplicates
|
||||||
PlanQuota.objects.get_or_create(
|
PlanQuota.objects.get_or_create(
|
||||||
plan=basic_plan,
|
plan=basic_plan, quota=users_quota, defaults={"value": 10000000}
|
||||||
quota=users_quota,
|
|
||||||
defaults={"value": 10000000}
|
|
||||||
)
|
)
|
||||||
PlanQuota.objects.get_or_create(
|
PlanQuota.objects.get_or_create(
|
||||||
plan=basic_plan,
|
plan=basic_plan, quota=cars_quota, defaults={"value": 10000000}
|
||||||
quota=cars_quota,
|
|
||||||
defaults={"value": 10000000}
|
|
||||||
)
|
)
|
||||||
|
|
||||||
# Pro plan quotas
|
# Pro plan quotas
|
||||||
PlanQuota.objects.get_or_create(
|
PlanQuota.objects.get_or_create(
|
||||||
plan=pro_plan,
|
plan=pro_plan, quota=users_quota, defaults={"value": 10000000}
|
||||||
quota=users_quota,
|
|
||||||
defaults={"value": 10000000}
|
|
||||||
)
|
)
|
||||||
PlanQuota.objects.get_or_create(
|
PlanQuota.objects.get_or_create(
|
||||||
plan=pro_plan,
|
plan=pro_plan, quota=cars_quota, defaults={"value": 10000000}
|
||||||
quota=cars_quota,
|
|
||||||
defaults={"value": 10000000}
|
|
||||||
)
|
)
|
||||||
|
|
||||||
# Enterprise plan quotas
|
# Enterprise plan quotas
|
||||||
PlanQuota.objects.get_or_create(
|
PlanQuota.objects.get_or_create(
|
||||||
plan=enterprise_plan,
|
plan=enterprise_plan, quota=users_quota, defaults={"value": 10000000}
|
||||||
quota=users_quota,
|
|
||||||
defaults={"value": 10000000}
|
|
||||||
)
|
)
|
||||||
PlanQuota.objects.get_or_create(
|
PlanQuota.objects.get_or_create(
|
||||||
plan=enterprise_plan,
|
plan=enterprise_plan, quota=cars_quota, defaults={"value": 10000000}
|
||||||
quota=cars_quota,
|
|
||||||
defaults={"value": 10000000}
|
|
||||||
)
|
)
|
||||||
|
|
||||||
# Create or get pricing
|
# Create or get pricing
|
||||||
basic_pricing, created_bp_p = Pricing.objects.get_or_create(
|
basic_pricing, created_bp_p = Pricing.objects.get_or_create(
|
||||||
name="3 Months",
|
name="3 Months", defaults={"period": 90}
|
||||||
defaults={"period": 90}
|
|
||||||
)
|
)
|
||||||
pro_pricing, created_pp_p = Pricing.objects.get_or_create(
|
pro_pricing, created_pp_p = Pricing.objects.get_or_create(
|
||||||
name="6 Months",
|
name="6 Months", defaults={"period": 180}
|
||||||
defaults={"period": 180}
|
|
||||||
)
|
)
|
||||||
enterprise_pricing, created_ep_p = Pricing.objects.get_or_create(
|
enterprise_pricing, created_ep_p = Pricing.objects.get_or_create(
|
||||||
name="1 Year",
|
name="1 Year", defaults={"period": 365}
|
||||||
defaults={"period": 365}
|
|
||||||
)
|
)
|
||||||
|
|
||||||
# Assign pricing to plans
|
# Assign pricing to plans
|
||||||
PlanPricing.objects.get_or_create(
|
PlanPricing.objects.get_or_create(
|
||||||
plan=basic_plan,
|
plan=basic_plan,
|
||||||
pricing=basic_pricing,
|
pricing=basic_pricing,
|
||||||
defaults={"price": Decimal("2997.00")}
|
defaults={"price": Decimal("2997.00")},
|
||||||
)
|
)
|
||||||
PlanPricing.objects.get_or_create(
|
PlanPricing.objects.get_or_create(
|
||||||
plan=pro_plan,
|
plan=pro_plan, pricing=pro_pricing, defaults={"price": Decimal("5395.00")}
|
||||||
pricing=pro_pricing,
|
|
||||||
defaults={"price": Decimal("5395.00")}
|
|
||||||
)
|
)
|
||||||
PlanPricing.objects.get_or_create(
|
PlanPricing.objects.get_or_create(
|
||||||
plan=enterprise_plan,
|
plan=enterprise_plan,
|
||||||
pricing=enterprise_pricing,
|
pricing=enterprise_pricing,
|
||||||
defaults={"price": Decimal("9590.00")}
|
defaults={"price": Decimal("9590.00")},
|
||||||
)
|
)
|
||||||
|
|
||||||
self.stdout.write(self.style.SUCCESS("Subscription plans structure successfully created or updated."))
|
self.stdout.write(
|
||||||
|
self.style.SUCCESS(
|
||||||
|
"Subscription plans structure successfully created or updated."
|
||||||
|
)
|
||||||
|
)
|
||||||
|
|||||||
@ -3,12 +3,13 @@ from django.core.management.base import BaseCommand
|
|||||||
from django.contrib.sites.models import Site
|
from django.contrib.sites.models import Site
|
||||||
from django.conf import settings
|
from django.conf import settings
|
||||||
|
|
||||||
|
|
||||||
class Command(BaseCommand):
|
class Command(BaseCommand):
|
||||||
help = 'Update the default site domain'
|
help = "Update the default site domain"
|
||||||
|
|
||||||
def handle(self, *args, **options):
|
def handle(self, *args, **options):
|
||||||
site = Site.objects.get_current()
|
site = Site.objects.get_current()
|
||||||
site.domain = settings.SITE_DOMAIN
|
site.domain = settings.SITE_DOMAIN
|
||||||
site.name = settings.SITE_NAME
|
site.name = settings.SITE_NAME
|
||||||
site.save()
|
site.save()
|
||||||
self.stdout.write(self.style.SUCCESS(f'Site updated to: {site.domain}'))
|
self.stdout.write(self.style.SUCCESS(f"Site updated to: {site.domain}"))
|
||||||
|
|||||||
@ -10,7 +10,7 @@ from django.urls import reverse
|
|||||||
# from django.utils.text import slugify
|
# from django.utils.text import slugify
|
||||||
from slugify import slugify
|
from slugify import slugify
|
||||||
from django.utils import timezone
|
from django.utils import timezone
|
||||||
from django.core.validators import MinValueValidator,MaxValueValidator
|
from django.core.validators import MinValueValidator, MaxValueValidator
|
||||||
import hashlib
|
import hashlib
|
||||||
from django.db import models
|
from django.db import models
|
||||||
from datetime import timedelta
|
from datetime import timedelta
|
||||||
@ -57,15 +57,17 @@ from encrypted_model_fields.fields import (
|
|||||||
EncryptedEmailField,
|
EncryptedEmailField,
|
||||||
EncryptedTextField,
|
EncryptedTextField,
|
||||||
)
|
)
|
||||||
|
|
||||||
# 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
|
||||||
|
|
||||||
from django_extensions.db.fields import RandomCharField,AutoSlugField
|
from django_extensions.db.fields import RandomCharField, AutoSlugField
|
||||||
|
|
||||||
logger = logging.getLogger(__name__)
|
logger = logging.getLogger(__name__)
|
||||||
logging.basicConfig(level=logging.INFO)
|
logging.basicConfig(level=logging.INFO)
|
||||||
|
|
||||||
|
|
||||||
class Base(models.Model):
|
class Base(models.Model):
|
||||||
id = models.UUIDField(
|
id = models.UUIDField(
|
||||||
unique=True,
|
unique=True,
|
||||||
@ -206,11 +208,8 @@ class VatRate(models.Model):
|
|||||||
max_digits=5,
|
max_digits=5,
|
||||||
decimal_places=2,
|
decimal_places=2,
|
||||||
default=Decimal("0.15"),
|
default=Decimal("0.15"),
|
||||||
validators=[
|
validators=[MinValueValidator(0.0), MaxValueValidator(1.0)],
|
||||||
MinValueValidator(0.0),
|
help_text=_("VAT rate as decimal between 0 and 1 (e.g., 0.2 for 20%)"),
|
||||||
MaxValueValidator(1.0)
|
|
||||||
],
|
|
||||||
help_text=_("VAT rate as decimal between 0 and 1 (e.g., 0.2 for 20%)")
|
|
||||||
)
|
)
|
||||||
is_active = models.BooleanField(default=True)
|
is_active = models.BooleanField(default=True)
|
||||||
created_at = models.DateTimeField(auto_now_add=True)
|
created_at = models.DateTimeField(auto_now_add=True)
|
||||||
@ -776,7 +775,7 @@ class Car(Base):
|
|||||||
make = self.id_car_make.name if self.id_car_make else "Unknown Make"
|
make = self.id_car_make.name if self.id_car_make else "Unknown Make"
|
||||||
model = self.id_car_model.name if self.id_car_model else "Unknown Model"
|
model = self.id_car_model.name if self.id_car_model else "Unknown Model"
|
||||||
trim = self.id_car_trim.name if self.id_car_trim else "Unknown Trim"
|
trim = self.id_car_trim.name if self.id_car_trim else "Unknown Trim"
|
||||||
vin=self.vin if self.vin else None
|
vin = self.vin if self.vin else None
|
||||||
return f"{self.year} - {make} - {model} - {trim}-{vin}"
|
return f"{self.year} - {make} - {model} - {trim}-{vin}"
|
||||||
|
|
||||||
@property
|
@property
|
||||||
@ -843,7 +842,7 @@ class Car(Base):
|
|||||||
def mark_as_sold(self):
|
def mark_as_sold(self):
|
||||||
self.cancel_reservation()
|
self.cancel_reservation()
|
||||||
self.status = CarStatusChoices.SOLD
|
self.status = CarStatusChoices.SOLD
|
||||||
self.sold_date=timezone.now()
|
self.sold_date = timezone.now()
|
||||||
self.save()
|
self.save()
|
||||||
|
|
||||||
def cancel_reservation(self):
|
def cancel_reservation(self):
|
||||||
@ -904,16 +903,23 @@ class Car(Base):
|
|||||||
|
|
||||||
def get_active_estimates(self):
|
def get_active_estimates(self):
|
||||||
try:
|
try:
|
||||||
qs = self.item_model.itemtransactionmodel_set.exclude(ce_model__status="canceled")
|
qs = self.item_model.itemtransactionmodel_set.exclude(
|
||||||
|
ce_model__status="canceled"
|
||||||
|
)
|
||||||
|
|
||||||
data = []
|
data = []
|
||||||
for item in qs:
|
for item in qs:
|
||||||
x = ExtraInfo.objects.filter(object_id=item.ce_model.pk,content_type=ContentType.objects.get_for_model(EstimateModel)).first()
|
x = ExtraInfo.objects.filter(
|
||||||
|
object_id=item.ce_model.pk,
|
||||||
|
content_type=ContentType.objects.get_for_model(EstimateModel),
|
||||||
|
).first()
|
||||||
if x:
|
if x:
|
||||||
data.append(x)
|
data.append(x)
|
||||||
return data
|
return data
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
logger.error(f"Error getting active estimates for car {self.vin} error: {e}")
|
logger.error(
|
||||||
|
f"Error getting active estimates for car {self.vin} error: {e}"
|
||||||
|
)
|
||||||
return []
|
return []
|
||||||
|
|
||||||
@property
|
@property
|
||||||
@ -1389,7 +1395,11 @@ class Dealer(models.Model, LocalizedNameMixin):
|
|||||||
options={"quality": 80},
|
options={"quality": 80},
|
||||||
)
|
)
|
||||||
entity = models.ForeignKey(
|
entity = models.ForeignKey(
|
||||||
EntityModel, on_delete=models.SET_NULL, null=True, blank=True,related_name="dealers"
|
EntityModel,
|
||||||
|
on_delete=models.SET_NULL,
|
||||||
|
null=True,
|
||||||
|
blank=True,
|
||||||
|
related_name="dealers",
|
||||||
)
|
)
|
||||||
joined_at = models.DateTimeField(auto_now_add=True, verbose_name=_("Joined At"))
|
joined_at = models.DateTimeField(auto_now_add=True, verbose_name=_("Joined At"))
|
||||||
updated_at = models.DateTimeField(auto_now=True, verbose_name=_("Updated At"))
|
updated_at = models.DateTimeField(auto_now=True, verbose_name=_("Updated At"))
|
||||||
@ -1421,6 +1431,7 @@ class Dealer(models.Model, LocalizedNameMixin):
|
|||||||
except Exception as e:
|
except Exception as e:
|
||||||
print(e)
|
print(e)
|
||||||
return None
|
return None
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def is_plan_expired(self):
|
def is_plan_expired(self):
|
||||||
try:
|
try:
|
||||||
@ -1455,6 +1466,7 @@ class Dealer(models.Model, LocalizedNameMixin):
|
|||||||
|
|
||||||
def get_vendors(self):
|
def get_vendors(self):
|
||||||
return VendorModel.objects.filter(entity_model=self.entity)
|
return VendorModel.objects.filter(entity_model=self.entity)
|
||||||
|
|
||||||
def get_staff(self):
|
def get_staff(self):
|
||||||
return Staff.objects.filter(dealer=self)
|
return Staff.objects.filter(dealer=self)
|
||||||
|
|
||||||
@ -1505,7 +1517,9 @@ class Staff(models.Model):
|
|||||||
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"),null=True,blank=True)
|
arabic_name = models.CharField(
|
||||||
|
max_length=255, verbose_name=_("Arabic Name"), null=True, blank=True
|
||||||
|
)
|
||||||
phone_number = EncryptedCharField(
|
phone_number = EncryptedCharField(
|
||||||
max_length=255,
|
max_length=255,
|
||||||
verbose_name=_("Phone Number"),
|
verbose_name=_("Phone Number"),
|
||||||
@ -1794,7 +1808,7 @@ class Customer(models.Model):
|
|||||||
commit=False,
|
commit=False,
|
||||||
customer_model_kwargs={
|
customer_model_kwargs={
|
||||||
"customer_name": self.full_name,
|
"customer_name": self.full_name,
|
||||||
"address_1": "",#self.address,
|
"address_1": "", # self.address,
|
||||||
# "phone": self.phone_number,
|
# "phone": self.phone_number,
|
||||||
# "email": self.email,
|
# "email": self.email,
|
||||||
},
|
},
|
||||||
@ -2120,6 +2134,10 @@ class Lead(models.Model):
|
|||||||
slug = RandomCharField(length=8, unique=True)
|
slug = RandomCharField(length=8, unique=True)
|
||||||
|
|
||||||
class Meta:
|
class Meta:
|
||||||
|
permissions = [
|
||||||
|
("can_view_crm", _("Can view CRM")),
|
||||||
|
("can_reassign_lead", _("Can reassign lead")),
|
||||||
|
]
|
||||||
verbose_name = _("Lead")
|
verbose_name = _("Lead")
|
||||||
verbose_name_plural = _("Leads")
|
verbose_name_plural = _("Leads")
|
||||||
indexes = [
|
indexes = [
|
||||||
@ -2305,10 +2323,14 @@ class Schedule(models.Model):
|
|||||||
help_text=_("What is the status of this schedule?"),
|
help_text=_("What is the status of this schedule?"),
|
||||||
)
|
)
|
||||||
created_at = models.DateTimeField(
|
created_at = models.DateTimeField(
|
||||||
auto_now_add=True, verbose_name=_("Created Date"), help_text=_("When was this schedule created?")
|
auto_now_add=True,
|
||||||
|
verbose_name=_("Created Date"),
|
||||||
|
help_text=_("When was this schedule created?"),
|
||||||
)
|
)
|
||||||
updated_at = models.DateTimeField(
|
updated_at = models.DateTimeField(
|
||||||
auto_now=True, verbose_name=_("Updated Date"), help_text=_("When was this schedule last updated?")
|
auto_now=True,
|
||||||
|
verbose_name=_("Updated Date"),
|
||||||
|
help_text=_("When was this schedule last updated?"),
|
||||||
)
|
)
|
||||||
|
|
||||||
def __str__(self):
|
def __str__(self):
|
||||||
@ -2495,13 +2517,12 @@ class Opportunity(models.Model):
|
|||||||
def __str__(self):
|
def __str__(self):
|
||||||
try:
|
try:
|
||||||
if self.customer:
|
if self.customer:
|
||||||
return (
|
return f"Opportunity for {self.customer.first_name} {self.customer.last_name}"
|
||||||
f"Opportunity for {self.customer.first_name} {self.customer.last_name}"
|
|
||||||
)
|
|
||||||
return f"Opportunity for {self.organization.name}"
|
return f"Opportunity for {self.organization.name}"
|
||||||
except Exception:
|
except Exception:
|
||||||
return f"Opportunity for car :{self.car}"
|
return f"Opportunity for car :{self.car}"
|
||||||
|
|
||||||
|
|
||||||
class Notes(models.Model):
|
class Notes(models.Model):
|
||||||
dealer = models.ForeignKey(Dealer, on_delete=models.CASCADE, related_name="notes")
|
dealer = models.ForeignKey(Dealer, on_delete=models.CASCADE, related_name="notes")
|
||||||
content_type = models.ForeignKey(ContentType, on_delete=models.CASCADE)
|
content_type = models.ForeignKey(ContentType, on_delete=models.CASCADE)
|
||||||
@ -2765,7 +2786,6 @@ class Vendor(models.Model, LocalizedNameMixin):
|
|||||||
),
|
),
|
||||||
]
|
]
|
||||||
|
|
||||||
|
|
||||||
def __str__(self):
|
def __str__(self):
|
||||||
return self.name
|
return self.name
|
||||||
|
|
||||||
@ -3155,7 +3175,7 @@ class CustomGroup(models.Model):
|
|||||||
"notes",
|
"notes",
|
||||||
"tasks",
|
"tasks",
|
||||||
"activity",
|
"activity",
|
||||||
"additionalservices"
|
"additionalservices",
|
||||||
],
|
],
|
||||||
)
|
)
|
||||||
self.set_permissions(
|
self.set_permissions(
|
||||||
@ -3272,7 +3292,7 @@ class CustomGroup(models.Model):
|
|||||||
"payment",
|
"payment",
|
||||||
"vendor",
|
"vendor",
|
||||||
"additionalservices",
|
"additionalservices",
|
||||||
'customer'
|
"customer",
|
||||||
],
|
],
|
||||||
other_perms=[
|
other_perms=[
|
||||||
"view_car",
|
"view_car",
|
||||||
@ -3340,7 +3360,9 @@ class DealerSettings(models.Model):
|
|||||||
on_delete=models.SET_NULL,
|
on_delete=models.SET_NULL,
|
||||||
null=True,
|
null=True,
|
||||||
blank=True,
|
blank=True,
|
||||||
help_text=_("Cash account to track cash transactions when an invoice is created."),
|
help_text=_(
|
||||||
|
"Cash account to track cash transactions when an invoice is created."
|
||||||
|
),
|
||||||
verbose_name=_("Invoice Cash Account"),
|
verbose_name=_("Invoice Cash Account"),
|
||||||
)
|
)
|
||||||
invoice_prepaid_account = models.ForeignKey(
|
invoice_prepaid_account = models.ForeignKey(
|
||||||
@ -3349,7 +3371,9 @@ class DealerSettings(models.Model):
|
|||||||
on_delete=models.SET_NULL,
|
on_delete=models.SET_NULL,
|
||||||
null=True,
|
null=True,
|
||||||
blank=True,
|
blank=True,
|
||||||
help_text=_("Prepaid Revenue account to track prepaid revenue when an invoice is created."),
|
help_text=_(
|
||||||
|
"Prepaid Revenue account to track prepaid revenue when an invoice is created."
|
||||||
|
),
|
||||||
verbose_name=_("Invoice Prepaid Account"),
|
verbose_name=_("Invoice Prepaid Account"),
|
||||||
)
|
)
|
||||||
invoice_unearned_account = models.ForeignKey(
|
invoice_unearned_account = models.ForeignKey(
|
||||||
@ -3358,7 +3382,9 @@ class DealerSettings(models.Model):
|
|||||||
on_delete=models.SET_NULL,
|
on_delete=models.SET_NULL,
|
||||||
null=True,
|
null=True,
|
||||||
blank=True,
|
blank=True,
|
||||||
help_text=_("Unearned Revenue account to track unearned revenue when an invoice is created."),
|
help_text=_(
|
||||||
|
"Unearned Revenue account to track unearned revenue when an invoice is created."
|
||||||
|
),
|
||||||
verbose_name=_("Invoice Unearned Account"),
|
verbose_name=_("Invoice Unearned Account"),
|
||||||
)
|
)
|
||||||
invoice_tax_payable_account = models.ForeignKey(
|
invoice_tax_payable_account = models.ForeignKey(
|
||||||
@ -3367,7 +3393,9 @@ class DealerSettings(models.Model):
|
|||||||
on_delete=models.SET_NULL,
|
on_delete=models.SET_NULL,
|
||||||
null=True,
|
null=True,
|
||||||
blank=True,
|
blank=True,
|
||||||
help_text=_("Tax Payable account to track tax liabilities when an invoice is created."),
|
help_text=_(
|
||||||
|
"Tax Payable account to track tax liabilities when an invoice is created."
|
||||||
|
),
|
||||||
verbose_name=_("Invoice Tax Payable Account"),
|
verbose_name=_("Invoice Tax Payable Account"),
|
||||||
)
|
)
|
||||||
invoice_vehicle_sale_account = models.ForeignKey(
|
invoice_vehicle_sale_account = models.ForeignKey(
|
||||||
@ -3376,7 +3404,9 @@ class DealerSettings(models.Model):
|
|||||||
on_delete=models.SET_NULL,
|
on_delete=models.SET_NULL,
|
||||||
null=True,
|
null=True,
|
||||||
blank=True,
|
blank=True,
|
||||||
help_text=_("Vehicle Sales account to track vehicle sales revenue when an invoice is created."),
|
help_text=_(
|
||||||
|
"Vehicle Sales account to track vehicle sales revenue when an invoice is created."
|
||||||
|
),
|
||||||
verbose_name=_("Invoice Vehicle Sale Account"),
|
verbose_name=_("Invoice Vehicle Sale Account"),
|
||||||
)
|
)
|
||||||
invoice_additional_services_account = models.ForeignKey(
|
invoice_additional_services_account = models.ForeignKey(
|
||||||
@ -3385,7 +3415,9 @@ class DealerSettings(models.Model):
|
|||||||
on_delete=models.SET_NULL,
|
on_delete=models.SET_NULL,
|
||||||
null=True,
|
null=True,
|
||||||
blank=True,
|
blank=True,
|
||||||
help_text=_("Additional Services account to track additional services revenue when an invoice is created."),
|
help_text=_(
|
||||||
|
"Additional Services account to track additional services revenue when an invoice is created."
|
||||||
|
),
|
||||||
verbose_name=_("Invoice Additional Services Account"),
|
verbose_name=_("Invoice Additional Services Account"),
|
||||||
)
|
)
|
||||||
invoice_cost_of_good_sold_account = models.ForeignKey(
|
invoice_cost_of_good_sold_account = models.ForeignKey(
|
||||||
@ -3394,7 +3426,9 @@ class DealerSettings(models.Model):
|
|||||||
on_delete=models.SET_NULL,
|
on_delete=models.SET_NULL,
|
||||||
null=True,
|
null=True,
|
||||||
blank=True,
|
blank=True,
|
||||||
help_text=_("Cost of Goods Sold account to track the cost of goods sold when an invoice is created."),
|
help_text=_(
|
||||||
|
"Cost of Goods Sold account to track the cost of goods sold when an invoice is created."
|
||||||
|
),
|
||||||
verbose_name=_("Invoice Cost of Goods Sold Account"),
|
verbose_name=_("Invoice Cost of Goods Sold Account"),
|
||||||
)
|
)
|
||||||
|
|
||||||
@ -3404,7 +3438,9 @@ class DealerSettings(models.Model):
|
|||||||
on_delete=models.SET_NULL,
|
on_delete=models.SET_NULL,
|
||||||
null=True,
|
null=True,
|
||||||
blank=True,
|
blank=True,
|
||||||
help_text=_("Inventory account to track the cost of goods sold when an invoice is created."),
|
help_text=_(
|
||||||
|
"Inventory account to track the cost of goods sold when an invoice is created."
|
||||||
|
),
|
||||||
verbose_name=_("Invoice Inventory Account"),
|
verbose_name=_("Invoice Inventory Account"),
|
||||||
)
|
)
|
||||||
|
|
||||||
@ -3423,7 +3459,9 @@ class DealerSettings(models.Model):
|
|||||||
on_delete=models.SET_NULL,
|
on_delete=models.SET_NULL,
|
||||||
null=True,
|
null=True,
|
||||||
blank=True,
|
blank=True,
|
||||||
help_text=_("Prepaid account to track prepaid expenses when a bill is created."),
|
help_text=_(
|
||||||
|
"Prepaid account to track prepaid expenses when a bill is created."
|
||||||
|
),
|
||||||
verbose_name=_("Bill Prepaid Account"),
|
verbose_name=_("Bill Prepaid Account"),
|
||||||
)
|
)
|
||||||
bill_unearned_account = models.ForeignKey(
|
bill_unearned_account = models.ForeignKey(
|
||||||
@ -3432,10 +3470,14 @@ class DealerSettings(models.Model):
|
|||||||
on_delete=models.SET_NULL,
|
on_delete=models.SET_NULL,
|
||||||
null=True,
|
null=True,
|
||||||
blank=True,
|
blank=True,
|
||||||
help_text=_("Unearned account to track unearned expenses when a bill is created."),
|
help_text=_(
|
||||||
|
"Unearned account to track unearned expenses when a bill is created."
|
||||||
|
),
|
||||||
verbose_name=_("Bill Unearned Account"),
|
verbose_name=_("Bill Unearned Account"),
|
||||||
)
|
)
|
||||||
additional_info = models.JSONField(default=dict, null=True, blank=True, help_text=_("Additional information"))
|
additional_info = models.JSONField(
|
||||||
|
default=dict, null=True, blank=True, help_text=_("Additional information")
|
||||||
|
)
|
||||||
|
|
||||||
def __str__(self):
|
def __str__(self):
|
||||||
return f"Settings for {self.dealer}"
|
return f"Settings for {self.dealer}"
|
||||||
@ -3790,7 +3832,10 @@ class Ticket(models.Model):
|
|||||||
]
|
]
|
||||||
|
|
||||||
dealer = models.ForeignKey(
|
dealer = models.ForeignKey(
|
||||||
Dealer, on_delete=models.CASCADE, related_name="tickets", verbose_name=_("Dealer")
|
Dealer,
|
||||||
|
on_delete=models.CASCADE,
|
||||||
|
related_name="tickets",
|
||||||
|
verbose_name=_("Dealer"),
|
||||||
)
|
)
|
||||||
subject = models.CharField(
|
subject = models.CharField(
|
||||||
max_length=200, verbose_name=_("Subject"), help_text=_("Short description")
|
max_length=200, verbose_name=_("Subject"), help_text=_("Short description")
|
||||||
@ -3882,7 +3927,6 @@ class CarImage(models.Model):
|
|||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
class UserRegistration(models.Model):
|
class UserRegistration(models.Model):
|
||||||
name = models.CharField(_("Name"), max_length=255)
|
name = models.CharField(_("Name"), max_length=255)
|
||||||
arabic_name = models.CharField(_("Arabic Name"), max_length=255)
|
arabic_name = models.CharField(_("Arabic Name"), max_length=255)
|
||||||
@ -3892,15 +3936,24 @@ class UserRegistration(models.Model):
|
|||||||
verbose_name=_("Phone Number"),
|
verbose_name=_("Phone Number"),
|
||||||
validators=[SaudiPhoneNumberValidator()],
|
validators=[SaudiPhoneNumberValidator()],
|
||||||
)
|
)
|
||||||
crn = models.CharField(_("Commercial Registration Number"), max_length=10, unique=True)
|
crn = models.CharField(
|
||||||
|
_("Commercial Registration Number"), max_length=10, unique=True
|
||||||
|
)
|
||||||
vrn = models.CharField(_("Vehicle Registration Number"), max_length=15, unique=True)
|
vrn = models.CharField(_("Vehicle Registration Number"), max_length=15, unique=True)
|
||||||
address = models.TextField(_("Address"))
|
address = models.TextField(_("Address"))
|
||||||
password = models.CharField(_("Password"), max_length=255,null=True,blank=True)
|
password = models.CharField(_("Password"), max_length=255, null=True, blank=True)
|
||||||
is_created = models.BooleanField(default=False)
|
is_created = models.BooleanField(default=False)
|
||||||
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)
|
||||||
|
|
||||||
REQUIRED_FIELDS = ["username", "arabic_name", "crn", "vrn", "address", "phone_number"]
|
REQUIRED_FIELDS = [
|
||||||
|
"username",
|
||||||
|
"arabic_name",
|
||||||
|
"crn",
|
||||||
|
"vrn",
|
||||||
|
"address",
|
||||||
|
"phone_number",
|
||||||
|
]
|
||||||
|
|
||||||
def __str__(self):
|
def __str__(self):
|
||||||
return self.email
|
return self.email
|
||||||
@ -3924,7 +3977,7 @@ class UserRegistration(models.Model):
|
|||||||
phone=self.phone_number,
|
phone=self.phone_number,
|
||||||
crn=self.crn,
|
crn=self.crn,
|
||||||
vrn=self.vrn,
|
vrn=self.vrn,
|
||||||
address=self.address
|
address=self.address,
|
||||||
)
|
)
|
||||||
|
|
||||||
if dealer:
|
if dealer:
|
||||||
|
|||||||
@ -1,82 +1,3 @@
|
|||||||
# import json
|
|
||||||
# from django.contrib.auth.models import AnonymousUser
|
|
||||||
# from django.contrib.auth import get_user_model
|
|
||||||
# from django.db import close_old_connections
|
|
||||||
# from urllib.parse import parse_qs
|
|
||||||
# from channels.db import database_sync_to_async
|
|
||||||
# from inventory.models import Notification
|
|
||||||
# import asyncio
|
|
||||||
|
|
||||||
# @database_sync_to_async
|
|
||||||
# def get_notifications(user, last_id):
|
|
||||||
# return Notification.objects.filter(
|
|
||||||
# user=user, id__gt=last_id, is_read=False
|
|
||||||
# ).order_by("created")
|
|
||||||
|
|
||||||
# class NotificationSSEApp:
|
|
||||||
# async def __call__(self, scope, receive, send):
|
|
||||||
# if scope["type"] != "http":
|
|
||||||
# return
|
|
||||||
|
|
||||||
# query_string = parse_qs(scope["query_string"].decode())
|
|
||||||
# last_id = int(query_string.get("last_id", [0])[0])
|
|
||||||
|
|
||||||
# # Get user from scope if using AuthMiddlewareStack
|
|
||||||
# user = scope.get("user", AnonymousUser())
|
|
||||||
# if not user.is_authenticated:
|
|
||||||
# await send({
|
|
||||||
# "type": "http.response.start",
|
|
||||||
# "status": 403,
|
|
||||||
# "headers": [(b"content-type", b"text/plain")],
|
|
||||||
# })
|
|
||||||
# await send({
|
|
||||||
# "type": "http.response.body",
|
|
||||||
# "body": b"Unauthorized",
|
|
||||||
# })
|
|
||||||
# return
|
|
||||||
|
|
||||||
# await send({
|
|
||||||
# "type": "http.response.start",
|
|
||||||
# "status": 200,
|
|
||||||
# "headers": [
|
|
||||||
# (b"content-type", b"text/event-stream"),
|
|
||||||
# (b"cache-control", b"no-cache"),
|
|
||||||
# (b"x-accel-buffering", b"no"),
|
|
||||||
# ]
|
|
||||||
# })
|
|
||||||
|
|
||||||
# try:
|
|
||||||
# while True:
|
|
||||||
# close_old_connections()
|
|
||||||
|
|
||||||
# notifications = await get_notifications(user, last_id)
|
|
||||||
# for notification in notifications:
|
|
||||||
# data = {
|
|
||||||
# "id": notification.id,
|
|
||||||
# "message": notification.message,
|
|
||||||
# "created": notification.created.isoformat(),
|
|
||||||
# "is_read": notification.is_read,
|
|
||||||
# }
|
|
||||||
|
|
||||||
# event_str = (
|
|
||||||
# f"id: {notification.id}\n"
|
|
||||||
# f"event: notification\n"
|
|
||||||
# f"data: {json.dumps(data)}\n\n"
|
|
||||||
# )
|
|
||||||
|
|
||||||
# await send({
|
|
||||||
# "type": "http.response.body",
|
|
||||||
# "body": event_str.encode("utf-8"),
|
|
||||||
# "more_body": True
|
|
||||||
# })
|
|
||||||
|
|
||||||
# last_id = notification.id
|
|
||||||
|
|
||||||
# await asyncio.sleep(2)
|
|
||||||
|
|
||||||
# except asyncio.CancelledError:
|
|
||||||
# pass
|
|
||||||
|
|
||||||
import json
|
import json
|
||||||
import time
|
import time
|
||||||
from django.contrib.auth.models import AnonymousUser
|
from django.contrib.auth.models import AnonymousUser
|
||||||
|
|||||||
@ -411,6 +411,22 @@ class BasePurchaseOrderActionActionView(
|
|||||||
f"while performing action '{self.action_name}' on Purchase Order ID: {po_model.pk}. "
|
f"while performing action '{self.action_name}' on Purchase Order ID: {po_model.pk}. "
|
||||||
f"Error: {e}"
|
f"Error: {e}"
|
||||||
)
|
)
|
||||||
|
except Exception as e:
|
||||||
|
print(
|
||||||
|
f"User {user_username} encountered an exception "
|
||||||
|
f"while performing action '{self.action_name}' on Purchase Order ID: {po_model.pk}. "
|
||||||
|
f"Error: {e}"
|
||||||
|
)
|
||||||
|
logger.warning(
|
||||||
|
f"User {user_username} encountered an exception "
|
||||||
|
f"while performing action '{self.action_name}' on Purchase Order ID: {po_model.pk}. "
|
||||||
|
f"Error: {e}"
|
||||||
|
)
|
||||||
|
messages.add_message(
|
||||||
|
request,
|
||||||
|
message=f"Failed to update PO {po_model.po_number}. {e}",
|
||||||
|
level=messages.ERROR,
|
||||||
|
)
|
||||||
return response
|
return response
|
||||||
|
|
||||||
|
|
||||||
@ -1129,6 +1145,7 @@ class ChartOfAccountModelCreateView(ChartOfAccountModelModelBaseViewMixIn, Creat
|
|||||||
},
|
},
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
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"
|
||||||
|
|||||||
@ -5,6 +5,7 @@ from django.urls import reverse
|
|||||||
from django.contrib.auth.models import Group
|
from django.contrib.auth.models import Group
|
||||||
from django.db.models.signals import post_save, post_delete
|
from django.db.models.signals import post_save, post_delete
|
||||||
from django.dispatch import receiver
|
from django.dispatch import receiver
|
||||||
|
|
||||||
# from appointment.models import Service
|
# from appointment.models import Service
|
||||||
from django.utils.translation import gettext_lazy as _
|
from django.utils.translation import gettext_lazy as _
|
||||||
from django.contrib.contenttypes.models import ContentType
|
from django.contrib.contenttypes.models import ContentType
|
||||||
@ -21,7 +22,7 @@ from django_ledger.models import (
|
|||||||
EstimateModel,
|
EstimateModel,
|
||||||
BillModel,
|
BillModel,
|
||||||
ChartOfAccountModel,
|
ChartOfAccountModel,
|
||||||
CustomerModel
|
CustomerModel,
|
||||||
)
|
)
|
||||||
from . import models
|
from . import models
|
||||||
from django.utils.timezone import now
|
from django.utils.timezone import now
|
||||||
@ -136,7 +137,6 @@ def create_car_location(sender, instance, created, **kwargs):
|
|||||||
print(f"Failed to create CarLocation for car {instance.vin}: {e}")
|
print(f"Failed to create CarLocation for car {instance.vin}: {e}")
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
@receiver(post_save, sender=models.Dealer)
|
@receiver(post_save, sender=models.Dealer)
|
||||||
def create_ledger_entity(sender, instance, created, **kwargs):
|
def create_ledger_entity(sender, instance, created, **kwargs):
|
||||||
if not created:
|
if not created:
|
||||||
@ -155,20 +155,22 @@ def create_ledger_entity(sender, instance, created, **kwargs):
|
|||||||
raise Exception("Entity creation failed")
|
raise Exception("Entity creation failed")
|
||||||
|
|
||||||
instance.entity = entity
|
instance.entity = entity
|
||||||
instance.save(update_fields=['entity'])
|
instance.save(update_fields=["entity"])
|
||||||
|
|
||||||
# Create default COA
|
# Create default COA
|
||||||
entity.create_chart_of_accounts(
|
entity.create_chart_of_accounts(
|
||||||
assign_as_default=True,
|
assign_as_default=True, commit=True, coa_name=f"{entity.name}-COA"
|
||||||
commit=True,
|
|
||||||
coa_name=f"{entity.name}-COA"
|
|
||||||
)
|
)
|
||||||
|
|
||||||
logger.info(f"✅ Setup complete for dealer {instance.id}: entity & COA ready.")
|
logger.info(
|
||||||
|
f"✅ Setup complete for dealer {instance.id}: entity & COA ready."
|
||||||
|
)
|
||||||
|
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
logger.error(f"💥 Failed setup for dealer {instance.id}: {e}")
|
logger.error(f"💥 Failed setup for dealer {instance.id}: {e}")
|
||||||
# Optional: schedule retry or alert
|
# Optional: schedule retry or alert
|
||||||
|
|
||||||
|
|
||||||
# Create Entity
|
# Create Entity
|
||||||
# @receiver(post_save, sender=models.Dealer)
|
# @receiver(post_save, sender=models.Dealer)
|
||||||
# def create_ledger_entity(sender, instance, created, **kwargs):
|
# def create_ledger_entity(sender, instance, created, **kwargs):
|
||||||
@ -218,10 +220,10 @@ def create_ledger_entity(sender, instance, created, **kwargs):
|
|||||||
# dealer=instance,
|
# dealer=instance,
|
||||||
# hook="inventory.hooks.check_create_coa_accounts",
|
# 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)
|
||||||
# create_accounts_for_make(instance.pk)
|
# create_accounts_for_make(instance.pk)
|
||||||
|
|
||||||
|
|
||||||
@receiver(post_save, sender=models.Dealer)
|
@receiver(post_save, sender=models.Dealer)
|
||||||
@ -998,16 +1000,28 @@ def save_po(sender, instance, created, **kwargs):
|
|||||||
instance.itemtransactionmodel_set.first().po_model.save()
|
instance.itemtransactionmodel_set.first().po_model.save()
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
pass
|
pass
|
||||||
|
|
||||||
|
|
||||||
@receiver(post_save, sender=PurchaseOrderModel)
|
@receiver(post_save, sender=PurchaseOrderModel)
|
||||||
def create_po_item_upload(sender, instance, created, **kwargs):
|
def create_po_item_upload(sender, instance, created, **kwargs):
|
||||||
if instance.po_status == "fulfilled" or instance.po_status == 'approved':
|
if instance.po_status == "fulfilled" or instance.po_status == "approved":
|
||||||
for item in instance.get_itemtxs_data()[0]:
|
for item in instance.get_itemtxs_data()[0]:
|
||||||
dealer = models.Dealer.objects.get(entity=instance.entity)
|
dealer = models.Dealer.objects.get(entity=instance.entity)
|
||||||
if item.bill_model.is_paid():
|
if item.bill_model and item.bill_model.is_paid():
|
||||||
models.PoItemsUploaded.objects.get_or_create(
|
models.PoItemsUploaded.objects.update_or_create(
|
||||||
dealer=dealer, po=instance, item=item, status=instance.po_status
|
dealer=dealer,
|
||||||
|
po=instance,
|
||||||
|
item=item,
|
||||||
|
defaults={"status": instance.po_status},
|
||||||
)
|
)
|
||||||
|
|
||||||
|
# po_item = models.PoItemsUploaded.objects.get_or_create(
|
||||||
|
# dealer=dealer, po=instance, item=item,
|
||||||
|
# defaults={
|
||||||
|
# "status":instance.po_status
|
||||||
|
# }
|
||||||
|
# )
|
||||||
|
|
||||||
|
|
||||||
# @receiver(post_save, sender=models.Staff)
|
# @receiver(post_save, sender=models.Staff)
|
||||||
# def add_service_to_staff(sender, instance, created, **kwargs):
|
# def add_service_to_staff(sender, instance, created, **kwargs):
|
||||||
@ -1354,7 +1368,9 @@ def handle_car_image(sender, instance, created, **kwargs):
|
|||||||
# )
|
# )
|
||||||
|
|
||||||
# Check for existing image with same hash
|
# Check for existing image with same hash
|
||||||
existing = os.path.exists(os.path.join(settings.MEDIA_ROOT, "car_images",car.get_hash + ".png"))
|
existing = os.path.exists(
|
||||||
|
os.path.join(settings.MEDIA_ROOT, "car_images", car.get_hash + ".png")
|
||||||
|
)
|
||||||
# existing = (
|
# existing = (
|
||||||
# models.CarImage.objects.filter(
|
# models.CarImage.objects.filter(
|
||||||
# image_hash=car.get_hash, image__isnull=False
|
# image_hash=car.get_hash, image__isnull=False
|
||||||
@ -1396,7 +1412,7 @@ def handle_user_registration(sender, instance, created, **kwargs):
|
|||||||
"""
|
"""
|
||||||
Thank you for registering with us. We will contact you shortly to complete your application.
|
Thank you for registering with us. We will contact you shortly to complete your application.
|
||||||
شكرا لمراسلتنا. سوف نتصل بك قريبا لاستكمال طلبك.
|
شكرا لمراسلتنا. سوف نتصل بك قريبا لاستكمال طلبك.
|
||||||
"""
|
""",
|
||||||
)
|
)
|
||||||
|
|
||||||
if instance.is_created:
|
if instance.is_created:
|
||||||
@ -1420,7 +1436,8 @@ def handle_user_registration(sender, instance, created, **kwargs):
|
|||||||
يرجى تسجيل الدخول إلى الموقع لاستكمال الملف الشخصي والبدء في استخدام خدماتنا.
|
يرجى تسجيل الدخول إلى الموقع لاستكمال الملف الشخصي والبدء في استخدام خدماتنا.
|
||||||
|
|
||||||
شكرا لاختيارك لنا.
|
شكرا لاختيارك لنا.
|
||||||
""")
|
""",
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
@receiver(post_save, sender=ChartOfAccountModel)
|
@receiver(post_save, sender=ChartOfAccountModel)
|
||||||
|
|||||||
@ -12,12 +12,14 @@ from django.db import transaction
|
|||||||
from django_ledger.io import roles
|
from django_ledger.io import roles
|
||||||
from django_q.tasks import async_task
|
from django_q.tasks import async_task
|
||||||
from django.core.mail import send_mail
|
from django.core.mail import send_mail
|
||||||
|
|
||||||
# from appointment.models import StaffMember
|
# from appointment.models import StaffMember
|
||||||
from django.utils.translation import activate
|
from django.utils.translation import activate
|
||||||
from django.core.files.base import ContentFile
|
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 _
|
||||||
@ -30,7 +32,7 @@ from inventory.models import (
|
|||||||
CarReservation,
|
CarReservation,
|
||||||
CarStatusChoices,
|
CarStatusChoices,
|
||||||
CarImage,
|
CarImage,
|
||||||
Car
|
Car,
|
||||||
)
|
)
|
||||||
|
|
||||||
logger = logging.getLogger(__name__)
|
logger = logging.getLogger(__name__)
|
||||||
@ -63,17 +65,18 @@ def create_settings(pk):
|
|||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
def create_coa_accounts(dealer_id,**kwargs):
|
def create_coa_accounts(dealer_id, **kwargs):
|
||||||
"""
|
"""
|
||||||
Idempotent: Creates only missing default accounts.
|
Idempotent: Creates only missing default accounts.
|
||||||
Safe to retry. Returns True if all done.
|
Safe to retry. Returns True if all done.
|
||||||
"""
|
"""
|
||||||
from .models import Dealer
|
from .models import Dealer
|
||||||
from .utils import get_accounts_data, create_account
|
from .utils import get_accounts_data, create_account
|
||||||
|
|
||||||
try:
|
try:
|
||||||
dealer = Dealer.objects.get(pk=dealer_id)
|
dealer = Dealer.objects.get(pk=dealer_id)
|
||||||
entity = dealer.entity
|
entity = dealer.entity
|
||||||
coa_slug = kwargs.get('coa_slug', None)
|
coa_slug = kwargs.get("coa_slug", None)
|
||||||
if not entity:
|
if not entity:
|
||||||
logger.error(f"❌ No entity for dealer {dealer_id}")
|
logger.error(f"❌ No entity for dealer {dealer_id}")
|
||||||
return False
|
return False
|
||||||
@ -82,7 +85,9 @@ def create_coa_accounts(dealer_id,**kwargs):
|
|||||||
try:
|
try:
|
||||||
coa = entity.get_coa_model_qs().get(slug=coa_slug)
|
coa = entity.get_coa_model_qs().get(slug=coa_slug)
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
logger.error(f"COA with slug {coa_slug} not found for entity {entity.pk}: {e}")
|
logger.error(
|
||||||
|
f"COA with slug {coa_slug} not found for entity {entity.pk}: {e}"
|
||||||
|
)
|
||||||
return False
|
return False
|
||||||
else:
|
else:
|
||||||
coa = entity.get_default_coa()
|
coa = entity.get_default_coa()
|
||||||
@ -92,10 +97,13 @@ def create_coa_accounts(dealer_id,**kwargs):
|
|||||||
return False
|
return False
|
||||||
|
|
||||||
# Get missing accounts
|
# Get missing accounts
|
||||||
existing_codes = set(entity.get_all_accounts().filter(coa_model=coa).values_list('code', flat=True))
|
existing_codes = set(
|
||||||
|
entity.get_all_accounts()
|
||||||
|
.filter(coa_model=coa)
|
||||||
|
.values_list("code", flat=True)
|
||||||
|
)
|
||||||
accounts_to_create = [
|
accounts_to_create = [
|
||||||
acc for acc in get_accounts_data()
|
acc for acc in get_accounts_data() if acc["code"] not in existing_codes
|
||||||
if acc["code"] not in existing_codes
|
|
||||||
]
|
]
|
||||||
|
|
||||||
if not accounts_to_create:
|
if not accounts_to_create:
|
||||||
@ -122,6 +130,7 @@ def create_coa_accounts(dealer_id,**kwargs):
|
|||||||
logger.error(f"💥 Task failed for dealer {dealer_id}: {e}")
|
logger.error(f"💥 Task failed for dealer {dealer_id}: {e}")
|
||||||
raise # Let Django-Q handle retry if configured
|
raise # Let Django-Q handle retry if configured
|
||||||
|
|
||||||
|
|
||||||
def retry_entity_creation(dealer_id, retry_count=0):
|
def retry_entity_creation(dealer_id, retry_count=0):
|
||||||
"""
|
"""
|
||||||
Retry entity creation if initial attempt failed
|
Retry entity creation if initial attempt failed
|
||||||
@ -164,8 +173,10 @@ def retry_entity_creation(dealer_id, retry_count=0):
|
|||||||
async_task(
|
async_task(
|
||||||
"inventory.tasks.retry_entity_creation",
|
"inventory.tasks.retry_entity_creation",
|
||||||
dealer_id=dealer_id,
|
dealer_id=dealer_id,
|
||||||
retry_count=retry_count + 1
|
retry_count=retry_count + 1,
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
# 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")
|
||||||
|
|||||||
@ -13,6 +13,7 @@ from django.db.models import Case, Value, When, IntegerField
|
|||||||
|
|
||||||
register = template.Library()
|
register = template.Library()
|
||||||
|
|
||||||
|
|
||||||
@register.filter
|
@register.filter
|
||||||
def is_negative(value):
|
def is_negative(value):
|
||||||
"""
|
"""
|
||||||
@ -23,6 +24,7 @@ def is_negative(value):
|
|||||||
except (ValueError, TypeError):
|
except (ValueError, TypeError):
|
||||||
return False
|
return False
|
||||||
|
|
||||||
|
|
||||||
@register.filter
|
@register.filter
|
||||||
def get_percentage(value, total):
|
def get_percentage(value, total):
|
||||||
try:
|
try:
|
||||||
@ -501,8 +503,16 @@ def bill_item_formset_table(context, item_formset):
|
|||||||
for item in item_formset:
|
for item in item_formset:
|
||||||
if item:
|
if item:
|
||||||
print(item.fields["item_model"])
|
print(item.fields["item_model"])
|
||||||
item.initial["quantity"] = item.instance.po_quantity if item.instance.po_quantity else item.instance.quantity
|
item.initial["quantity"] = (
|
||||||
item.initial["unit_cost"] = item.instance.po_unit_cost if item.instance.po_unit_cost else item.instance.unit_cost
|
item.instance.po_quantity
|
||||||
|
if item.instance.po_quantity
|
||||||
|
else item.instance.quantity
|
||||||
|
)
|
||||||
|
item.initial["unit_cost"] = (
|
||||||
|
item.instance.po_unit_cost
|
||||||
|
if item.instance.po_unit_cost
|
||||||
|
else item.instance.unit_cost
|
||||||
|
)
|
||||||
# print(item.instance.po_quantity)
|
# print(item.instance.po_quantity)
|
||||||
# print(item.instance.po_unit_cost)
|
# print(item.instance.po_unit_cost)
|
||||||
# print(item.instance.po_total_amount)
|
# print(item.instance.po_total_amount)
|
||||||
|
|||||||
@ -2,7 +2,7 @@ from inventory.utils import get_user_type
|
|||||||
from . import views
|
from . import views
|
||||||
from django.urls import path
|
from django.urls import path
|
||||||
from django.urls import reverse_lazy
|
from django.urls import reverse_lazy
|
||||||
from django.views.generic import RedirectView,TemplateView
|
from django.views.generic import RedirectView, TemplateView
|
||||||
from django_tables2.export.export import TableExport
|
from django_tables2.export.export import TableExport
|
||||||
from django.conf.urls import handler403, handler400, handler404, handler500
|
from django.conf.urls import handler403, handler400, handler404, handler500
|
||||||
|
|
||||||
@ -10,14 +10,17 @@ urlpatterns = [
|
|||||||
# main URLs
|
# main URLs
|
||||||
path("", views.WelcomeView, name="welcome"),
|
path("", views.WelcomeView, name="welcome"),
|
||||||
# path("signup/", views.dealer_signup, name="account_signup"),
|
# path("signup/", views.dealer_signup, name="account_signup"),
|
||||||
path('signup/', views.CarDealershipSignUpView.as_view(), name='account_signup'),
|
path("signup/", views.CarDealershipSignUpView.as_view(), name="account_signup"),
|
||||||
path('success/', TemplateView.as_view(template_name='account/success.html'), name='registration_success'),
|
path(
|
||||||
|
"success/",
|
||||||
|
TemplateView.as_view(template_name="account/success.html"),
|
||||||
|
name="registration_success",
|
||||||
|
),
|
||||||
path("", views.HomeView, name="home"),
|
path("", views.HomeView, name="home"),
|
||||||
# path('refund-policy/',views.refund_policy,name='refund_policy'),
|
# path('refund-policy/',views.refund_policy,name='refund_policy'),
|
||||||
path("<slug:dealer_slug>/", views.HomeView, name="home"),
|
path("<slug:dealer_slug>/", views.HomeView, name="home"),
|
||||||
# Tasks
|
# Tasks
|
||||||
path("legal/", views.terms_and_privacy, name="terms_and_privacy"),
|
path("legal/", views.terms_and_privacy, name="terms_and_privacy"),
|
||||||
|
|
||||||
# path('tasks/<int:task_id>/detail/', views.task_detail, name='task_detail'),
|
# path('tasks/<int:task_id>/detail/', views.task_detail, name='task_detail'),
|
||||||
# Dashboards
|
# Dashboards
|
||||||
# path("user/<int:pk>/settings/", views.UserSettingsView.as_view(), name="user_settings"),
|
# path("user/<int:pk>/settings/", views.UserSettingsView.as_view(), name="user_settings"),
|
||||||
@ -44,13 +47,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,
|
||||||
@ -786,7 +794,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,
|
||||||
@ -794,7 +806,7 @@ urlpatterns = [
|
|||||||
),
|
),
|
||||||
path(
|
path(
|
||||||
"<slug:dealer_slug>/sales/estimates/create/<slug:slug>/",
|
"<slug:dealer_slug>/sales/estimates/create/<slug:slug>/",
|
||||||
views.create_estimate,
|
views.estimate_create_from_opportunity,
|
||||||
name="estimate_create_from_opportunity",
|
name="estimate_create_from_opportunity",
|
||||||
),
|
),
|
||||||
path(
|
path(
|
||||||
@ -943,7 +955,6 @@ urlpatterns = [
|
|||||||
views.ItemServiceUpdateView.as_view(),
|
views.ItemServiceUpdateView.as_view(),
|
||||||
name="item_service_update",
|
name="item_service_update",
|
||||||
),
|
),
|
||||||
|
|
||||||
path(
|
path(
|
||||||
"<slug:dealer_slug>/items/services/<int:pk>/detail/",
|
"<slug:dealer_slug>/items/services/<int:pk>/detail/",
|
||||||
views.ItemServiceDetailView.as_view(),
|
views.ItemServiceDetailView.as_view(),
|
||||||
@ -1112,32 +1123,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(
|
||||||
@ -1313,42 +1339,80 @@ 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('<slug:dealer_slug>/car-sale-report/get_filtered_choices/',views.get_filtered_choices,name='get_filtered_choices'),
|
path(
|
||||||
path('car-sale-report/<slug:dealer_slug>/csv/', views.car_sale_report_csv_export, name='car-sale-report-csv-export'),
|
"<slug:dealer_slug>/car-sale-report/get_filtered_choices/",
|
||||||
|
views.get_filtered_choices,
|
||||||
path('feature/recall/', views.RecallListView.as_view(), name='recall_list'),
|
name="get_filtered_choices",
|
||||||
path('feature/recall/filter/', views.RecallFilterView, name='recall_filter'),
|
),
|
||||||
path('feature/recall/<int:pk>/view/', views.RecallDetailView.as_view(), name='recall_detail'),
|
path(
|
||||||
path('feature/recall/create/', views.RecallCreateView.as_view(), name='recall_create'),
|
"car-sale-report/<slug:dealer_slug>/csv/",
|
||||||
path('feature/recall/success/', views.RecallSuccessView.as_view(), name='recall_success'),
|
views.car_sale_report_csv_export,
|
||||||
|
name="car-sale-report-csv-export",
|
||||||
path('<slug:dealer_slug>/schedules/calendar/', views.schedule_calendar, name='schedule_calendar'),
|
),
|
||||||
|
path("feature/recall/", views.RecallListView.as_view(), name="recall_list"),
|
||||||
|
path("feature/recall/filter/", views.RecallFilterView, name="recall_filter"),
|
||||||
|
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/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'),
|
||||||
path('payment_results/', views.payment_result, name='payment_result'),
|
path("payment_results/", views.payment_result, name="payment_result"),
|
||||||
|
|
||||||
]
|
]
|
||||||
|
|
||||||
handler404 = "inventory.views.custom_page_not_found_view"
|
handler404 = "inventory.views.custom_page_not_found_view"
|
||||||
|
|||||||
@ -27,7 +27,7 @@ from django_ledger.models import (
|
|||||||
VendorModel,
|
VendorModel,
|
||||||
AccountModel,
|
AccountModel,
|
||||||
EntityModel,
|
EntityModel,
|
||||||
ChartOfAccountModel
|
ChartOfAccountModel,
|
||||||
)
|
)
|
||||||
from django.core.files.base import ContentFile
|
from django.core.files.base import ContentFile
|
||||||
from django_ledger.models.items import ItemModel
|
from django_ledger.models.items import ItemModel
|
||||||
@ -1325,11 +1325,13 @@ def get_finance_data(estimate, dealer):
|
|||||||
)
|
)
|
||||||
discount = extra_info.data.get("discount", 0)
|
discount = extra_info.data.get("discount", 0)
|
||||||
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_amount = additional_services.get("total")
|
||||||
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_services_amount_ = additional_services.get("total_")
|
||||||
total_vat = vat_amount + total_services_vat
|
total_vat = vat_amount + total_services_vat
|
||||||
return {
|
return {
|
||||||
"car": car,
|
"car": car,
|
||||||
@ -1341,6 +1343,8 @@ def get_finance_data(estimate, dealer):
|
|||||||
"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_services_amount": total_services_amount,
|
||||||
|
"total_services_amount_": total_services_amount_,
|
||||||
"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"),
|
||||||
}
|
}
|
||||||
@ -1596,24 +1600,38 @@ def _post_sale_and_cogs(invoice, dealer):
|
|||||||
1) Cash / A-R / VAT / Revenue journal
|
1) Cash / A-R / VAT / Revenue journal
|
||||||
2) COGS / Inventory journal
|
2) COGS / Inventory journal
|
||||||
"""
|
"""
|
||||||
entity:EntityModel = invoice.ledger.entity
|
entity: EntityModel = 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")
|
||||||
|
|
||||||
coa:ChartOfAccountModel = entity.get_default_coa()
|
coa: ChartOfAccountModel = entity.get_default_coa()
|
||||||
cash_acc = invoice.cash_account or dealer.settings.invoice_cash_account
|
cash_acc = invoice.cash_account or dealer.settings.invoice_cash_account
|
||||||
|
|
||||||
vat_acc = dealer.settings.invoice_tax_payable_account or entity.get_default_coa_accounts().filter(role_default=True, role=roles.LIABILITY_CL_TAXES_PAYABLE).first()
|
vat_acc = (
|
||||||
|
dealer.settings.invoice_tax_payable_account
|
||||||
|
or entity.get_default_coa_accounts()
|
||||||
|
.filter(role_default=True, role=roles.LIABILITY_CL_TAXES_PAYABLE)
|
||||||
|
.first()
|
||||||
|
)
|
||||||
|
|
||||||
car_rev = dealer.settings.invoice_vehicle_sale_account or entity.get_default_coa_accounts().filter(role_default=True, role=roles.INCOME_OPERATIONAL).first()
|
car_rev = (
|
||||||
|
dealer.settings.invoice_vehicle_sale_account
|
||||||
|
or entity.get_default_coa_accounts()
|
||||||
|
.filter(role_default=True, role=roles.INCOME_OPERATIONAL)
|
||||||
|
.first()
|
||||||
|
)
|
||||||
|
|
||||||
add_rev = dealer.settings.invoice_additional_services_account
|
add_rev = dealer.settings.invoice_additional_services_account
|
||||||
|
|
||||||
if not add_rev:
|
if not add_rev:
|
||||||
try:
|
try:
|
||||||
add_rev = entity.get_default_coa_accounts().filter(name="After-Sales Services", active=True).first()
|
add_rev = (
|
||||||
|
entity.get_default_coa_accounts()
|
||||||
|
.filter(name="After-Sales Services", active=True)
|
||||||
|
.first()
|
||||||
|
)
|
||||||
if not add_rev:
|
if not add_rev:
|
||||||
add_rev = coa.create_account(
|
add_rev = coa.create_account(
|
||||||
code="4020",
|
code="4020",
|
||||||
@ -1623,16 +1641,28 @@ def _post_sale_and_cogs(invoice, dealer):
|
|||||||
active=True,
|
active=True,
|
||||||
)
|
)
|
||||||
add_rev.role_default = False
|
add_rev.role_default = False
|
||||||
add_rev.save(update_fields=['role_default'])
|
add_rev.save(update_fields=["role_default"])
|
||||||
dealer.settings.invoice_additional_services_account = add_rev
|
dealer.settings.invoice_additional_services_account = add_rev
|
||||||
dealer.settings.save()
|
dealer.settings.save()
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
logger.error(f"error find or create additional services account {e}")
|
logger.error(f"error find or create additional services account {e}")
|
||||||
if car.get_additional_services_amount > 0 and not add_rev:
|
if car.get_additional_services_amount > 0 and not add_rev:
|
||||||
raise Exception("additional services exist but not account found,please create account for the additional services and set as default in the settings")
|
raise Exception(
|
||||||
cogs_acc = dealer.settings.invoice_cost_of_good_sold_account or entity.get_default_coa_accounts().filter(role_default=True, role=roles.COGS).first()
|
"additional services exist but not account found,please create account for the additional services and set as default in the settings"
|
||||||
|
)
|
||||||
|
cogs_acc = (
|
||||||
|
dealer.settings.invoice_cost_of_good_sold_account
|
||||||
|
or entity.get_default_coa_accounts()
|
||||||
|
.filter(role_default=True, role=roles.COGS)
|
||||||
|
.first()
|
||||||
|
)
|
||||||
|
|
||||||
inv_acc = dealer.settings.invoice_inventory_account or entity.get_default_coa_accounts().filter(role_default=True, role=roles.ASSET_CA_INVENTORY).first()
|
inv_acc = (
|
||||||
|
dealer.settings.invoice_inventory_account
|
||||||
|
or entity.get_default_coa_accounts()
|
||||||
|
.filter(role_default=True, role=roles.ASSET_CA_INVENTORY)
|
||||||
|
.first()
|
||||||
|
)
|
||||||
|
|
||||||
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"])
|
||||||
@ -1687,11 +1717,12 @@ def _post_sale_and_cogs(invoice, dealer):
|
|||||||
# tx_type='credit'
|
# tx_type='credit'
|
||||||
# )
|
# )
|
||||||
|
|
||||||
|
|
||||||
if car.get_additional_services_amount > 0:
|
if car.get_additional_services_amount > 0:
|
||||||
# Cr Sales – Additional Services
|
# Cr Sales – Additional Services
|
||||||
if not add_rev:
|
if not add_rev:
|
||||||
logger.warning(f"Additional Services account not set for dealer {dealer}. Skipping additional services revenue entry.")
|
logger.warning(
|
||||||
|
f"Additional Services account not set for dealer {dealer}. Skipping additional services revenue entry."
|
||||||
|
)
|
||||||
else:
|
else:
|
||||||
TransactionModel.objects.create(
|
TransactionModel.objects.create(
|
||||||
journal_entry=je_sale,
|
journal_entry=je_sale,
|
||||||
@ -1929,7 +1960,7 @@ def handle_payment(request, dealer):
|
|||||||
}
|
}
|
||||||
|
|
||||||
# Get selected plan from session
|
# Get selected plan from session
|
||||||
selected_plan_id = request.session.get('pending_plan_id')
|
selected_plan_id = request.session.get("pending_plan_id")
|
||||||
if not selected_plan_id:
|
if not selected_plan_id:
|
||||||
raise ValueError("No pending plan found in session")
|
raise ValueError("No pending plan found in session")
|
||||||
from plans.models import PlanPricing
|
from plans.models import PlanPricing
|
||||||
@ -1947,7 +1978,8 @@ def handle_payment(request, dealer):
|
|||||||
"dealer_slug": dealer.slug,
|
"dealer_slug": dealer.slug,
|
||||||
}
|
}
|
||||||
|
|
||||||
payload = json.dumps({
|
payload = json.dumps(
|
||||||
|
{
|
||||||
"amount": total,
|
"amount": total,
|
||||||
"currency": "SAR",
|
"currency": "SAR",
|
||||||
"description": f"Payment for plan {pp.plan.name}",
|
"description": f"Payment for plan {pp.plan.name}",
|
||||||
@ -1965,7 +1997,8 @@ def handle_payment(request, dealer):
|
|||||||
"save_card": False,
|
"save_card": False,
|
||||||
},
|
},
|
||||||
"metadata": metadata,
|
"metadata": metadata,
|
||||||
})
|
}
|
||||||
|
)
|
||||||
|
|
||||||
headers = {"Content-Type": "application/json", "Accept": "application/json"}
|
headers = {"Content-Type": "application/json", "Accept": "application/json"}
|
||||||
auth = (settings.MOYASAR_SECRET_KEY, "")
|
auth = (settings.MOYASAR_SECRET_KEY, "")
|
||||||
@ -1989,7 +2022,9 @@ def handle_payment(request, dealer):
|
|||||||
gateway_response=data,
|
gateway_response=data,
|
||||||
)
|
)
|
||||||
logger.info(f"Payment initiated: {data}")
|
logger.info(f"Payment initiated: {data}")
|
||||||
return data["source"]["transaction_url"],None
|
return data["source"]["transaction_url"], None
|
||||||
|
|
||||||
|
|
||||||
# def handle_payment(request, order):
|
# def handle_payment(request, order):
|
||||||
# logger.info(f"Handling payment for order {order}")
|
# logger.info(f"Handling payment for order {order}")
|
||||||
# url = "https://api.moyasar.com/v1/payments"
|
# url = "https://api.moyasar.com/v1/payments"
|
||||||
@ -2509,10 +2544,9 @@ def create_account(entity, coa, account_data):
|
|||||||
logger.info(f"Created account: {account}")
|
logger.info(f"Created account: {account}")
|
||||||
if account:
|
if account:
|
||||||
account.role_default = account_data.get("default", False)
|
account.role_default = account_data.get("default", False)
|
||||||
account.save(update_fields=['role_default'])
|
account.save(update_fields=["role_default"])
|
||||||
return True
|
return True
|
||||||
|
|
||||||
|
|
||||||
except IntegrityError:
|
except IntegrityError:
|
||||||
return True # Already created by race condition
|
return True # Already created by race condition
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
@ -2520,6 +2554,7 @@ def create_account(entity, coa, account_data):
|
|||||||
|
|
||||||
return False
|
return False
|
||||||
|
|
||||||
|
|
||||||
# def create_account(entity, coa, account_data):
|
# def create_account(entity, coa, account_data):
|
||||||
# try:
|
# try:
|
||||||
# account = entity.create_account(
|
# account = entity.create_account(
|
||||||
@ -2801,7 +2836,9 @@ def generate_car_image_simple(car):
|
|||||||
# Save the resized image
|
# Save the resized image
|
||||||
logger.info(f" {car.vin}")
|
logger.info(f" {car.vin}")
|
||||||
with open(
|
with open(
|
||||||
os.path.join(settings.MEDIA_ROOT, f"car_images/{car.get_hash}.{file_extension}"),
|
os.path.join(
|
||||||
|
settings.MEDIA_ROOT, f"car_images/{car.get_hash}.{file_extension}"
|
||||||
|
),
|
||||||
"wb",
|
"wb",
|
||||||
) as f:
|
) as f:
|
||||||
f.write(resized_data)
|
f.write(resized_data)
|
||||||
@ -2817,9 +2854,7 @@ def generate_car_image_simple(car):
|
|||||||
return {"success": False, "error": error_msg}
|
return {"success": False, "error": error_msg}
|
||||||
|
|
||||||
|
|
||||||
|
def create_estimate_(dealer, car, customer):
|
||||||
|
|
||||||
def create_estimate_(dealer,car,customer):
|
|
||||||
entity = dealer.entity
|
entity = dealer.entity
|
||||||
title = f"Estimate for {car.vin}-{car.id_car_make.name}-{car.id_car_model.name}-{car.year} for customer {customer.first_name} {customer.last_name}"
|
title = f"Estimate for {car.vin}-{car.id_car_make.name}-{car.id_car_model.name}-{car.year} for customer {customer.first_name} {customer.last_name}"
|
||||||
estimate = entity.create_estimate(
|
estimate = entity.create_estimate(
|
||||||
|
|||||||
@ -16,9 +16,10 @@ class SaudiPhoneNumberValidator(RegexValidator):
|
|||||||
cleaned_value = re.sub(r"[\s\-\(\)\.]", "", str(value))
|
cleaned_value = re.sub(r"[\s\-\(\)\.]", "", str(value))
|
||||||
super().__call__(cleaned_value)
|
super().__call__(cleaned_value)
|
||||||
|
|
||||||
|
|
||||||
def vat_rate_validator(value):
|
def vat_rate_validator(value):
|
||||||
if value < 0 or value > 1:
|
if value < 0 or value > 1:
|
||||||
raise ValidationError(
|
raise ValidationError(
|
||||||
_('%(value)s is not a valid VAT rate. It must be between 0 and 1.'),
|
_("%(value)s is not a valid VAT rate. It must be between 0 and 1."),
|
||||||
params={'value': value},
|
params={"value": value},
|
||||||
)
|
)
|
||||||
2084
inventory/views.py
2084
inventory/views.py
File diff suppressed because it is too large
Load Diff
@ -7,7 +7,9 @@ autobahn==24.4.2
|
|||||||
Automat==25.4.16
|
Automat==25.4.16
|
||||||
Babel==2.15.0
|
Babel==2.15.0
|
||||||
beautifulsoup4==4.13.4
|
beautifulsoup4==4.13.4
|
||||||
|
blacknoise==1.2.0
|
||||||
blessed==1.21.0
|
blessed==1.21.0
|
||||||
|
Brotli==1.1.0
|
||||||
cattrs==25.1.1
|
cattrs==25.1.1
|
||||||
certifi==2025.7.9
|
certifi==2025.7.9
|
||||||
cffi==1.17.1
|
cffi==1.17.1
|
||||||
@ -19,15 +21,13 @@ constantly==23.10.4
|
|||||||
crispy-bootstrap5==2025.6
|
crispy-bootstrap5==2025.6
|
||||||
cryptography==45.0.5
|
cryptography==45.0.5
|
||||||
cssbeautifier==1.15.4
|
cssbeautifier==1.15.4
|
||||||
daphne==4.2.1
|
cssselect2==0.8.0
|
||||||
defusedxml==0.7.1
|
defusedxml==0.7.1
|
||||||
diff-match-patch==20241021
|
diff-match-patch==20241021
|
||||||
distro==1.9.0
|
distro==1.9.0
|
||||||
Django==5.2.4
|
Django==5.2.4
|
||||||
django-allauth==65.10.0
|
django-allauth==65.10.0
|
||||||
django-appconf==1.1.0
|
django-appconf==1.1.0
|
||||||
django-appointment==3.8.0
|
|
||||||
django-background-tasks==1.2.8
|
|
||||||
django-bootstrap5==25.1
|
django-bootstrap5==25.1
|
||||||
django-ckeditor==6.7.3
|
django-ckeditor==6.7.3
|
||||||
django-cors-headers==4.7.0
|
django-cors-headers==4.7.0
|
||||||
@ -35,6 +35,7 @@ django-countries==7.6.1
|
|||||||
django-crispy-forms==2.4
|
django-crispy-forms==2.4
|
||||||
django-debug-toolbar==5.2.0
|
django-debug-toolbar==5.2.0
|
||||||
django-easy-audit==1.3.7
|
django-easy-audit==1.3.7
|
||||||
|
django-encrypted-model-fields==0.6.5
|
||||||
django-extensions==4.1
|
django-extensions==4.1
|
||||||
django-filter==25.1
|
django-filter==25.1
|
||||||
django-imagekit==5.0.0
|
django-imagekit==5.0.0
|
||||||
@ -47,7 +48,6 @@ django-ordered-model==3.7.4
|
|||||||
django-phonenumber-field==8.0.0
|
django-phonenumber-field==8.0.0
|
||||||
django-picklefield==3.3
|
django-picklefield==3.3
|
||||||
django-plans==2.0.0
|
django-plans==2.0.0
|
||||||
django-prometheus==2.4.1
|
|
||||||
django-q2==1.8.0
|
django-q2==1.8.0
|
||||||
django-query-builder==3.2.0
|
django-query-builder==3.2.0
|
||||||
django-schema-graph==3.1.0
|
django-schema-graph==3.1.0
|
||||||
@ -56,8 +56,6 @@ django-tables2==2.7.5
|
|||||||
django-treebeard==4.7.1
|
django-treebeard==4.7.1
|
||||||
django-widget-tweaks==1.5.0
|
django-widget-tweaks==1.5.0
|
||||||
djangorestframework==3.16.0
|
djangorestframework==3.16.0
|
||||||
djhtml==3.0.8
|
|
||||||
djlint==1.36.4
|
|
||||||
dnspython==2.7.0
|
dnspython==2.7.0
|
||||||
docopt==0.6.2
|
docopt==0.6.2
|
||||||
EditorConfig==0.17.1
|
EditorConfig==0.17.1
|
||||||
@ -78,8 +76,6 @@ hyperlink==21.0.0
|
|||||||
icalendar==6.3.1
|
icalendar==6.3.1
|
||||||
idna==3.10
|
idna==3.10
|
||||||
incremental==24.7.2
|
incremental==24.7.2
|
||||||
iron-core==1.2.1
|
|
||||||
iron-mq==0.9
|
|
||||||
jiter==0.10.0
|
jiter==0.10.0
|
||||||
jsbeautifier==1.15.4
|
jsbeautifier==1.15.4
|
||||||
json5==0.12.0
|
json5==0.12.0
|
||||||
@ -109,16 +105,17 @@ phonenumbers==8.13.42
|
|||||||
pilkit==3.0
|
pilkit==3.0
|
||||||
pillow==10.4.0
|
pillow==10.4.0
|
||||||
priority==1.3.0
|
priority==1.3.0
|
||||||
prometheus_client==0.22.1
|
|
||||||
psycopg2-binary==2.9.10
|
psycopg2-binary==2.9.10
|
||||||
pyasn1==0.6.1
|
pyasn1==0.6.1
|
||||||
pyasn1_modules==0.4.2
|
pyasn1_modules==0.4.2
|
||||||
pycparser==2.22
|
pycparser==2.22
|
||||||
pydantic==2.11.7
|
pydantic==2.11.7
|
||||||
pydantic_core==2.33.2
|
pydantic_core==2.33.2
|
||||||
|
pydyf==0.11.0
|
||||||
Pygments==2.19.2
|
Pygments==2.19.2
|
||||||
pymongo==4.14.1
|
pymongo==4.14.1
|
||||||
pyOpenSSL==25.1.0
|
pyOpenSSL==25.1.0
|
||||||
|
pyphen==0.17.2
|
||||||
python-dateutil==2.9.0.post0
|
python-dateutil==2.9.0.post0
|
||||||
python-dotenv==1.1.1
|
python-dotenv==1.1.1
|
||||||
python-slugify==8.0.4
|
python-slugify==8.0.4
|
||||||
@ -131,8 +128,6 @@ redis==6.2.0
|
|||||||
regex==2024.11.6
|
regex==2024.11.6
|
||||||
requests==2.32.4
|
requests==2.32.4
|
||||||
requests-toolbelt==1.0.0
|
requests-toolbelt==1.0.0
|
||||||
rich==14.0.0
|
|
||||||
ruff==0.12.2
|
|
||||||
service-identity==24.2.0
|
service-identity==24.2.0
|
||||||
setuptools==80.9.0
|
setuptools==80.9.0
|
||||||
six==1.17.0
|
six==1.17.0
|
||||||
@ -140,11 +135,15 @@ sniffio==1.3.1
|
|||||||
soupsieve==2.7
|
soupsieve==2.7
|
||||||
SQLAlchemy==2.0.41
|
SQLAlchemy==2.0.41
|
||||||
sqlparse==0.5.3
|
sqlparse==0.5.3
|
||||||
|
starlette==0.47.3
|
||||||
|
static3==0.7.0
|
||||||
suds==1.2.0
|
suds==1.2.0
|
||||||
swapper==1.3.0
|
swapper==1.3.0
|
||||||
tablib==3.8.0
|
tablib==3.8.0
|
||||||
tenacity==9.1.2
|
tenacity==9.1.2
|
||||||
text-unidecode==1.3
|
text-unidecode==1.3
|
||||||
|
tinycss2==1.4.0
|
||||||
|
tinyhtml5==2.0.0
|
||||||
tqdm==4.67.1
|
tqdm==4.67.1
|
||||||
Twisted==25.5.0
|
Twisted==25.5.0
|
||||||
txaio==25.6.1
|
txaio==25.6.1
|
||||||
@ -156,6 +155,8 @@ urllib3==2.5.0
|
|||||||
uvicorn==0.35.0
|
uvicorn==0.35.0
|
||||||
uvicorn-worker==0.3.0
|
uvicorn-worker==0.3.0
|
||||||
wcwidth==0.2.13
|
wcwidth==0.2.13
|
||||||
whitenoise==6.9.0
|
weasyprint==66.0
|
||||||
|
webencodings==0.5.1
|
||||||
zope.interface==7.2
|
zope.interface==7.2
|
||||||
|
zopfli==0.2.3.post1
|
||||||
zstandard==0.23.0
|
zstandard==0.23.0
|
||||||
|
|||||||
@ -7,8 +7,8 @@
|
|||||||
{% block content %}
|
{% block content %}
|
||||||
{% element h1 %}
|
{% element h1 %}
|
||||||
{% translate "Account Inactive" %}
|
{% translate "Account Inactive" %}
|
||||||
{% endelement %}
|
{% endelement %}
|
||||||
{% element p %}
|
{% element p %}
|
||||||
{% translate "This account is inactive." %}
|
{% translate "This account is inactive." %}
|
||||||
{% endelement %}
|
{% endelement %}
|
||||||
{% endblock content %}
|
{% endblock content %}
|
||||||
|
|||||||
@ -7,42 +7,42 @@
|
|||||||
{% block content %}
|
{% block content %}
|
||||||
{% element h1 %}
|
{% element h1 %}
|
||||||
{% translate "Enter Email Verification Code" %}
|
{% translate "Enter Email Verification Code" %}
|
||||||
{% endelement %}
|
{% endelement %}
|
||||||
{% setvar email_link %}
|
{% setvar email_link %}
|
||||||
<a href="mailto:{{ email }}">{{ email }}</a>
|
<a href="mailto:{{ email }}">{{ email }}</a>
|
||||||
{% endsetvar %}
|
{% endsetvar %}
|
||||||
{% element p %}
|
{% element p %}
|
||||||
{% blocktranslate %}We’ve sent a code to {{ email_link }}. The code expires shortly, so please enter it soon.{% endblocktranslate %}
|
{% blocktranslate %}We’ve sent a code to {{ email_link }}. The code expires shortly, so please enter it soon.{% endblocktranslate %}
|
||||||
{% endelement %}
|
{% endelement %}
|
||||||
{% url 'account_email_verification_sent' as action_url %}
|
{% url 'account_email_verification_sent' as action_url %}
|
||||||
{% element form form=form method="post" action=action_url tags="entrance,email,verification" %}
|
{% element form form=form method="post" action=action_url tags="entrance,email,verification" %}
|
||||||
{% slot body %}
|
{% slot body %}
|
||||||
{% csrf_token %}
|
{% csrf_token %}
|
||||||
{% element fields form=form unlabeled=True %}
|
{% element fields form=form unlabeled=True %}
|
||||||
{% endelement %}
|
{% endelement %}
|
||||||
{{ redirect_field }}
|
{{ redirect_field }}
|
||||||
{% endslot %}
|
{% endslot %}
|
||||||
{% slot actions %}
|
{% slot actions %}
|
||||||
{% element button type="submit" tags="prominent,confirm" %}
|
{% element button type="submit" tags="prominent,confirm" %}
|
||||||
{% translate "Confirm" %}
|
{% translate "Confirm" %}
|
||||||
{% endelement %}
|
{% endelement %}
|
||||||
{% if cancel_url %}
|
{% if cancel_url %}
|
||||||
{% element button href=cancel_url tags="link,cancel" %}
|
{% element button href=cancel_url tags="link,cancel" %}
|
||||||
{% translate "Cancel" %}
|
{% translate "Cancel" %}
|
||||||
{% endelement %}
|
{% endelement %}
|
||||||
{% else %}
|
{% else %}
|
||||||
{% element button type="submit" form="logout-from-stage" tags="link,cancel" %}
|
{% element button type="submit" form="logout-from-stage" tags="link,cancel" %}
|
||||||
{% translate "Cancel" %}
|
{% translate "Cancel" %}
|
||||||
{% endelement %}
|
{% endelement %}
|
||||||
{% endif %}
|
{% endif %}
|
||||||
{% endslot %}
|
{% endslot %}
|
||||||
{% endelement %}
|
{% endelement %}
|
||||||
{% if not cancel_url %}
|
{% if not cancel_url %}
|
||||||
<form id="logout-from-stage"
|
<form id="logout-from-stage"
|
||||||
method="post"
|
method="post"
|
||||||
action="{% url 'account_logout' %}">
|
action="{% url 'account_logout' %}">
|
||||||
<input type="hidden" name="next" value="{% url 'account_login' %}">
|
<input type="hidden" name="next" value="{% url 'account_login' %}">
|
||||||
{% csrf_token %}
|
{% csrf_token %}
|
||||||
</form>
|
</form>
|
||||||
{% endif %}
|
{% endif %}
|
||||||
{% endblock content %}
|
{% endblock content %}
|
||||||
|
|||||||
@ -52,6 +52,6 @@
|
|||||||
{% csrf_token %}
|
{% csrf_token %}
|
||||||
</form>
|
</form>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
{% endblock content %}
|
{% endblock content %}
|
||||||
|
|||||||
@ -7,22 +7,22 @@
|
|||||||
{% block content %}
|
{% block content %}
|
||||||
{% element h1 %}
|
{% element h1 %}
|
||||||
{% trans "Email Address" %}
|
{% trans "Email Address" %}
|
||||||
{% endelement %}
|
{% endelement %}
|
||||||
{% if not emailaddresses %}
|
{% if not emailaddresses %}
|
||||||
{% include "account/snippets/warn_no_email.html" %}
|
{% include "account/snippets/warn_no_email.html" %}
|
||||||
{% endif %}
|
{% endif %}
|
||||||
{% url 'account_email' as action_url %}
|
{% url 'account_email' as action_url %}
|
||||||
{% element form method="post" action=action_url %}
|
{% element form method="post" action=action_url %}
|
||||||
{% slot body %}
|
{% slot body %}
|
||||||
{% csrf_token %}
|
{% csrf_token %}
|
||||||
{% if current_emailaddress %}
|
{% if current_emailaddress %}
|
||||||
{% element field id="current_email" disabled=True type="email" value=current_emailaddress.email %}
|
{% element field id="current_email" disabled=True type="email" value=current_emailaddress.email %}
|
||||||
{% slot label %}
|
{% slot label %}
|
||||||
{% translate "Current email" %}:
|
{% translate "Current email" %}:
|
||||||
{% endslot %}
|
{% endslot %}
|
||||||
{% endelement %}
|
{% endelement %}
|
||||||
{% endif %}
|
{% endif %}
|
||||||
{% if new_emailaddress %}
|
{% if new_emailaddress %}
|
||||||
{% element field id="new_email" value=new_emailaddress.email disabled=True type="email" %}
|
{% element field id="new_email" value=new_emailaddress.email disabled=True type="email" %}
|
||||||
{% slot label %}
|
{% slot label %}
|
||||||
{% if not current_emailaddress %}
|
{% if not current_emailaddress %}
|
||||||
@ -30,33 +30,33 @@
|
|||||||
{% else %}
|
{% else %}
|
||||||
{% translate "Changing to" %}:
|
{% translate "Changing to" %}:
|
||||||
{% endif %}
|
{% endif %}
|
||||||
{% endslot %}
|
{% endslot %}
|
||||||
{% slot help_text %}
|
{% slot help_text %}
|
||||||
{% blocktranslate %}Your email address is still pending verification.{% endblocktranslate %}
|
{% blocktranslate %}Your email address is still pending verification.{% endblocktranslate %}
|
||||||
{% element button form="pending-email" type="submit" name="action_send" tags="minor,secondary" %}
|
{% element button form="pending-email" type="submit" name="action_send" tags="minor,secondary" %}
|
||||||
{% trans 'Re-send Verification' %}
|
{% trans 'Re-send Verification' %}
|
||||||
{% endelement %}
|
{% endelement %}
|
||||||
{% if current_emailaddress %}
|
{% if current_emailaddress %}
|
||||||
{% element button form="pending-email" type="submit" name="action_remove" tags="danger,minor" %}
|
{% element button form="pending-email" type="submit" name="action_remove" tags="danger,minor" %}
|
||||||
{% trans 'Cancel Change' %}
|
{% trans 'Cancel Change' %}
|
||||||
{% endelement %}
|
{% endelement %}
|
||||||
{% endif %}
|
{% endif %}
|
||||||
{% endslot %}
|
{% endslot %}
|
||||||
{% endelement %}
|
{% endelement %}
|
||||||
{% endif %}
|
{% endif %}
|
||||||
{% element field id=form.email.auto_id name="email" value=form.email.value errors=form.email.errors type="email" %}
|
{% element field id=form.email.auto_id name="email" value=form.email.value errors=form.email.errors type="email" %}
|
||||||
{% slot label %}
|
{% slot label %}
|
||||||
{% translate "Change to" %}:
|
{% translate "Change to" %}:
|
||||||
{% endslot %}
|
{% endslot %}
|
||||||
{% endelement %}
|
{% endelement %}
|
||||||
{% endslot %}
|
{% endslot %}
|
||||||
{% slot actions %}
|
{% slot actions %}
|
||||||
{% element button name="action_add" type="submit" %}
|
{% element button name="action_add" type="submit" %}
|
||||||
{% trans "Change Email" %}
|
{% trans "Change Email" %}
|
||||||
{% endelement %}
|
{% endelement %}
|
||||||
{% endslot %}
|
{% endslot %}
|
||||||
{% endelement %}
|
{% endelement %}
|
||||||
{% if new_emailaddress %}
|
{% if new_emailaddress %}
|
||||||
<form style="display: none"
|
<form style="display: none"
|
||||||
id="pending-email"
|
id="pending-email"
|
||||||
method="post"
|
method="post"
|
||||||
@ -64,5 +64,5 @@
|
|||||||
{% csrf_token %}
|
{% csrf_token %}
|
||||||
<input type="hidden" name="email" value="{{ new_emailaddress.email }}">
|
<input type="hidden" name="email" value="{{ new_emailaddress.email }}">
|
||||||
</form>
|
</form>
|
||||||
{% endif %}
|
{% endif %}
|
||||||
{% endblock content %}
|
{% endblock content %}
|
||||||
|
|||||||
@ -204,12 +204,12 @@
|
|||||||
<button class="btn btn-support-chat p-0 border border-translucent">
|
<button class="btn btn-support-chat p-0 border border-translucent">
|
||||||
<span class="fs-8 btn-text text-primary text-nowrap">Chat demo</span><span class="ping-icon-wrapper mt-n4 ms-n6 mt-sm-0 ms-sm-2 position-absolute position-sm-relative"><span class="ping-icon-bg"></span><span class="fa-solid fa-circle ping-icon"></span></span><span class="fa-solid fa-headset text-primary fs-8 d-sm-none"></span><span class="fa-solid fa-chevron-down text-primary fs-7"></span>
|
<span class="fs-8 btn-text text-primary text-nowrap">Chat demo</span><span class="ping-icon-wrapper mt-n4 ms-n6 mt-sm-0 ms-sm-2 position-absolute position-sm-relative"><span class="ping-icon-bg"></span><span class="fa-solid fa-circle ping-icon"></span></span><span class="fa-solid fa-headset text-primary fs-8 d-sm-none"></span><span class="fa-solid fa-chevron-down text-primary fs-7"></span>
|
||||||
</button>
|
</button>
|
||||||
</div>
|
</div>
|
||||||
</main>
|
</main>
|
||||||
<!-- ===============================================-->
|
<!-- ===============================================-->
|
||||||
<!-- End of Main Content-->
|
<!-- End of Main Content-->
|
||||||
<!-- ===============================================-->
|
<!-- ===============================================-->
|
||||||
<div class="offcanvas offcanvas-end settings-panel border-0"
|
<div class="offcanvas offcanvas-end settings-panel border-0"
|
||||||
id="settings-offcanvas"
|
id="settings-offcanvas"
|
||||||
tabindex="-1"
|
tabindex="-1"
|
||||||
aria-labelledby="settings-offcanvas">
|
aria-labelledby="settings-offcanvas">
|
||||||
@ -548,8 +548,8 @@
|
|||||||
href="https://themes.getbootstrap.com/product/phoenix-admin-dashboard-webapp-template/"
|
href="https://themes.getbootstrap.com/product/phoenix-admin-dashboard-webapp-template/"
|
||||||
target="_blank">Purchase template</a>
|
target="_blank">Purchase template</a>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<a class="card setting-toggle"
|
<a class="card setting-toggle"
|
||||||
href="#settings-offcanvas"
|
href="#settings-offcanvas"
|
||||||
data-bs-toggle="offcanvas">
|
data-bs-toggle="offcanvas">
|
||||||
<div class="card-body d-flex align-items-center px-2 py-1">
|
<div class="card-body d-flex align-items-center px-2 py-1">
|
||||||
@ -571,19 +571,19 @@
|
|||||||
</div>
|
</div>
|
||||||
<small class="text-uppercase text-body-tertiary fw-bold py-2 pe-2 ps-1 rounded-end">customize</small>
|
<small class="text-uppercase text-body-tertiary fw-bold py-2 pe-2 ps-1 rounded-end">customize</small>
|
||||||
</div>
|
</div>
|
||||||
</a>
|
</a>
|
||||||
<!-- ===============================================-->
|
<!-- ===============================================-->
|
||||||
<!-- JavaScripts-->
|
<!-- JavaScripts-->
|
||||||
<!-- ===============================================-->
|
<!-- ===============================================-->
|
||||||
<script src="../../../vendors/popper/popper.min.js"></script>
|
<script src="../../../vendors/popper/popper.min.js"></script>
|
||||||
<script src="../../../vendors/bootstrap/bootstrap.min.js"></script>
|
<script src="../../../vendors/bootstrap/bootstrap.min.js"></script>
|
||||||
<script src="../../../vendors/anchorjs/anchor.min.js"></script>
|
<script src="../../../vendors/anchorjs/anchor.min.js"></script>
|
||||||
<script src="../../../vendors/is/is.min.js"></script>
|
<script src="../../../vendors/is/is.min.js"></script>
|
||||||
<script src="../../../vendors/fontawesome/all.min.js"></script>
|
<script src="../../../vendors/fontawesome/all.min.js"></script>
|
||||||
<script src="../../../vendors/lodash/lodash.min.js"></script>
|
<script src="../../../vendors/lodash/lodash.min.js"></script>
|
||||||
<script src="../../../vendors/list.js/list.min.js"></script>
|
<script src="../../../vendors/list.js/list.min.js"></script>
|
||||||
<script src="../../../vendors/feather-icons/feather.min.js"></script>
|
<script src="../../../vendors/feather-icons/feather.min.js"></script>
|
||||||
<script src="../../../vendors/dayjs/dayjs.min.js"></script>
|
<script src="../../../vendors/dayjs/dayjs.min.js"></script>
|
||||||
<script src="../../../assets/js/phoenix.js"></script>
|
<script src="../../../assets/js/phoenix.js"></script>
|
||||||
</body>
|
</body>
|
||||||
</html>
|
</html>
|
||||||
|
|||||||
@ -91,11 +91,11 @@
|
|||||||
{% trans "Mail me a sign-in code" %}
|
{% trans "Mail me a sign-in code" %}
|
||||||
{% endelement %}
|
{% endelement %}
|
||||||
{% endif %}
|
{% endif %}
|
||||||
{% endelement %}
|
{% endelement %}
|
||||||
{% endif %}
|
{% endif %}
|
||||||
{% if SOCIALACCOUNT_ENABLED %}
|
{% if SOCIALACCOUNT_ENABLED %}
|
||||||
{% include "socialaccount/snippets/login.html" with page_layout="entrance" %}
|
{% include "socialaccount/snippets/login.html" with page_layout="entrance" %}
|
||||||
{% endif %}
|
{% endif %}
|
||||||
{% endblock content %}
|
{% endblock content %}
|
||||||
{% block extra_body %}
|
{% block extra_body %}
|
||||||
{{ block.super }}
|
{{ block.super }}
|
||||||
|
|||||||
@ -7,19 +7,19 @@
|
|||||||
{% block content %}
|
{% block content %}
|
||||||
{% element h1 %}
|
{% element h1 %}
|
||||||
{% trans "Set Password" %}
|
{% trans "Set Password" %}
|
||||||
{% endelement %}
|
{% endelement %}
|
||||||
{% url 'account_set_password' as action_url %}
|
{% url 'account_set_password' as action_url %}
|
||||||
{% element form method="post" action=action_url %}
|
{% element form method="post" action=action_url %}
|
||||||
{% slot body %}
|
{% slot body %}
|
||||||
{% csrf_token %}
|
{% csrf_token %}
|
||||||
{{ redirect_field }}
|
{{ redirect_field }}
|
||||||
{% element fields form=form %}
|
{% element fields form=form %}
|
||||||
{% endelement %}
|
{% endelement %}
|
||||||
{% endslot %}
|
{% endslot %}
|
||||||
{% slot actions %}
|
{% slot actions %}
|
||||||
{% element button type="submit" name="action" %}
|
{% element button type="submit" name="action" %}
|
||||||
{% trans 'Set Password' %}
|
{% trans 'Set Password' %}
|
||||||
{% endelement %}
|
{% endelement %}
|
||||||
{% endslot %}
|
{% endslot %}
|
||||||
{% endelement %}
|
{% endelement %}
|
||||||
{% endblock content %}
|
{% endblock content %}
|
||||||
|
|||||||
@ -4,19 +4,19 @@
|
|||||||
{% block reauthenticate_content %}
|
{% block reauthenticate_content %}
|
||||||
{% element p %}
|
{% element p %}
|
||||||
{% blocktranslate %}Enter your password:{% endblocktranslate %}
|
{% blocktranslate %}Enter your password:{% endblocktranslate %}
|
||||||
{% endelement %}
|
{% endelement %}
|
||||||
{% url 'account_reauthenticate' as action_url %}
|
{% url 'account_reauthenticate' as action_url %}
|
||||||
{% element form form=form method="post" action=action_url %}
|
{% element form form=form method="post" action=action_url %}
|
||||||
{% slot body %}
|
{% slot body %}
|
||||||
{% csrf_token %}
|
{% csrf_token %}
|
||||||
{% element fields form=form unlabeled=True %}
|
{% element fields form=form unlabeled=True %}
|
||||||
{% endelement %}
|
{% endelement %}
|
||||||
{{ redirect_field }}
|
{{ redirect_field }}
|
||||||
{% endslot %}
|
{% endslot %}
|
||||||
{% slot actions %}
|
{% slot actions %}
|
||||||
{% element button type="submit" tags="primary,reauthenticate" %}
|
{% element button type="submit" tags="primary,reauthenticate" %}
|
||||||
{% trans "Confirm" %}
|
{% trans "Confirm" %}
|
||||||
{% endelement %}
|
{% endelement %}
|
||||||
{% endslot %}
|
{% endslot %}
|
||||||
{% endelement %}
|
{% endelement %}
|
||||||
{% endblock %}
|
{% endblock %}
|
||||||
|
|||||||
@ -7,26 +7,26 @@
|
|||||||
{% block content %}
|
{% block content %}
|
||||||
{% element h1 %}
|
{% element h1 %}
|
||||||
{% translate "Mail me a sign-in code" %}
|
{% translate "Mail me a sign-in code" %}
|
||||||
{% endelement %}
|
{% endelement %}
|
||||||
{% element p %}
|
{% element p %}
|
||||||
{% blocktranslate %}You will receive an email containing a special code for a password-free sign-in.{% endblocktranslate %}
|
{% blocktranslate %}You will receive an email containing a special code for a password-free sign-in.{% endblocktranslate %}
|
||||||
{% endelement %}
|
{% endelement %}
|
||||||
{% url 'account_request_login_code' as login_url %}
|
{% url 'account_request_login_code' as login_url %}
|
||||||
{% element form form=form method="post" action=login_url tags="entrance,login" %}
|
{% element form form=form method="post" action=login_url tags="entrance,login" %}
|
||||||
{% slot body %}
|
{% slot body %}
|
||||||
{% csrf_token %}
|
{% csrf_token %}
|
||||||
{% element fields form=form unlabeled=True %}
|
{% element fields form=form unlabeled=True %}
|
||||||
{% endelement %}
|
{% endelement %}
|
||||||
{{ redirect_field }}
|
{{ redirect_field }}
|
||||||
{% endslot %}
|
{% endslot %}
|
||||||
{% slot actions %}
|
{% slot actions %}
|
||||||
{% element button type="submit" tags="prominent,login" %}
|
{% element button type="submit" tags="prominent,login" %}
|
||||||
{% translate "Request Code" %}
|
{% translate "Request Code" %}
|
||||||
{% endelement %}
|
{% endelement %}
|
||||||
{% endslot %}
|
{% endslot %}
|
||||||
{% endelement %}
|
{% endelement %}
|
||||||
{% url 'account_login' as login_url %}
|
{% url 'account_login' as login_url %}
|
||||||
{% element button href=login_url tags="link" %}
|
{% element button href=login_url tags="link" %}
|
||||||
{% translate "Other sign-in options" %}
|
{% translate "Other sign-in options" %}
|
||||||
{% endelement %}
|
{% endelement %}
|
||||||
{% endblock content %}
|
{% endblock content %}
|
||||||
|
|||||||
@ -1,4 +1,4 @@
|
|||||||
{% load i18n allauth %}
|
{% load i18n allauth %}
|
||||||
{% element p %}
|
{% element p %}
|
||||||
<strong>{% trans 'Warning:' %}</strong> {% trans "You currently do not have any email address set up. You should really add an email address so you can receive notifications, reset your password, etc." %}
|
<strong>{% trans 'Warning:' %}</strong> {% trans "You currently do not have any email address set up. You should really add an email address so you can receive notifications, reset your password, etc." %}
|
||||||
{% endelement %}
|
{% endelement %}
|
||||||
|
|||||||
@ -5,7 +5,7 @@
|
|||||||
{% endblock %}
|
{% endblock %}
|
||||||
|
|
||||||
{% block content %}
|
{% block content %}
|
||||||
{% if request.user.is_superuser %}
|
{% if request.user.is_superuser %}
|
||||||
<div class="container py-5">
|
<div class="container py-5">
|
||||||
<header class="mb-5">
|
<header class="mb-5">
|
||||||
<h1 class="display-4 fw-bold">
|
<h1 class="display-4 fw-bold">
|
||||||
@ -42,5 +42,5 @@
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
{% endif %}
|
{% endif %}
|
||||||
{% endblock content %}
|
{% endblock content %}
|
||||||
@ -97,7 +97,7 @@
|
|||||||
{{ form.as_p }} <!-- Renders the form fields -->
|
{{ form.as_p }} <!-- Renders the form fields -->
|
||||||
<button type="submit">{% trans 'Reset Password' %}</button>
|
<button type="submit">{% trans 'Reset Password' %}</button>
|
||||||
</form>
|
</form>
|
||||||
{% endblock %}
|
{% endblock %}
|
||||||
{% block customJS %}
|
{% block customJS %}
|
||||||
<script src="{% static 'js/js-utils.js' %}"></script>
|
<script src="{% static 'js/js-utils.js' %}"></script>
|
||||||
{% endblock %}
|
{% endblock %}
|
||||||
|
|||||||
@ -94,6 +94,7 @@
|
|||||||
<th class="sort white-space-nowrap align-middle " scope="col">{% trans 'PO' %}</th>
|
<th class="sort white-space-nowrap align-middle " scope="col">{% trans 'PO' %}</th>
|
||||||
</tr>
|
</tr>
|
||||||
</thead>
|
</thead>
|
||||||
|
|
||||||
<tbody class="list fs-9" id="project-list-table-body">
|
<tbody class="list fs-9" id="project-list-table-body">
|
||||||
{% for bill_item in itemtxs_qs %}
|
{% for bill_item in itemtxs_qs %}
|
||||||
<tr>
|
<tr>
|
||||||
@ -161,4 +162,4 @@
|
|||||||
</div>
|
</div>
|
||||||
<div>
|
<div>
|
||||||
{% include "bill/includes/mark_as.html" %}
|
{% include "bill/includes/mark_as.html" %}
|
||||||
{% endblock %}
|
{% endblock %}
|
||||||
|
|||||||
@ -50,7 +50,7 @@
|
|||||||
</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-start">
|
||||||
<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 %}
|
||||||
@ -199,7 +199,7 @@
|
|||||||
{% endif %}
|
{% endif %}
|
||||||
</div>
|
</div>
|
||||||
<div class="card-footer p-0">
|
<div class="card-footer p-0">
|
||||||
<div class="d-flex flex-wrap gap-2 mt-2">
|
<div class="d-flex flex-wrap gap-2 mt-2 justify-content-end">
|
||||||
<!-- 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 %}
|
||||||
@ -296,6 +296,15 @@
|
|||||||
//document.addEventListener('htmx:afterSwap',processElements);
|
//document.addEventListener('htmx:afterSwap',processElements);
|
||||||
|
|
||||||
function processElements() {
|
function processElements() {
|
||||||
|
let bill_status = "{{bill.bill_status}}";
|
||||||
|
if(bill_status == 'draft'){
|
||||||
|
const formDiv = document.getElementById('bill-update-form');
|
||||||
|
if (formDiv) {
|
||||||
|
setTimeout(()=>{
|
||||||
|
formDiv.scrollIntoView({ behavior: 'smooth', block: 'center' });
|
||||||
|
},500)
|
||||||
|
}
|
||||||
|
}
|
||||||
window.showPOModal = function(title, actionUrl, buttonText) {
|
window.showPOModal = function(title, actionUrl, buttonText) {
|
||||||
const modalEl = document.getElementById('POModal');
|
const modalEl = document.getElementById('POModal');
|
||||||
if (!modalEl) {
|
if (!modalEl) {
|
||||||
|
|||||||
@ -2,7 +2,7 @@
|
|||||||
{% load static %}
|
{% load static %}
|
||||||
{% load django_ledger %}
|
{% load django_ledger %}
|
||||||
{% load widget_tweaks %}
|
{% load widget_tweaks %}
|
||||||
<form id="bill-update-form"
|
<form id="bill-update-form"
|
||||||
action="{% url 'bill-update-items' dealer_slug=dealer_slug entity_slug=entity_slug bill_pk=bill_pk %}"
|
action="{% url 'bill-update-items' dealer_slug=dealer_slug entity_slug=entity_slug bill_pk=bill_pk %}"
|
||||||
method="post">
|
method="post">
|
||||||
<div class="container-fluid py-4">
|
<div class="container-fluid py-4">
|
||||||
@ -130,4 +130,4 @@
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</form>
|
</form>
|
||||||
|
|||||||
@ -55,8 +55,8 @@
|
|||||||
</p>
|
</p>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div class="card-footer d-flex align-items-center gap-2 border-top border-translucent ps-3 pe-4 py-3">
|
<div class="card-footer d-flex align-items-center gap-2 border-top border-translucent ps-3 pe-4 py-3">
|
||||||
<div class="d-flex align-items-center flex-1 gap-3 border border-translucent rounded-pill px-4">
|
<div class="d-flex align-items-center flex-1 gap-3 border border-translucent rounded-pill px-4">
|
||||||
<input class="form-control outline-none border-0 flex-1 fs-9 px-0"
|
<input class="form-control outline-none border-0 flex-1 fs-9 px-0"
|
||||||
type="text"
|
type="text"
|
||||||
@ -75,10 +75,10 @@
|
|||||||
<button class="btn p-0 border-0 send-btn">
|
<button class="btn p-0 border-0 send-btn">
|
||||||
<span class="fa-solid fa-paper-plane fs-9"></span>
|
<span class="fa-solid fa-paper-plane fs-9"></span>
|
||||||
</button>
|
</button>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<button class="btn btn-support-chat p-0 border border-translucent">
|
<button class="btn btn-support-chat p-0 border border-translucent">
|
||||||
<span class="fs-8 btn-text text-primary text-nowrap">Chat demo</span><span class="ping-icon-wrapper mt-n4 ms-n6 mt-sm-0 ms-sm-2 position-absolute position-sm-relative"><span class="ping-icon-bg"></span><span class="fa-solid fa-circle ping-icon"></span></span><span class="fa-solid fa-headset text-primary fs-8 d-sm-none"></span><span class="fa-solid fa-chevron-down text-primary fs-7"></span>
|
<span class="fs-8 btn-text text-primary text-nowrap">Chat demo</span><span class="ping-icon-wrapper mt-n4 ms-n6 mt-sm-0 ms-sm-2 position-absolute position-sm-relative"><span class="ping-icon-bg"></span><span class="fa-solid fa-circle ping-icon"></span></span><span class="fa-solid fa-headset text-primary fs-8 d-sm-none"></span><span class="fa-solid fa-chevron-down text-primary fs-7"></span>
|
||||||
</button>
|
</button>
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@ -22,6 +22,7 @@
|
|||||||
hx-target="#notesTable"
|
hx-target="#notesTable"
|
||||||
hx-on::after-request="{ resetSubmitButton(document.querySelector('.add_note_form button[type=submit]')); $('#noteModal').modal('hide'); }"
|
hx-on::after-request="{ resetSubmitButton(document.querySelector('.add_note_form button[type=submit]')); $('#noteModal').modal('hide'); }"
|
||||||
hx-swap="outerHTML show:window.top"
|
hx-swap="outerHTML show:window.top"
|
||||||
|
hx-select-oob="#timeline"
|
||||||
method="post"
|
method="post"
|
||||||
class="add_note_form">
|
class="add_note_form">
|
||||||
{% csrf_token %}
|
{% csrf_token %}
|
||||||
|
|||||||
@ -22,7 +22,7 @@
|
|||||||
hx-target=".taskTable"
|
hx-target=".taskTable"
|
||||||
hx-on::after-request="{ resetSubmitButton(document.querySelector('.add_schedule_form button[type=submit]')); $('#scheduleModal').modal('hide'); }"
|
hx-on::after-request="{ resetSubmitButton(document.querySelector('.add_schedule_form button[type=submit]')); $('#scheduleModal').modal('hide'); }"
|
||||||
hx-swap="outerHTML"
|
hx-swap="outerHTML"
|
||||||
hx-select-oob="#toast-container:outerHTML"
|
hx-select-oob="#toast-container:outerHTML,#timeline:outerHTML"
|
||||||
method="post"
|
method="post"
|
||||||
class="add_schedule_form">
|
class="add_schedule_form">
|
||||||
{% csrf_token %}
|
{% csrf_token %}
|
||||||
|
|||||||
@ -335,7 +335,7 @@
|
|||||||
</div>
|
</div>
|
||||||
<div class="row justify-content-between align-items-md-center hover-actions-trigger btn-reveal-trigger border-translucent py-3 gx-0 border-top">
|
<div class="row justify-content-between align-items-md-center hover-actions-trigger btn-reveal-trigger border-translucent py-3 gx-0 border-top">
|
||||||
<div class="col-12 col-lg-auto">
|
<div class="col-12 col-lg-auto">
|
||||||
<div class="timeline-basic mb-9">
|
<div id="timeline" class="timeline-basic mb-9">
|
||||||
{% for activity in activities %}
|
{% for activity in activities %}
|
||||||
<div class="timeline-item">
|
<div class="timeline-item">
|
||||||
<div class="row g-3">
|
<div class="row g-3">
|
||||||
@ -354,6 +354,8 @@
|
|||||||
<span class="fa-solid fa-users text-danger fs-8"></span>
|
<span class="fa-solid fa-users text-danger fs-8"></span>
|
||||||
{% elif activity.activity_type == "whatsapp" %}
|
{% elif activity.activity_type == "whatsapp" %}
|
||||||
<span class="fab fa-whatsapp text-success-dark fs-7"></span>
|
<span class="fab fa-whatsapp text-success-dark fs-7"></span>
|
||||||
|
{% elif activity.activity_type == "meeting" %}
|
||||||
|
<span class="fa-solid fa-users text-danger fs-8"></span>
|
||||||
{% endif %}
|
{% endif %}
|
||||||
</div>
|
</div>
|
||||||
{% if forloop.last %}
|
{% if forloop.last %}
|
||||||
@ -815,8 +817,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 = ""
|
||||||
@ -873,4 +875,4 @@
|
|||||||
});
|
});
|
||||||
|
|
||||||
</script>
|
</script>
|
||||||
{% endblock customJS %}
|
{% endblock customJS %}
|
||||||
|
|||||||
@ -260,4 +260,4 @@
|
|||||||
{% 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=empty_state_value url=create_lead_url %}
|
{% include "empty-illustration-page.html" with value=empty_state_value url=create_lead_url %}
|
||||||
{% endif %}
|
{% endif %}
|
||||||
{% endblock %}
|
{% endblock %}
|
||||||
|
|||||||
@ -59,6 +59,6 @@
|
|||||||
</form>
|
</form>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
{% endblock content %}
|
{% endblock content %}
|
||||||
|
|||||||
@ -3,16 +3,16 @@
|
|||||||
<form method="post"
|
<form method="post"
|
||||||
action="{% url 'update_note_to_lead' note.pk %}"
|
action="{% url 'update_note_to_lead' note.pk %}"
|
||||||
enctype="multipart/form-data">
|
enctype="multipart/form-data">
|
||||||
{% else %}
|
{% else %}
|
||||||
<form method="post"
|
<form method="post"
|
||||||
action="{% url 'add_note_to_lead' lead.slug %}"
|
action="{% url 'add_note_to_lead' lead.slug %}"
|
||||||
enctype="multipart/form-data">
|
enctype="multipart/form-data">
|
||||||
{% endif %}
|
{% endif %}
|
||||||
{% csrf_token %}
|
{% csrf_token %}
|
||||||
{{ form|crispy }}
|
{{ form|crispy }}
|
||||||
{% if form.instance.pk %}
|
{% if form.instance.pk %}
|
||||||
<button type="submit" class="btn btn-sm btn-phoenix-primary w-100">{{ _("Update") }}</button>
|
<button type="submit" class="btn btn-sm btn-phoenix-primary w-100">{{ _("Update") }}</button>
|
||||||
{% else %}
|
{% else %}
|
||||||
<button type="submit" class="btn btn-sm btn-phoenix-success w-100">{{ _("Add") }}</button>
|
<button type="submit" class="btn btn-sm btn-phoenix-success w-100">{{ _("Add") }}</button>
|
||||||
{% endif %}
|
{% endif %}
|
||||||
</form>
|
</form>
|
||||||
|
|||||||
@ -7,24 +7,24 @@
|
|||||||
|
|
||||||
{% block customeCSS %}
|
{% block customeCSS %}
|
||||||
|
|
||||||
/* General Layout */
|
/* General Layout */
|
||||||
body {
|
body {
|
||||||
background-color: #f8f9fa; /* Light gray background for contrast */
|
background-color: #f8f9fa; /* Light gray background for contrast */
|
||||||
}
|
}
|
||||||
|
|
||||||
/* List Item Styling */
|
/* List Item Styling */
|
||||||
.list-item {
|
.list-item {
|
||||||
transition: background-color 0.2s ease-in-out, transform 0.2s ease-in-out;
|
transition: background-color 0.2s ease-in-out, transform 0.2s ease-in-out;
|
||||||
cursor: pointer;
|
cursor: pointer;
|
||||||
}
|
}
|
||||||
|
|
||||||
.list-item:hover {
|
.list-item:hover {
|
||||||
background-color: #f1f3f5;
|
background-color: #f1f3f5;
|
||||||
transform: translateY(-2px); /* Subtle lift on hover */
|
transform: translateY(-2px); /* Subtle lift on hover */
|
||||||
}
|
}
|
||||||
|
|
||||||
/* Unread Notification Dot */
|
/* Unread Notification Dot */
|
||||||
.unread-dot {
|
.unread-dot {
|
||||||
position: absolute;
|
position: absolute;
|
||||||
left: 0;
|
left: 0;
|
||||||
top: 50%;
|
top: 50%;
|
||||||
@ -32,12 +32,12 @@ body {
|
|||||||
width: 8px;
|
width: 8px;
|
||||||
height: 8px;
|
height: 8px;
|
||||||
border-radius: 50%;
|
border-radius: 50%;
|
||||||
}
|
}
|
||||||
|
|
||||||
/* HTMX Loading Indicator */
|
/* HTMX Loading Indicator */
|
||||||
.htmx-request #loading-indicator {
|
.htmx-request #loading-indicator {
|
||||||
display: block;
|
display: block;
|
||||||
}
|
}
|
||||||
{% endblock %}
|
{% endblock %}
|
||||||
{% block content %}
|
{% block content %}
|
||||||
<main class="container-fluid p-5">
|
<main class="container-fluid p-5">
|
||||||
|
|||||||
@ -28,7 +28,7 @@
|
|||||||
href="{% url 'estimate_detail' request.dealer.slug opportunity.estimate.pk %}">{{ _("View Quotation") }}</a>
|
href="{% url 'estimate_detail' request.dealer.slug opportunity.estimate.pk %}">{{ _("View Quotation") }}</a>
|
||||||
{% endif %}
|
{% endif %}
|
||||||
{% else %}
|
{% else %}
|
||||||
{% if perms.django_ledger.add_estimatemodel %}
|
{% if perms.django_ledger.add_estimatemodel and not opportunity.estimate %}
|
||||||
<a class="dropdown-item"
|
<a class="dropdown-item"
|
||||||
type="button"
|
type="button"
|
||||||
data-bs-toggle="modal"
|
data-bs-toggle="modal"
|
||||||
@ -629,7 +629,7 @@
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class="tab-pane fade"
|
<div class="tab-pane fade"
|
||||||
id="tab-emails"
|
id="tab-emails"
|
||||||
role="tabpanel"
|
role="tabpanel"
|
||||||
aria-labelledby="emails-tab">
|
aria-labelledby="emails-tab">
|
||||||
@ -738,9 +738,9 @@
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class="tab-pane fade"
|
<div class="tab-pane fade"
|
||||||
id="tab-activity"
|
id="tab-activity"
|
||||||
hx-get="{% url 'opportunity_detail' request.dealer.slug opportunity.slug %}"
|
hx-get="{% url 'opportunity_detail' request.dealer.slug opportunity.slug %}"
|
||||||
hx-trigger="htmx:afterRequest from:"
|
hx-trigger="htmx:afterRequest from:"
|
||||||
@ -813,11 +813,11 @@
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
{% endfor %}
|
{% endfor %}
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div class="modal fade"
|
<div class="modal fade"
|
||||||
id="updateStageModal"
|
id="updateStageModal"
|
||||||
tabindex="-1"
|
tabindex="-1"
|
||||||
aria-hidden="true">
|
aria-hidden="true">
|
||||||
@ -846,18 +846,18 @@
|
|||||||
</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" %}
|
||||||
<!-- task Modal -->
|
<!-- task Modal -->
|
||||||
{% include "components/task_modal.html" with content_type="opportunity" slug=opportunity.slug %}
|
{% include "components/task_modal.html" with content_type="opportunity" slug=opportunity.slug %}
|
||||||
<!-- note Modal -->
|
<!-- note Modal -->
|
||||||
{% include "components/note_modal.html" with content_type="opportunity" slug=opportunity.slug %}
|
{% include "components/note_modal.html" with content_type="opportunity" slug=opportunity.slug %}
|
||||||
<!-- 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 %}
|
||||||
|
|
||||||
<div class="modal fade"
|
<div class="modal fade"
|
||||||
id="estimateModal"
|
id="estimateModal"
|
||||||
tabindex="-1"
|
tabindex="-1"
|
||||||
aria-labelledby="estimateModalLabel"
|
aria-labelledby="estimateModalLabel"
|
||||||
|
|||||||
@ -181,7 +181,7 @@
|
|||||||
</div>
|
</div>
|
||||||
{% endblock %}
|
{% endblock %}
|
||||||
{% block customJS %}
|
{% block customJS %}
|
||||||
<script>
|
<script>
|
||||||
function updateProbabilityValue(value) {
|
function updateProbabilityValue(value) {
|
||||||
const amount = document.getElementById('id_amount');
|
const amount = document.getElementById('id_amount');
|
||||||
const expectedRevenue = document.getElementById('id_expected_revenue');
|
const expectedRevenue = document.getElementById('id_expected_revenue');
|
||||||
@ -213,5 +213,5 @@
|
|||||||
updateProbabilityValue(rangeInput.value);
|
updateProbabilityValue(rangeInput.value);
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
</script>
|
</script>
|
||||||
{% endblock %}
|
{% endblock %}
|
||||||
@ -161,4 +161,4 @@
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
{% endfor %}
|
{% endfor %}
|
||||||
|
|||||||
@ -163,4 +163,4 @@
|
|||||||
{% url "customer_create" request.dealer.slug as create_customer_url %}
|
{% url "customer_create" request.dealer.slug as create_customer_url %}
|
||||||
{% include "empty-illustration-page.html" with value=empty_state_value url=create_customer_url %}
|
{% include "empty-illustration-page.html" with value=empty_state_value url=create_customer_url %}
|
||||||
{% endif %}
|
{% endif %}
|
||||||
{% endblock %}
|
{% endblock %}
|
||||||
|
|||||||
@ -71,7 +71,7 @@
|
|||||||
'#39e6a9', '#4d4f79', '#9f1d35', '#2a5a5b', '#f77f00',
|
'#39e6a9', '#4d4f79', '#9f1d35', '#2a5a5b', '#f77f00',
|
||||||
'#3282b8', '#00bcd4', '#009688', '#4caf50', '#8bc34a',
|
'#3282b8', '#00bcd4', '#009688', '#4caf50', '#8bc34a',
|
||||||
'#ffeb3b', '#ff9800', '#ff5722', '#795548', '#9e9e9e'
|
'#ffeb3b', '#ff9800', '#ff5722', '#795548', '#9e9e9e'
|
||||||
];
|
];
|
||||||
|
|
||||||
// Pass translated strings from Django to JavaScript
|
// Pass translated strings from Django to JavaScript
|
||||||
const translatedStrings = {
|
const translatedStrings = {
|
||||||
@ -326,11 +326,11 @@
|
|||||||
}
|
}
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
{% endif %}
|
{% endif %}
|
||||||
// -----------------------------------------------------------
|
// -----------------------------------------------------------
|
||||||
// 5. Inventory by Make (Pie Chart)
|
// 5. Inventory by Make (Pie Chart)
|
||||||
// -----------------------------------------------------------
|
// -----------------------------------------------------------
|
||||||
{% if request.is_dealer or request.is_manager or request.is_inventory %}
|
{% if request.is_dealer or request.is_manager or request.is_inventory %}
|
||||||
const ctxInventoryMake = document.getElementById('inventoryByMakeChart').getContext('2d');
|
const ctxInventoryMake = document.getElementById('inventoryByMakeChart').getContext('2d');
|
||||||
new Chart(ctxInventoryMake, {
|
new Chart(ctxInventoryMake, {
|
||||||
type: 'pie',
|
type: 'pie',
|
||||||
|
|||||||
@ -126,4 +126,4 @@
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
{% endif %}
|
{% endif %}
|
||||||
<script src="https://cdn.jsdelivr.net/npm/chart.js"></script>
|
<script src="https://cdn.jsdelivr.net/npm/chart.js"></script>
|
||||||
@ -1,7 +1,7 @@
|
|||||||
{% extends 'base.html' %}
|
{% extends 'base.html' %}
|
||||||
{% load i18n %}
|
{% load i18n %}
|
||||||
{% block title %}
|
{% block title %}
|
||||||
{% trans "Sales Dashboard" %}
|
{% trans "Sales Dashboard" %}
|
||||||
{% endblock %}
|
{% endblock %}
|
||||||
{% 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">
|
||||||
|
|||||||
@ -340,4 +340,4 @@
|
|||||||
</tr>
|
</tr>
|
||||||
</table>
|
</table>
|
||||||
</body>
|
</body>
|
||||||
</html>
|
</html>
|
||||||
|
|||||||
@ -210,12 +210,12 @@
|
|||||||
<button class="btn btn-support-chat p-0 border border-translucent">
|
<button class="btn btn-support-chat p-0 border border-translucent">
|
||||||
<span class="fs-8 btn-text text-primary text-nowrap">Chat demo</span><span class="ping-icon-wrapper mt-n4 ms-n6 mt-sm-0 ms-sm-2 position-absolute position-sm-relative"><span class="ping-icon-bg"></span><span class="fa-solid fa-circle ping-icon"></span></span><span class="fa-solid fa-headset text-primary fs-8 d-sm-none"></span><span class="fa-solid fa-chevron-down text-primary fs-7"></span>
|
<span class="fs-8 btn-text text-primary text-nowrap">Chat demo</span><span class="ping-icon-wrapper mt-n4 ms-n6 mt-sm-0 ms-sm-2 position-absolute position-sm-relative"><span class="ping-icon-bg"></span><span class="fa-solid fa-circle ping-icon"></span></span><span class="fa-solid fa-headset text-primary fs-8 d-sm-none"></span><span class="fa-solid fa-chevron-down text-primary fs-7"></span>
|
||||||
</button>
|
</button>
|
||||||
</div>
|
</div>
|
||||||
</main>
|
</main>
|
||||||
<!-- ===============================================-->
|
<!-- ===============================================-->
|
||||||
<!-- End of Main Content-->
|
<!-- End of Main Content-->
|
||||||
<!-- ===============================================-->
|
<!-- ===============================================-->
|
||||||
<div class="offcanvas offcanvas-end settings-panel border-0"
|
<div class="offcanvas offcanvas-end settings-panel border-0"
|
||||||
id="settings-offcanvas"
|
id="settings-offcanvas"
|
||||||
tabindex="-1"
|
tabindex="-1"
|
||||||
aria-labelledby="settings-offcanvas">
|
aria-labelledby="settings-offcanvas">
|
||||||
@ -532,8 +532,8 @@
|
|||||||
href="https://themes.getbootstrap.com/product/phoenix-admin-dashboard-webapp-template/"
|
href="https://themes.getbootstrap.com/product/phoenix-admin-dashboard-webapp-template/"
|
||||||
target="_blank">Purchase template</a>
|
target="_blank">Purchase template</a>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<a class="card setting-toggle"
|
<a class="card setting-toggle"
|
||||||
href="#settings-offcanvas"
|
href="#settings-offcanvas"
|
||||||
data-bs-toggle="offcanvas">
|
data-bs-toggle="offcanvas">
|
||||||
<div class="card-body d-flex align-items-center px-2 py-1">
|
<div class="card-body d-flex align-items-center px-2 py-1">
|
||||||
@ -555,19 +555,19 @@
|
|||||||
</div>
|
</div>
|
||||||
<small class="text-uppercase text-body-tertiary fw-bold py-2 pe-2 ps-1 rounded-end">customize</small>
|
<small class="text-uppercase text-body-tertiary fw-bold py-2 pe-2 ps-1 rounded-end">customize</small>
|
||||||
</div>
|
</div>
|
||||||
</a>
|
</a>
|
||||||
<!-- ===============================================-->
|
<!-- ===============================================-->
|
||||||
<!-- JavaScripts-->
|
<!-- JavaScripts-->
|
||||||
<!-- ===============================================-->
|
<!-- ===============================================-->
|
||||||
<script src="../../vendors/popper/popper.min.js"></script>
|
<script src="../../vendors/popper/popper.min.js"></script>
|
||||||
<script src="../../vendors/bootstrap/bootstrap.min.js"></script>
|
<script src="../../vendors/bootstrap/bootstrap.min.js"></script>
|
||||||
<script src="../../vendors/anchorjs/anchor.min.js"></script>
|
<script src="../../vendors/anchorjs/anchor.min.js"></script>
|
||||||
<script src="../../vendors/is/is.min.js"></script>
|
<script src="../../vendors/is/is.min.js"></script>
|
||||||
<script src="../../vendors/fontawesome/all.min.js"></script>
|
<script src="../../vendors/fontawesome/all.min.js"></script>
|
||||||
<script src="../../vendors/lodash/lodash.min.js"></script>
|
<script src="../../vendors/lodash/lodash.min.js"></script>
|
||||||
<script src="../../vendors/list.js/list.min.js"></script>
|
<script src="../../vendors/list.js/list.min.js"></script>
|
||||||
<script src="../../vendors/feather-icons/feather.min.js"></script>
|
<script src="../../vendors/feather-icons/feather.min.js"></script>
|
||||||
<script src="../../vendors/dayjs/dayjs.min.js"></script>
|
<script src="../../vendors/dayjs/dayjs.min.js"></script>
|
||||||
<script src="../../assets/js/phoenix.js"></script>
|
<script src="../../assets/js/phoenix.js"></script>
|
||||||
</body>
|
</body>
|
||||||
</html>
|
</html>
|
||||||
|
|||||||
@ -206,12 +206,12 @@
|
|||||||
<button class="btn btn-support-chat p-0 border border-translucent">
|
<button class="btn btn-support-chat p-0 border border-translucent">
|
||||||
<span class="fs-8 btn-text text-primary text-nowrap">Chat demo</span><span class="ping-icon-wrapper mt-n4 ms-n6 mt-sm-0 ms-sm-2 position-absolute position-sm-relative"><span class="ping-icon-bg"></span><span class="fa-solid fa-circle ping-icon"></span></span><span class="fa-solid fa-headset text-primary fs-8 d-sm-none"></span><span class="fa-solid fa-chevron-down text-primary fs-7"></span>
|
<span class="fs-8 btn-text text-primary text-nowrap">Chat demo</span><span class="ping-icon-wrapper mt-n4 ms-n6 mt-sm-0 ms-sm-2 position-absolute position-sm-relative"><span class="ping-icon-bg"></span><span class="fa-solid fa-circle ping-icon"></span></span><span class="fa-solid fa-headset text-primary fs-8 d-sm-none"></span><span class="fa-solid fa-chevron-down text-primary fs-7"></span>
|
||||||
</button>
|
</button>
|
||||||
</div>
|
</div>
|
||||||
</main>
|
</main>
|
||||||
<!-- ===============================================-->
|
<!-- ===============================================-->
|
||||||
<!-- End of Main Content-->
|
<!-- End of Main Content-->
|
||||||
<!-- ===============================================-->
|
<!-- ===============================================-->
|
||||||
<div class="offcanvas offcanvas-end settings-panel border-0"
|
<div class="offcanvas offcanvas-end settings-panel border-0"
|
||||||
id="settings-offcanvas"
|
id="settings-offcanvas"
|
||||||
tabindex="-1"
|
tabindex="-1"
|
||||||
aria-labelledby="settings-offcanvas">
|
aria-labelledby="settings-offcanvas">
|
||||||
@ -528,8 +528,8 @@
|
|||||||
href="https://themes.getbootstrap.com/product/phoenix-admin-dashboard-webapp-template/"
|
href="https://themes.getbootstrap.com/product/phoenix-admin-dashboard-webapp-template/"
|
||||||
target="_blank">Purchase template</a>
|
target="_blank">Purchase template</a>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<a class="card setting-toggle"
|
<a class="card setting-toggle"
|
||||||
href="#settings-offcanvas"
|
href="#settings-offcanvas"
|
||||||
data-bs-toggle="offcanvas">
|
data-bs-toggle="offcanvas">
|
||||||
<div class="card-body d-flex align-items-center px-2 py-1">
|
<div class="card-body d-flex align-items-center px-2 py-1">
|
||||||
@ -551,19 +551,19 @@
|
|||||||
</div>
|
</div>
|
||||||
<small class="text-uppercase text-body-tertiary fw-bold py-2 pe-2 ps-1 rounded-end">customize</small>
|
<small class="text-uppercase text-body-tertiary fw-bold py-2 pe-2 ps-1 rounded-end">customize</small>
|
||||||
</div>
|
</div>
|
||||||
</a>
|
</a>
|
||||||
<!-- ===============================================-->
|
<!-- ===============================================-->
|
||||||
<!-- JavaScripts-->
|
<!-- JavaScripts-->
|
||||||
<!-- ===============================================-->
|
<!-- ===============================================-->
|
||||||
<script src="../../vendors/popper/popper.min.js"></script>
|
<script src="../../vendors/popper/popper.min.js"></script>
|
||||||
<script src="../../vendors/bootstrap/bootstrap.min.js"></script>
|
<script src="../../vendors/bootstrap/bootstrap.min.js"></script>
|
||||||
<script src="../../vendors/anchorjs/anchor.min.js"></script>
|
<script src="../../vendors/anchorjs/anchor.min.js"></script>
|
||||||
<script src="../../vendors/is/is.min.js"></script>
|
<script src="../../vendors/is/is.min.js"></script>
|
||||||
<script src="../../vendors/fontawesome/all.min.js"></script>
|
<script src="../../vendors/fontawesome/all.min.js"></script>
|
||||||
<script src="../../vendors/lodash/lodash.min.js"></script>
|
<script src="../../vendors/lodash/lodash.min.js"></script>
|
||||||
<script src="../../vendors/list.js/list.min.js"></script>
|
<script src="../../vendors/list.js/list.min.js"></script>
|
||||||
<script src="../../vendors/feather-icons/feather.min.js"></script>
|
<script src="../../vendors/feather-icons/feather.min.js"></script>
|
||||||
<script src="../../vendors/dayjs/dayjs.min.js"></script>
|
<script src="../../vendors/dayjs/dayjs.min.js"></script>
|
||||||
<script src="../../assets/js/phoenix.js"></script>
|
<script src="../../assets/js/phoenix.js"></script>
|
||||||
</body>
|
</body>
|
||||||
</html>
|
</html>
|
||||||
|
|||||||
@ -35,7 +35,9 @@
|
|||||||
<div class="row my-5">
|
<div class="row my-5">
|
||||||
<div class="card rounded ">
|
<div class="card rounded ">
|
||||||
<div class="card-header ">
|
<div class="card-header ">
|
||||||
<p class="mb-0">{{ _("Group Details") }}</p>
|
<p class="mb-2">{{ _("Group Details") }}</p>
|
||||||
|
<a class="btn btn-phoenix-secondary me-2" href="{% url 'group_list' request.dealer.slug %}">{% trans "Groups List" %}</a>
|
||||||
|
<a class="btn btn-phoenix-secondary " href="{% url 'user_list' request.dealer.slug %}">{% trans "Staffs List" %}</a>
|
||||||
</div>
|
</div>
|
||||||
<div class="card-body">
|
<div class="card-body">
|
||||||
<div class="row">
|
<div class="row">
|
||||||
@ -123,4 +125,4 @@
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
{% endblock %}
|
{% endblock %}
|
||||||
|
|||||||
@ -2,60 +2,56 @@
|
|||||||
{% load i18n %}
|
{% load i18n %}
|
||||||
{% load custom_filters %}
|
{% load custom_filters %}
|
||||||
{% load render_table from django_tables2 %}
|
{% load render_table from django_tables2 %}
|
||||||
|
|
||||||
{% block title %}
|
{% block title %}
|
||||||
{% trans "Groups" %}
|
{% trans "Groups" %}
|
||||||
{% endblock title %}
|
{% endblock title %}
|
||||||
|
|
||||||
{% block content %}
|
{% block content %}
|
||||||
<main class="py-5">
|
<div class="container py-5">
|
||||||
<div class="container">
|
|
||||||
{% if groups or request.GET.q %}
|
{% if groups or request.GET.q %}
|
||||||
<div class=" d-flex flex-column flex-md-row justify-content-between align-items-md-center p-4">
|
<div class="d-flex align-items-center justify-content-between mb-4">
|
||||||
<h5 class="card-title mb-2 mb-md-0 me-md-4 fw-bold">
|
<h1 class="h3 mb-0 text-muted d-flex align-items-center">
|
||||||
<i class="fa-solid fa-user-group fs-3 me-1 "></i>{% trans "Groups" %}
|
<i class="fa-solid fa-user-group me-3 fs-2"></i>
|
||||||
</h5>
|
{% trans "Groups" %}
|
||||||
<div class="d-flex gap-2">
|
</h1>
|
||||||
|
<div class="d-flex">
|
||||||
<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 me-2 shadow-sm">
|
||||||
<i class="fa-solid fa-user-group fs-9 me-1"></i>
|
<i class="fa-solid fa-plus me-2"></i>{% trans "Add New Group" %}
|
||||||
<span class="fas fa-plus me-2"></span>{% trans "Add Group" %}
|
|
||||||
</a>
|
</a>
|
||||||
<a href="{% url 'user_list' request.dealer.slug %}"
|
<a href="{% url 'user_list' request.dealer.slug %}"
|
||||||
class="btn btn-phoenix-secondary">
|
class="btn btn-phoenix-secondary shadow-sm">
|
||||||
<span class="fas fas fa-arrow-left me-2"></span>{% trans "Back to Staffs" %}
|
<i class="fa-solid fa-arrow-left me-2"></i>{% trans "Back to Staffs" %}
|
||||||
</a>
|
</a>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div class="card border-0 rounded-4 animate__animated animate__fadeInUp">
|
<div class="card border-0 shadow-sm rounded-lg">
|
||||||
|
|
||||||
<div class="card-body p-0">
|
<div class="card-body p-0">
|
||||||
<div class="table-responsive scrollbar mx-n1 px-1 mt-3">
|
<div class="table-responsive">
|
||||||
<table class="table align-items-center table-hover mb-0">
|
<table class="table table-hover table-borderless mb-0">
|
||||||
<thead>
|
<thead class="bg-light">
|
||||||
<tr class="">
|
<tr>
|
||||||
<th scope="col" class=" text-uppercase fw-bold ps-4">{% trans 'name'|capfirst %}</th>
|
<th class="py-3 px-4">{% trans 'Name'|capfirst %}</th>
|
||||||
<th scope="col" class="text-uppercase fw-bold">{% trans 'total Users'|capfirst %}</th>
|
<th class="py-3 px-4">{% trans 'Total Users'|capfirst %}</th>
|
||||||
<th scope="col" class="text-uppercase fw-bold">{% trans 'total permission'|capfirst %}</th>
|
<th class="py-3 px-4">{% trans 'Total Permissions'|capfirst %}</th>
|
||||||
<th scope="col"
|
<th class="py-3 px-4 text-center">{% trans 'Actions'|capfirst %}</th>
|
||||||
class="text-uppercase fw-bold text-end pe-4">
|
|
||||||
{% trans 'actions'|capfirst %}
|
|
||||||
</th>
|
|
||||||
</tr>
|
</tr>
|
||||||
</thead>
|
</thead>
|
||||||
<tbody>
|
<tbody>
|
||||||
{% for group in groups %}
|
{% for group in groups %}
|
||||||
<tr>
|
<tr class="border-bottom">
|
||||||
<td class="align-middle white-space-nowrap ps-4">{{ group.name }}</td>
|
<td class="align-middle py-3 px-4">{{ group.name }}</td>
|
||||||
<td class="align-middle white-space-nowrap">
|
<td class="align-middle py-3 px-4 text-muted">
|
||||||
<i class="fa-solid fa-users me-1"></i> {{ group.users.count }}
|
<i class="fa-solid fa-users me-1"></i> {{ group.users.count }}
|
||||||
</td>
|
</td>
|
||||||
<td class="align-middle white-space-nowrap">
|
<td class="align-middle py-3 px-4 text-muted">
|
||||||
<i class="fa-solid fa-unlock me-1"></i> {{ group.permissions.count }}
|
<i class="fa-solid fa-unlock me-1"></i> {{ group.permissions.count }}
|
||||||
</td>
|
</td>
|
||||||
<td class="align-middle white-space-nowrap text-end pe-4">
|
<td class="align-middle py-3 px-4 text-center">
|
||||||
<a class="btn btn-phoenix-secondary btn-sm"
|
<a class="btn btn-sm btn-phoenix-secondary"
|
||||||
href="{% url 'group_detail' request.dealer.slug group.id %}">
|
href="{% url 'group_detail' request.dealer.slug group.id %}">
|
||||||
<i class="fa-solid fa-eye me-1"></i>
|
<i class="fa-solid fa-eye me-1"></i>{% trans 'View Permissions' %}
|
||||||
{% trans 'view'|capfirst %}
|
|
||||||
</a>
|
</a>
|
||||||
</td>
|
</td>
|
||||||
</tr>
|
</tr>
|
||||||
@ -65,9 +61,7 @@
|
|||||||
</div>
|
</div>
|
||||||
</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="d-flex justify-content-center mt-4">{% include 'partials/pagination.html' %}</div>
|
||||||
<div class="d-flex justify-content-end">{% include 'partials/pagination.html' %}</div>
|
|
||||||
</div>
|
|
||||||
{% endif %}
|
{% endif %}
|
||||||
</div>
|
</div>
|
||||||
{% else %}
|
{% else %}
|
||||||
@ -75,5 +69,4 @@
|
|||||||
{% 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>
|
|
||||||
{% endblock %}
|
{% endblock %}
|
||||||
@ -194,7 +194,7 @@
|
|||||||
data-bs-parent="#navbarVerticalCollapse"
|
data-bs-parent="#navbarVerticalCollapse"
|
||||||
id="nv-sales">
|
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 %}
|
{% comment %} {% if perms.django_ledger.add_estimatemodel %}
|
||||||
<li class="nav-item">
|
<li class="nav-item">
|
||||||
<a
|
<a
|
||||||
class="nav-link"
|
class="nav-link"
|
||||||
@ -204,7 +204,7 @@
|
|||||||
</div>
|
</div>
|
||||||
</a>
|
</a>
|
||||||
</li>
|
</li>
|
||||||
{% endif %}
|
{% endif %} {% endcomment %}
|
||||||
{% if perms.django_ledger.view_estimatemodel %}
|
{% if perms.django_ledger.view_estimatemodel %}
|
||||||
<li class="nav-item">
|
<li class="nav-item">
|
||||||
<a class="nav-link" href="{% url 'estimate_list' request.dealer.slug %}">
|
<a class="nav-link" href="{% url 'estimate_list' request.dealer.slug %}">
|
||||||
@ -438,10 +438,10 @@
|
|||||||
|
|
||||||
</ul>
|
</ul>
|
||||||
</div> {% endcomment %}
|
</div> {% endcomment %}
|
||||||
{% endif %}
|
{% endif %}
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div class="navbar-vertical-footer">
|
<div class="navbar-vertical-footer">
|
||||||
<a class="nav-link ps-2" href="{% if request.is_dealer%}{% url 'ticket_list' request.dealer.slug %} {% else %}#{%endif%}">
|
<a class="nav-link ps-2" href="{% if request.is_dealer%}{% url 'ticket_list' request.dealer.slug %} {% else %}#{%endif%}">
|
||||||
<div class="d-flex align-items-center">
|
<div class="d-flex align-items-center">
|
||||||
{% if user.is_authenticated%}
|
{% if user.is_authenticated%}
|
||||||
@ -452,7 +452,7 @@
|
|||||||
{% endif %}
|
{% endif %}
|
||||||
</div>
|
</div>
|
||||||
</a>
|
</a>
|
||||||
</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">
|
||||||
@ -677,8 +677,8 @@
|
|||||||
{% endif %}
|
{% endif %}
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</li>
|
</li>
|
||||||
</ul>
|
</ul>
|
||||||
</div>
|
</div>
|
||||||
</nav>
|
</nav>
|
||||||
|
|||||||
@ -2,7 +2,7 @@
|
|||||||
{% load i18n %}
|
{% load i18n %}
|
||||||
{% block title %}
|
{% block title %}
|
||||||
{% trans 'Add Colors' %} {% endblock %}
|
{% trans 'Add Colors' %} {% endblock %}
|
||||||
{% block content %}
|
{% 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">
|
||||||
@ -117,4 +117,4 @@
|
|||||||
width: 100%;
|
width: 100%;
|
||||||
}
|
}
|
||||||
</style>
|
</style>
|
||||||
{% endblock %}
|
{% endblock %}
|
||||||
|
|||||||
@ -75,7 +75,7 @@
|
|||||||
</div>
|
</div>
|
||||||
{% endif %}
|
{% endif %}
|
||||||
<!-- Main row -->
|
<!-- Main row -->
|
||||||
{% if car.ready %}
|
{% if car.ready and car.status != 'sold' %}
|
||||||
<div class="d-flex align-items-center gap-2 mb-2">
|
<div class="d-flex align-items-center gap-2 mb-2">
|
||||||
<button type="button"
|
<button type="button"
|
||||||
class="btn btn-sm btn-phoenix-success"
|
class="btn btn-sm btn-phoenix-success"
|
||||||
@ -91,8 +91,8 @@
|
|||||||
<span class="ms-2 badge rounded-pill text-bg-light">{{ active_estimates|length }}</span>
|
<span class="ms-2 badge rounded-pill text-bg-light">{{ active_estimates|length }}</span>
|
||||||
</button>
|
</button>
|
||||||
{% endif %}
|
{% endif %}
|
||||||
</div>
|
</div>
|
||||||
{% endif %}
|
{% endif %}
|
||||||
{% 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">
|
||||||
@ -542,7 +542,7 @@
|
|||||||
data-bs-dismiss="modal"
|
data-bs-dismiss="modal"
|
||||||
aria-label="Close"></button>
|
aria-label="Close"></button>
|
||||||
</div>
|
</div>
|
||||||
<div id="estimateModalBody" class="main-modal-body" style="padding: 20px;">
|
<div id="estimateModalBody" style="padding: 20px;">
|
||||||
<form action="{% url 'create_estimate_for_car' request.dealer.slug car.slug %}" method="post">
|
<form action="{% url 'create_estimate_for_car' request.dealer.slug car.slug %}" method="post">
|
||||||
{% csrf_token %}
|
{% csrf_token %}
|
||||||
{{estimate_form|crispy}}
|
{{estimate_form|crispy}}
|
||||||
@ -660,7 +660,7 @@
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
{% endblock %}
|
{% endblock %}
|
||||||
{% block customJS %}
|
{% block customJS %}
|
||||||
<script>
|
<script>
|
||||||
@ -772,15 +772,15 @@
|
|||||||
|
|
||||||
document.addEventListener('htmx:afterRequest', e => {
|
document.addEventListener('htmx:afterRequest', e => {
|
||||||
console.log('PUT Success!')
|
console.log('PUT Success!')
|
||||||
});
|
});
|
||||||
document.addEventListener('htmx:beforeSwap', e => {
|
document.addEventListener('htmx:beforeSwap', e => {
|
||||||
console.log('Before Swap!')
|
console.log('Before Swap!')
|
||||||
});
|
});
|
||||||
document.addEventListener('htmx:swapError', e => {
|
document.addEventListener('htmx:swapError', e => {
|
||||||
console.log('Swap Error!')
|
console.log('Swap Error!')
|
||||||
});
|
});
|
||||||
document.addEventListener('htmx:afterSwap', e => {
|
document.addEventListener('htmx:afterSwap', e => {
|
||||||
console.log('After Swap!')
|
console.log('After Swap!')
|
||||||
});
|
});
|
||||||
</script>
|
</script>
|
||||||
{% endblock %}
|
{% endblock %}
|
||||||
|
|||||||
@ -303,10 +303,10 @@
|
|||||||
</div>
|
</div>
|
||||||
{% endblock %}
|
{% endblock %}
|
||||||
{% block customJS%}
|
{% block customJS%}
|
||||||
<script>
|
<script>
|
||||||
function toggle_filter() {
|
function toggle_filter() {
|
||||||
const filterContainer = document.querySelector('.filter');
|
const filterContainer = document.querySelector('.filter');
|
||||||
filterContainer.classList.toggle('d-none');
|
filterContainer.classList.toggle('d-none');
|
||||||
}
|
}
|
||||||
</script>
|
</script>
|
||||||
{% endblock %}
|
{% endblock %}
|
||||||
@ -45,7 +45,7 @@
|
|||||||
#065535
|
#065535
|
||||||
#000000
|
#000000
|
||||||
#133337
|
#133337
|
||||||
#ffc0cb</a>
|
#ffc0cb</a>
|
||||||
<br>
|
<br>
|
||||||
<span class="glyphicon glyphicon-star"></span>707
|
<span class="glyphicon glyphicon-star"></span>707
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@ -355,4 +355,4 @@
|
|||||||
});
|
});
|
||||||
</script>
|
</script>
|
||||||
</body>
|
</body>
|
||||||
</html>
|
</html>
|
||||||
|
|||||||
@ -7,14 +7,14 @@
|
|||||||
|
|
||||||
{% block content %}
|
{% block content %}
|
||||||
|
|
||||||
<section class="hero-section text-center py-5">
|
<section class="hero-section text-center py-5">
|
||||||
<div class="container">
|
<div class="container">
|
||||||
<h1 class="display-4 fw-bold text-primary">{{ expense.name|title }}</h1>
|
<h1 class="display-4 fw-bold text-primary">{{ expense.name|title }}</h1>
|
||||||
<p class="lead text-muted">{% trans "Comprehensive details for your expense." %}</p>
|
<p class="lead text-muted">{% trans "Comprehensive details for your expense." %}</p>
|
||||||
</div>
|
</div>
|
||||||
</section>
|
</section>
|
||||||
|
|
||||||
<div class="container my-5">
|
<div class="container my-5">
|
||||||
<div class="row justify-content-center">
|
<div class="row justify-content-center">
|
||||||
<div class="col-md-11">
|
<div class="col-md-11">
|
||||||
<div class="card shadow-lg border-0 rounded-4">
|
<div class="card shadow-lg border-0 rounded-4">
|
||||||
@ -93,5 +93,5 @@
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
{% endblock %}
|
{% endblock %}
|
||||||
|
|||||||
@ -7,14 +7,14 @@
|
|||||||
|
|
||||||
{% block content %}
|
{% block content %}
|
||||||
|
|
||||||
<section class="hero-section text-center py-5">
|
<section class="hero-section text-center py-5">
|
||||||
<div class="container">
|
<div class="container">
|
||||||
<h1 class="display-5 fw-bold">{{ service.name|title }}</h1>
|
<h1 class="display-5 fw-bold">{{ service.name|title }}</h1>
|
||||||
<p class=" mt-3">{{ service.description|default:"No description provided." }}</p>
|
<p class=" mt-3">{{ service.description|default:"No description provided." }}</p>
|
||||||
</div>
|
</div>
|
||||||
</section>
|
</section>
|
||||||
|
|
||||||
<section class="py-5">
|
<section class="py-5">
|
||||||
<div class="container">
|
<div class="container">
|
||||||
|
|
||||||
<div class="card mb-5 p-4 p-lg-5">
|
<div class="card mb-5 p-4 p-lg-5">
|
||||||
@ -61,6 +61,6 @@
|
|||||||
</div>
|
</div>
|
||||||
|
|
||||||
</div>
|
</div>
|
||||||
</section>
|
</section>
|
||||||
|
|
||||||
{% endblock %}
|
{% endblock %}
|
||||||
|
|||||||
@ -78,4 +78,4 @@
|
|||||||
{% url 'bank_account_create' request.dealer.slug as create_bank_account_url %}
|
{% url 'bank_account_create' request.dealer.slug as create_bank_account_url %}
|
||||||
{% include "empty-illustration-page.html" with value="bank account" url=create_bank_account_url %}
|
{% include "empty-illustration-page.html" with value="bank account" url=create_bank_account_url %}
|
||||||
{% endif %}
|
{% endif %}
|
||||||
{% endblock %}
|
{% endblock %}
|
||||||
|
|||||||
@ -49,6 +49,8 @@
|
|||||||
<span class="badge badge-phoenix badge-phoenix-success">
|
<span class="badge badge-phoenix badge-phoenix-success">
|
||||||
{% elif bill.is_canceled %}
|
{% elif bill.is_canceled %}
|
||||||
<span class="badge badge-phoenix badge-phoenix-danger">
|
<span class="badge badge-phoenix badge-phoenix-danger">
|
||||||
|
{% elif bill.is_void %}
|
||||||
|
<span class="badge badge-phoenix badge-phoenix-secondary">
|
||||||
{% endif %}
|
{% endif %}
|
||||||
{{ bill.bill_status }}
|
{{ bill.bill_status }}
|
||||||
</span>
|
</span>
|
||||||
@ -92,4 +94,4 @@
|
|||||||
{% url "bill-create" request.dealer.slug request.entity.slug as create_bill_url %}
|
{% url "bill-create" request.dealer.slug request.entity.slug as create_bill_url %}
|
||||||
{% include "empty-illustration-page.html" with value="bill" url=create_bill_url %}
|
{% include "empty-illustration-page.html" with value="bill" url=create_bill_url %}
|
||||||
{% endif %}
|
{% endif %}
|
||||||
{% endblock %}
|
{% endblock %}
|
||||||
|
|||||||
@ -156,4 +156,4 @@
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<!--test-->
|
<!--test-->
|
||||||
{% endblock %}
|
{% endblock %}
|
||||||
|
|||||||
@ -56,4 +56,4 @@
|
|||||||
</div>
|
</div>
|
||||||
{% endif %}
|
{% endif %}
|
||||||
</div>
|
</div>
|
||||||
{% endblock %}
|
{% endblock %}
|
||||||
|
|||||||
@ -293,7 +293,7 @@
|
|||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
|
||||||
<script>
|
<script>
|
||||||
document.addEventListener('DOMContentLoaded', () => {
|
document.addEventListener('DOMContentLoaded', () => {
|
||||||
const makeSelect = document.getElementById('make-select');
|
const makeSelect = document.getElementById('make-select');
|
||||||
const modelSelect = document.getElementById('model-select');
|
const modelSelect = document.getElementById('model-select');
|
||||||
@ -357,6 +357,6 @@
|
|||||||
// Initial call to populate other dropdowns on page load
|
// Initial call to populate other dropdowns on page load
|
||||||
fetchAndUpdateDropdowns();
|
fetchAndUpdateDropdowns();
|
||||||
});
|
});
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
{% endblock %}
|
{% endblock %}
|
||||||
@ -13,7 +13,7 @@
|
|||||||
font-size: 0.50rem">{{ notifications_.count }}</span>
|
font-size: 0.50rem">{{ notifications_.count }}</span>
|
||||||
{% endif %}
|
{% endif %}
|
||||||
</div>
|
</div>
|
||||||
<a class="nav-link"
|
<a class="nav-link"
|
||||||
href="{% url 'fetch_notifications' %}"
|
href="{% url 'fetch_notifications' %}"
|
||||||
hx-get="{% url 'fetch_notifications' %}"
|
hx-get="{% url 'fetch_notifications' %}"
|
||||||
hx-swap="innerHTML"
|
hx-swap="innerHTML"
|
||||||
|
|||||||
@ -87,9 +87,13 @@
|
|||||||
didOpen: (toast) => {
|
didOpen: (toast) => {
|
||||||
toast.onmouseenter = Swal.stopTimer;
|
toast.onmouseenter = Swal.stopTimer;
|
||||||
toast.onmouseleave = Swal.resumeTimer;
|
toast.onmouseleave = Swal.resumeTimer;
|
||||||
} });
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
{% with last_notif=notifications_|last %}
|
||||||
|
let lastNotificationId = {{ last_notif.id|default:0 }};
|
||||||
|
{% endwith %}
|
||||||
|
|
||||||
let lastNotificationId = {{ notifications_.last.id|default:0 }};
|
|
||||||
let seenNotificationIds = new Set();
|
let seenNotificationIds = new Set();
|
||||||
let counter = document.getElementById('notification-counter');
|
let counter = document.getElementById('notification-counter');
|
||||||
let notificationsContainer = document.getElementById('notifications-container');
|
let notificationsContainer = document.getElementById('notifications-container');
|
||||||
@ -100,7 +104,6 @@
|
|||||||
let initialUnreadCount = {{ notifications_.count|default:0 }};
|
let initialUnreadCount = {{ notifications_.count|default:0 }};
|
||||||
updateCounter(initialUnreadCount);
|
updateCounter(initialUnreadCount);
|
||||||
|
|
||||||
|
|
||||||
fetchInitialNotifications();
|
fetchInitialNotifications();
|
||||||
|
|
||||||
function fetchInitialNotifications() {
|
function fetchInitialNotifications() {
|
||||||
@ -108,29 +111,22 @@
|
|||||||
.then(response => response.json())
|
.then(response => response.json())
|
||||||
.then(data => {
|
.then(data => {
|
||||||
if (data.notifications && data.notifications.length > 0) {
|
if (data.notifications && data.notifications.length > 0) {
|
||||||
|
|
||||||
lastNotificationId = data.notifications[0].id;
|
lastNotificationId = data.notifications[0].id;
|
||||||
|
|
||||||
seenNotificationIds = new Set();
|
seenNotificationIds = new Set();
|
||||||
|
|
||||||
let unreadCount = 0;
|
let unreadCount = 0;
|
||||||
|
|
||||||
data.notifications.forEach(notification => {
|
data.notifications.forEach(notification => {
|
||||||
seenNotificationIds.add(notification.id);
|
seenNotificationIds.add(notification.id);
|
||||||
if (!notification.is_read) {
|
if (!notification.is_read) unreadCount++;
|
||||||
unreadCount++;
|
|
||||||
}
|
|
||||||
});
|
});
|
||||||
|
|
||||||
renderNotifications(data.notifications);
|
renderNotifications(data.notifications);
|
||||||
|
|
||||||
updateCounter(unreadCount);
|
updateCounter(unreadCount);
|
||||||
|
}
|
||||||
|
// Always connect SSE after initial load
|
||||||
setTimeout(() => {
|
setTimeout(() => {
|
||||||
connectSSE();
|
connectSSE();
|
||||||
}, 5000);
|
}, 1000);
|
||||||
}
|
|
||||||
})
|
})
|
||||||
.catch(error => {
|
.catch(error => {
|
||||||
console.error('Error fetching initial notifications:', error);
|
console.error('Error fetching initial notifications:', error);
|
||||||
@ -143,12 +139,12 @@
|
|||||||
eventSource.close();
|
eventSource.close();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// ✅ FIXED URL HERE
|
||||||
eventSource = new EventSource("/sse/notifications/?last_id=" + lastNotificationId);
|
eventSource = new EventSource("/sse/notifications/?last_id=" + lastNotificationId);
|
||||||
|
|
||||||
eventSource.addEventListener('notification', function(e) {
|
eventSource.addEventListener('notification', function(e) {
|
||||||
try {
|
try {
|
||||||
const data = JSON.parse(e.data);
|
const data = JSON.parse(e.data);
|
||||||
|
|
||||||
if (seenNotificationIds.has(data.id)) return;
|
if (seenNotificationIds.has(data.id)) return;
|
||||||
seenNotificationIds.add(data.id);
|
seenNotificationIds.add(data.id);
|
||||||
|
|
||||||
@ -158,6 +154,11 @@
|
|||||||
|
|
||||||
updateCounter('increment');
|
updateCounter('increment');
|
||||||
|
|
||||||
|
if (!notificationsContainer) {
|
||||||
|
console.warn("Notification container missing, can't render SSE event");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
const notificationElement = createNotificationElement(data);
|
const notificationElement = createNotificationElement(data);
|
||||||
notificationsContainer.insertAdjacentHTML('afterbegin', notificationElement);
|
notificationsContainer.insertAdjacentHTML('afterbegin', notificationElement);
|
||||||
|
|
||||||
@ -168,7 +169,7 @@
|
|||||||
|
|
||||||
Toast.fire({
|
Toast.fire({
|
||||||
icon: 'info',
|
icon: 'info',
|
||||||
html:`${data.message}`
|
html: `${data.message}`
|
||||||
});
|
});
|
||||||
|
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
@ -237,6 +238,8 @@
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (!counter) return;
|
||||||
|
|
||||||
let currentCount = parseInt(counter.textContent) || 0;
|
let currentCount = parseInt(counter.textContent) || 0;
|
||||||
|
|
||||||
if (action === 'increment') {
|
if (action === 'increment') {
|
||||||
@ -294,7 +297,7 @@
|
|||||||
notificationCard.closest('.notification-card').classList.add('fade-out');
|
notificationCard.closest('.notification-card').classList.add('fade-out');
|
||||||
setTimeout(() => {
|
setTimeout(() => {
|
||||||
notificationCard.closest('.notification-card').remove();
|
notificationCard.closest('.notification-card').remove();
|
||||||
}, 200);
|
}, 1000);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|||||||
@ -125,7 +125,7 @@
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<tr class="hover-actions-trigger btn-reveal-trigger position-static">
|
<tr class="hover-actions-trigger btn-reveal-trigger position-static">
|
||||||
<td class="name align-middle white-space-nowrap ps-0 px-1">
|
<td class="name align-middle white-space-nowrap px-1">
|
||||||
<div class="d-flex align-items-center">
|
<div class="d-flex align-items-center">
|
||||||
<div>
|
<div>
|
||||||
<a class="fs-8 fw-bold"
|
<a class="fs-8 fw-bold"
|
||||||
@ -144,7 +144,7 @@
|
|||||||
<td class="date align-middle white-space-nowrap text-body-tertiary text-opacity-85 ps-4 text-body-tertiary">
|
<td class="date align-middle white-space-nowrap text-body-tertiary text-opacity-85 ps-4 text-body-tertiary">
|
||||||
{{ org.created|date }}
|
{{ org.created|date }}
|
||||||
</td>
|
</td>
|
||||||
<td class="align-middle white-space-nowrap text-end pe-0 ps-4">
|
<td class="align-middle white-space-nowrap text-end pe-2 ps-4">
|
||||||
{% if perms.inventory.change_organization or perms.inventory.delete_organization %}
|
{% if perms.inventory.change_organization or perms.inventory.delete_organization %}
|
||||||
<div class="btn-reveal-trigger position-static">
|
<div class="btn-reveal-trigger position-static">
|
||||||
<button class="btn btn-sm dropdown-toggle dropdown-caret-none transition-none btn-reveal fs-10"
|
<button class="btn btn-sm dropdown-toggle dropdown-caret-none transition-none btn-reveal fs-10"
|
||||||
|
|||||||
@ -5,7 +5,7 @@
|
|||||||
{% 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">
|
||||||
@ -19,6 +19,7 @@
|
|||||||
method="post" class="needs-validation" novalidate>
|
method="post" class="needs-validation" novalidate>
|
||||||
{% 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">
|
||||||
@ -27,11 +28,11 @@
|
|||||||
</button>
|
</button>
|
||||||
<a href="{% url 'dealer_detail' request.dealer.slug %}"
|
<a href="{% url 'dealer_detail' request.dealer.slug %}"
|
||||||
class="btn btn-lg btn-phoenix-secondary md-me-2"
|
class="btn btn-lg btn-phoenix-secondary md-me-2"
|
||||||
type="submit"><i class="fa-solid fa-ban me-1"></i>{{ _("Cancel") }}</a>
|
type="submit"><i class="fa-solid fa-ban me-1"></i>{{ _("Go Back") }}</a>
|
||||||
</div>
|
</div>
|
||||||
</form>
|
</form>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</main>
|
</main>
|
||||||
{% endblock %}
|
{% endblock %}
|
||||||
@ -12,4 +12,4 @@
|
|||||||
</form>
|
</form>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
{% endblock %}
|
{% endblock %}
|
||||||
|
|||||||
@ -54,4 +54,4 @@
|
|||||||
{% endblocktrans %}
|
{% endblocktrans %}
|
||||||
{% endif %}
|
{% endif %}
|
||||||
</form>
|
</form>
|
||||||
{% endblock %}
|
{% endblock %}
|
||||||
|
|||||||
@ -12,7 +12,7 @@
|
|||||||
</script>
|
</script>
|
||||||
{% endblock %}
|
{% endblock %}
|
||||||
{% block body %}
|
{% block body %}
|
||||||
<div class="max-w-4xl mx-auto">
|
<div class="max-w-4xl mx-auto">
|
||||||
<div class="flex items-center justify-between mb-6">
|
<div class="flex items-center justify-between mb-6">
|
||||||
<h1 class="text-3xl font-bold text-gray-800">
|
<h1 class="text-3xl font-bold text-gray-800">
|
||||||
{% blocktrans with object.id as order_id %}Order #{{ order_id }}{% endblocktrans %}
|
{% blocktrans with object.id as order_id %}Order #{{ order_id }}{% endblocktrans %}
|
||||||
@ -60,15 +60,15 @@
|
|||||||
<h2 class="text-xl font-semibold text-gray-800 mb-4">
|
<h2 class="text-xl font-semibold text-gray-800 mb-4">
|
||||||
<i class="fas fa-credit-card text-gray-400 mr-2"></i> {% trans "Payment" %}
|
<i class="fas fa-credit-card text-gray-400 mr-2"></i> {% trans "Payment" %}
|
||||||
</h2>
|
</h2>
|
||||||
|
|
||||||
{% if object.completed %}
|
{% if object.completed %}
|
||||||
<div class="bg-green-50 text-green-700 px-4 py-3 rounded-lg flex items-center">
|
<div class="bg-green-50 text-green-700 px-4 py-3 rounded-lg flex items-center">
|
||||||
<i class="fas fa-check-circle mr-3 text-lg"></i>
|
<i class="fas fa-check-circle mr-3 text-lg"></i>
|
||||||
<p class="text-sm font-medium">
|
<p class="text-sm font-medium">
|
||||||
{% blocktrans with object.completed as completed %}
|
{% trans "Payment completed on : "%}{{object.completed}}
|
||||||
Payment completed on: {{ completed|date:"F j, Y" }}
|
|
||||||
{% endblocktrans %}
|
|
||||||
</p>
|
</p>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
{% else %}
|
{% else %}
|
||||||
{% if object.is_ready_for_payment %}
|
{% if object.is_ready_for_payment %}
|
||||||
<p class="text-gray-600 mb-4">
|
<p class="text-gray-600 mb-4">
|
||||||
@ -104,5 +104,5 @@
|
|||||||
{% endif %}
|
{% endif %}
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
{% endblock %}
|
{% endblock %}
|
||||||
@ -19,7 +19,7 @@
|
|||||||
const modalBody = document.getElementById('POModalBody')
|
const modalBody = document.getElementById('POModalBody')
|
||||||
modalBody.innerHTML = `
|
modalBody.innerHTML = `
|
||||||
<div class="d-flex justify-content-center gap-3 py-3">
|
<div class="d-flex justify-content-center gap-3 py-3">
|
||||||
<a hx-boost="true" class="btn btn-phoenix-primary px-4" href="${actionUrl}">
|
<a hx-boost="true" class="btn btn-phoenix-danger px-4" href="${actionUrl}">
|
||||||
<i class="fas fa-check-circle me-2"></i>${buttonText}
|
<i class="fas fa-check-circle me-2"></i>${buttonText}
|
||||||
</a>
|
</a>
|
||||||
<button class="btn btn-phoenix-secondary" data-bs-dismiss="modal">
|
<button class="btn btn-phoenix-secondary" data-bs-dismiss="modal">
|
||||||
@ -45,7 +45,7 @@
|
|||||||
href="{% url 'purchase_order_list' request.dealer.slug request.dealer.entity.slug %}"
|
href="{% url 'purchase_order_list' request.dealer.slug request.dealer.entity.slug %}"
|
||||||
title="Click to view the complete list of Purchase Orders"
|
title="Click to view the complete list of Purchase Orders"
|
||||||
role="button">
|
role="button">
|
||||||
<i class="fas fa-list me-2"></i>{% trans 'PO List' %}
|
<i class="fas fa-list me-2"></i>{% trans 'Purchase Order List' %}
|
||||||
</a>
|
</a>
|
||||||
</p>
|
</p>
|
||||||
</div>
|
</div>
|
||||||
@ -135,18 +135,34 @@
|
|||||||
{% endif %}
|
{% endif %}
|
||||||
{% if po_model.can_delete %}
|
{% if po_model.can_delete %}
|
||||||
{% if perms.django_ledger.delete_purchaseordermodel %}
|
{% if perms.django_ledger.delete_purchaseordermodel %}
|
||||||
<button class="btn btn-phoenix-danger"
|
<button type="button" class="btn btn-phoenix-danger" data-bs-toggle="modal" data-bs-target="#exampleModal">
|
||||||
onclick="showPOModal('Delete PO', '{% url 'po-delete' request.dealer.slug entity_slug po_model.pk %}', 'Delete')">
|
{% trans "Delete" %}
|
||||||
<i class="fas fa-trash me-2"></i>{% trans 'Delete' %}
|
|
||||||
</button>
|
</button>
|
||||||
|
|
||||||
|
<div class="modal fade" id="exampleModal" tabindex="-1" aria-labelledby="exampleModalLabel" aria-hidden="true">
|
||||||
|
<div class="modal-dialog">
|
||||||
|
<div class="modal-content">
|
||||||
|
<div class="modal-header">
|
||||||
|
<h5 class="modal-title" id="exampleModalLabel">{% trans "Purchase Order Delete" %}</h5>
|
||||||
|
<button type="button" class="btn-close" data-bs-dismiss="modal" aria-label="Close"></button>
|
||||||
|
</div>
|
||||||
|
<div class="modal-body">
|
||||||
|
{% include 'purchase_orders/po_confirm_delete.html' %}
|
||||||
|
</div>
|
||||||
|
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
{% endif %}
|
{% endif %}
|
||||||
{% endif %}
|
{% endif %}
|
||||||
{% if po_model.can_void %}
|
{% comment %} TODO: upgrade djnago ledger or replace core functionality {% endcomment %}
|
||||||
|
{% comment %} issue with django ledger base functionality when marking as void throughs error , will be fix in future {% endcomment %}
|
||||||
|
{% comment %} {% if po_model.can_void %}
|
||||||
<button class="btn btn-phoenix-danger"
|
<button class="btn btn-phoenix-danger"
|
||||||
onclick="showPOModal('Void PO', '{% url 'po-action-mark-as-void' request.dealer.slug entity_slug po_model.pk %}', 'Mark As Void')">
|
onclick="showPOModal('{% trans "Void Purchase Order" %}', '{% url 'po-action-mark-as-void' request.dealer.slug entity_slug po_model.pk %}', 'Mark As Void')">
|
||||||
<i class="fas fa-times-circle me-2"></i>{% trans 'Void' %}
|
<i class="fas fa-times-circle me-2"></i>{% trans 'Void' %}
|
||||||
</button>
|
</button>
|
||||||
{% endif %}
|
{% endif %} {% endcomment %}
|
||||||
{% if po_model.can_cancel %}
|
{% if po_model.can_cancel %}
|
||||||
<button class="btn btn-phoenix-secondary"
|
<button class="btn btn-phoenix-secondary"
|
||||||
onclick="showPOModal('Cancel PO', '{% url 'po-action-mark-as-canceled' request.dealer.slug entity_slug po_model.pk %}', 'Mark As Cancelled')">
|
onclick="showPOModal('Cancel PO', '{% url 'po-action-mark-as-canceled' request.dealer.slug entity_slug po_model.pk %}', 'Mark As Cancelled')">
|
||||||
|
|||||||
@ -32,8 +32,8 @@
|
|||||||
</button>
|
</button>
|
||||||
{% endif %}
|
{% endif %}
|
||||||
</th>
|
</th>
|
||||||
<th>{% trans 'Unit Cost' %}</th>
|
|
||||||
<th>{% trans 'Quantity' %}</th>
|
<th>{% trans 'Quantity' %}</th>
|
||||||
|
<th>{% trans 'Unit Cost' %}</th>
|
||||||
<th>{% trans 'Unit' %}</th>
|
<th>{% trans 'Unit' %}</th>
|
||||||
<th class="text-end">{% trans 'Amount' %}</th>
|
<th class="text-end">{% trans 'Amount' %}</th>
|
||||||
<th>{% trans 'Status' %}</th>
|
<th>{% trans 'Status' %}</th>
|
||||||
@ -52,8 +52,8 @@
|
|||||||
{{ f.item_model|add_class:"form-control" }}
|
{{ f.item_model|add_class:"form-control" }}
|
||||||
{% if f.errors %}<div class="text-danger small">{{ f.errors }}</div>{% endif %}
|
{% if f.errors %}<div class="text-danger small">{{ f.errors }}</div>{% endif %}
|
||||||
</td>
|
</td>
|
||||||
<td id="{{ f.instance.html_id_unit_cost }}">{{ f.po_unit_cost|add_class:"form-control" }}</td>
|
|
||||||
<td id="{{ f.instance.html_id_quantity }}">{{ f.po_quantity|add_class:"form-control" }}</td>
|
<td id="{{ f.instance.html_id_quantity }}">{{ f.po_quantity|add_class:"form-control" }}</td>
|
||||||
|
<td id="{{ f.instance.html_id_unit_cost }}">{{ f.po_unit_cost|add_class:"form-control" }}</td>
|
||||||
<td>{{ f.entity_unit|add_class:"form-control" }}</td>
|
<td>{{ f.entity_unit|add_class:"form-control" }}</td>
|
||||||
<td class="text-end" id="{{ f.instance.html_id_total_amount }}">
|
<td class="text-end" id="{{ f.instance.html_id_total_amount }}">
|
||||||
<span class="currency">{{ CURRENCY }}</span>{{ f.instance.po_total_amount | currency_format }}
|
<span class="currency">{{ CURRENCY }}</span>{{ f.instance.po_total_amount | currency_format }}
|
||||||
|
|||||||
@ -1,17 +1,12 @@
|
|||||||
<!-- templates/purchase_orders/po_confirm_delete.html -->
|
{% load i18n %}
|
||||||
{% extends "base.html" %}
|
<form action="{% url 'po-delete' dealer_slug entity_slug po_model.pk %}" method="post">
|
||||||
{% block title %}{% trans "Confirm Delete"%} - {{ block.super }}{% endblock %}
|
|
||||||
{% block content %}
|
|
||||||
<div class="container mt-4">
|
|
||||||
<h2>{% trans "Confirm Deletion" %}</h2>
|
|
||||||
<p>
|
|
||||||
{% trans "Are you sure you want to delete the Purchase Order" %} <strong>"{{ po.po_number }}"</strong>?
|
|
||||||
</p>
|
|
||||||
<form method="post">
|
|
||||||
{% csrf_token %}
|
{% csrf_token %}
|
||||||
|
<p>
|
||||||
|
{% blocktrans with po_number=po_model.po_number %}
|
||||||
|
Are you sure you want to delete #{{ po_number }}?
|
||||||
|
{% endblocktrans %}
|
||||||
|
</p>
|
||||||
<button type="submit" class="btn btn-phoenix-danger">{% trans "Yes, Delete" %}</button>
|
<button type="submit" class="btn btn-phoenix-danger">{% trans "Yes, Delete" %}</button>
|
||||||
<a href="{% url 'purchase_order_detail' po.id %}"
|
<a href="{% url 'purchase_order_detail' request.dealer.slug request.dealer.entity.slug po_model.pk %}"
|
||||||
class="btn btn-phoenix-secondary">{% trans "Cancel" %}</a>
|
class="btn btn-phoenix-secondary">{% trans "No" %}</a>
|
||||||
</form>
|
</form>
|
||||||
</div>
|
|
||||||
{% endblock %}
|
|
||||||
@ -1,35 +0,0 @@
|
|||||||
{% extends 'base.html' %}
|
|
||||||
{% load i18n %}
|
|
||||||
{% load static %}
|
|
||||||
{% load django_ledger %}
|
|
||||||
{% block title %}
|
|
||||||
{% trans "Delete Purchase Order" %}
|
|
||||||
{% endblock %}
|
|
||||||
{% block content %}
|
|
||||||
<div class="row justify-content-center">
|
|
||||||
<div class="col-md-6">
|
|
||||||
<form action="{% url 'po-delete' dealer_slug=request.dealer.slug entity_slug=view.kwargs.entity_slug po_pk=po_model.uuid %}"
|
|
||||||
method="post">
|
|
||||||
{% csrf_token %}
|
|
||||||
<div class="card shadow">
|
|
||||||
<div class="card-body text-center py-4">
|
|
||||||
<h2 class="card-title h3 fw-light mb-4">
|
|
||||||
{% blocktrans %}Are you sure you want to delete
|
|
||||||
Purchase Order {{ po_model.po_number }}?{% endblocktrans %}
|
|
||||||
</h2>
|
|
||||||
<p class="card-text text-muted mb-4">
|
|
||||||
{% trans "All transactions associated with this Purchase Order will be deleted.
|
|
||||||
If you want to void the PO instead," %} <a href="{% url 'purchase_order_detail' dealer_slug=request.dealer.slug pk=po_model.uuid %}"
|
|
||||||
class="text-decoration-none">{% trans "click here" %}</a>
|
|
||||||
</p>
|
|
||||||
<div class="d-flex justify-content-center gap-3 mt-4">
|
|
||||||
<a href="{% url 'purchase_order_update' dealer_slug=request.dealer.slug entity_slug=view.kwargs.entity_slug po_pk=po_model.uuid %}"
|
|
||||||
class="btn btn-phoenix-primary px-4">{% trans 'Go Back' %}</a>
|
|
||||||
<button type="submit" class="btn btn-phoenix-danger px-4">{% trans 'Delete' %}</button>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</form>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
{% endblock %}
|
|
||||||
@ -19,9 +19,9 @@
|
|||||||
{% if po_model.po_status == 'fulfilled' %}
|
{% if po_model.po_status == 'fulfilled' %}
|
||||||
<div class="ms-2">
|
<div class="ms-2">
|
||||||
<a class="btn btn-phoenix-primary my-2 mx-2"
|
<a class="btn btn-phoenix-primary my-2 mx-2"
|
||||||
href="{{ request.path }}?format=pdf&lang=en"><i class="fa-solid fa-arrow-down me-1"></i>{% trans 'Download PO ENG' %}</a>
|
href="{{ request.path }}?format=pdf&lang=en"><i class="fa-solid fa-arrow-down me-1"></i>{% trans 'Download PO EN' %}</a>
|
||||||
<a class="btn btn-phoenix-primary my-2"
|
<a class="btn btn-phoenix-primary my-2"
|
||||||
href="{{ request.path }}?format=pdf&lang=ar"><i class="fa-solid fa-arrow-down me-1"></i>{% trans 'Download PO ARB' %}</a>
|
href="{{ request.path }}?format=pdf&lang=ar"><i class="fa-solid fa-arrow-down me-1"></i>{% trans 'Download PO AR' %}</a>
|
||||||
</diV>
|
</diV>
|
||||||
{% endif %}
|
{% endif %}
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@ -1,9 +1,9 @@
|
|||||||
{% load tenhal_tag %}
|
{% load tenhal_tag %}
|
||||||
{% load custom_filters %}
|
{% load custom_filters %}
|
||||||
|
{% load i18n static custom_filters num2words_tags %}
|
||||||
<!DOCTYPE html>
|
<!DOCTYPE html>
|
||||||
<html lang="ar" dir="rtl">
|
<html lang="ar" dir="rtl">
|
||||||
<head>
|
<head>
|
||||||
<meta charset="UTF-8">
|
<meta charset="UTF-8">
|
||||||
<title>{{ po_model.po_number }}</title>
|
<title>{{ po_model.po_number }}</title>
|
||||||
<style>
|
<style>
|
||||||
@ -104,17 +104,22 @@
|
|||||||
font-weight: bold;
|
font-weight: bold;
|
||||||
}
|
}
|
||||||
.text-right {
|
.text-right {
|
||||||
text-align: left; /* This is now for left alignment in an RTL context */
|
text-align: right; /* This is now for left alignment in an RTL context */
|
||||||
}
|
}
|
||||||
|
|
||||||
/* Footer Styles */
|
/* Footer Styles */
|
||||||
.document-footer {
|
.document-footer {
|
||||||
|
position: relative;
|
||||||
|
bottom: 0;
|
||||||
|
left: 40px;
|
||||||
|
width: 100%;
|
||||||
text-align: center;
|
text-align: center;
|
||||||
font-size: 10px;
|
font-size: 10px;
|
||||||
color: #888;
|
color: #888;
|
||||||
border-top: 1px solid #ddd;
|
|
||||||
padding-top: 15px;
|
padding-top: 15px;
|
||||||
margin-top: 30px;
|
margin: 0 20mm;
|
||||||
|
|
||||||
}
|
}
|
||||||
.footer-flex {
|
.footer-flex {
|
||||||
display: flex;
|
display: flex;
|
||||||
@ -122,37 +127,76 @@
|
|||||||
align-items: center;
|
align-items: center;
|
||||||
width: 100%;
|
width: 100%;
|
||||||
}
|
}
|
||||||
|
.footer-logo {
|
||||||
|
text-align: center;
|
||||||
|
}
|
||||||
|
.footer-logo img {
|
||||||
|
height: 20px;
|
||||||
|
width: 20px;
|
||||||
|
}
|
||||||
|
.footer-logo p {
|
||||||
|
font-size: 9px;
|
||||||
|
font-weight: bold;
|
||||||
|
}
|
||||||
|
.footer-powered p {
|
||||||
|
font-size: 11px;
|
||||||
|
}
|
||||||
|
.footer-powered span {
|
||||||
|
font-weight: lighter;
|
||||||
|
}
|
||||||
|
.footer-powered a {
|
||||||
|
color: #112e40;
|
||||||
|
text-decoration: none;
|
||||||
|
font-size: 9px;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
</style>
|
</style>
|
||||||
</head>
|
</head>
|
||||||
<body>
|
<body>
|
||||||
<div class="document-header">
|
<div class="document-header">
|
||||||
|
|
||||||
|
<div>
|
||||||
|
<h1>{{ dealer.name }}</h1>
|
||||||
|
|
||||||
|
<address>
|
||||||
|
العنوان : {{ dealer.address }}<br>
|
||||||
|
البريد الإلكتروني : {{ dealer.user.email }}<br>
|
||||||
|
الهاتف : {{ dealer.phone_number }}<br>
|
||||||
|
رقم السجل التجاري : {{ dealer.crn }} | رقم ضريبة القيمة المضافة : {{ dealer.vrn }}
|
||||||
|
</address>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div>
|
||||||
|
<div class="dealer-logo ">
|
||||||
|
{% if dealer.logo %}
|
||||||
|
<img class="rounded-soft"
|
||||||
|
style="max-width:100px;
|
||||||
|
max-height:100px"
|
||||||
|
src="{{dealer.logo.url|default:'' }}"
|
||||||
|
alt="Dealer Logo" />
|
||||||
|
{% endif %}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
<div>
|
<div>
|
||||||
<h1>أمر شراء</h1>
|
<h1>أمر شراء</h1>
|
||||||
<h2 style="font-size: 18px;">{{ po_model.po_number }}</h2>
|
<h2 style="font-size: 18px;">{{ po_model.po_number }}</h2>
|
||||||
</div>
|
</div>
|
||||||
<div>
|
|
||||||
<h1>{{ dealer.name }}</h1>
|
|
||||||
<address>
|
|
||||||
العنوان: {{ dealer.address }}<br>
|
|
||||||
البريد الإلكتروني: {{ dealer.user.email }}<br>
|
|
||||||
الهاتف: {{ dealer.phone_number }}<br>
|
|
||||||
رقم السجل التجاري: {{ dealer.crn }} | رقم ضريبة القيمة المضافة: {{ dealer.vrn }}
|
|
||||||
</address>
|
|
||||||
</div>
|
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class="document-details">
|
<div class="document-details">
|
||||||
<div class="section">
|
<div class="section">
|
||||||
<h2>التفاصيل:</h2>
|
<h2>التفاصيل:</h2>
|
||||||
<p><span class="label">رقم أمر الشراء:</span> {{ po_model.po_number }}</p>
|
<p><span class="label">رقم أمر الشراء : </span> {{ po_model.po_number }}</p>
|
||||||
<p><span class="label">تاريخ الإصدار:</span> {{ po_model.date_fulfilled|date:"Y/m/d" }}</p>
|
<p><span class="label">تاريخ الإصدار : </span> {{ po_model.date_fulfilled|date:"Y/m/d" }}</p>
|
||||||
</div>
|
</div>
|
||||||
<div class="section">
|
<div class="section">
|
||||||
<h2>يُرسل إلى:</h2>
|
<h2>يُرسل إلى:</h2>
|
||||||
<p><span class="label">المورد:</span> {{ vendor.vendor_name }}</p>
|
<p><span class="label">المورد : </span> {{ vendor.vendor_name }}</p>
|
||||||
<p><span class="label">البريد الإلكتروني:</span> {{ vendor.email }}</p>
|
<p><span class="label">البريد الإلكتروني : </span> {{ vendor.email }}</p>
|
||||||
<p><span class="label">الهاتف:</span> {{ vendor.phone }}</p>
|
<p><span class="label">الهاتف : </span> {{ vendor.phone }}</p>
|
||||||
<p><span class="label">العنوان:</span> {{ vendor.address_1 }}</p>
|
<p><span class="label">العنوان : </span> {{ vendor.address_1 }}</p>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div class="col-lg-12">
|
<div class="col-lg-12">
|
||||||
@ -161,22 +205,28 @@
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class="document-details" style="margin-top: 30px;">
|
|
||||||
<div class="section" style="width: 100%;">
|
|
||||||
<h2>ملاحظات:</h2>
|
|
||||||
<p>{{ po_model.notes|default:"لا توجد ملاحظات إضافية." }}</p>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div class="document-details" style="margin-top: 10px; border-top: 1px solid #ddd; padding-top: 10px;">
|
<div class="document-details" style="margin-top: 10px; border-top: 1px solid #ddd; padding-top: 10px;">
|
||||||
<div class="section text-right" style="width: 100%;">
|
<div class="section text-right" style="width: 100%;">
|
||||||
<p class="h4"><span class="label">المبلغ الإجمالي:</span> {{ po_total_amount|floatformat:'2g' }}<span class="icon-saudi_riyal"></span></p>
|
<p class="h4"><span class="label">المبلغ الإجمالي : </span> {{ po_total_amount|currency_format }}<span class="icon-saudi_riyal"></span></p>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
<hr style="border-bottom:1px solid #ccc; ">
|
||||||
|
|
||||||
<div class="document-footer footer-flex">
|
<div class="document-footer">
|
||||||
<p>© <strong>هيكل</strong> {% now "Y" %} جميع الحقوق محفوظة.</p>
|
<div class="footer-logo">
|
||||||
<p><strong>تنحل</strong>مدعوم من</p>
|
<img src="{% static 'images/logos/logo-d-pdf.png' %}" alt="Logo" />
|
||||||
|
<p>
|
||||||
|
<span>Haikal</span> | <span>هيكل</span>
|
||||||
|
</p>
|
||||||
</div>
|
</div>
|
||||||
</body>
|
<div class="footer-powered">
|
||||||
|
<p>
|
||||||
|
<span>Powered by </span>
|
||||||
|
<a href="https://tenhal.sa"><span>TENHAL</span> | <span>تنحل</span></a>
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</body>
|
||||||
</html>
|
</html>
|
||||||
@ -106,4 +106,4 @@
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
{% endblock %}
|
{% endblock %}
|
||||||
|
|||||||
@ -6,7 +6,7 @@
|
|||||||
|
|
||||||
<!DOCTYPE html>
|
<!DOCTYPE html>
|
||||||
<html lang="en">
|
<html lang="en">
|
||||||
<head>
|
<head>
|
||||||
<meta charset="UTF-8">
|
<meta charset="UTF-8">
|
||||||
<title>{{ po_model.po_number }}</title>
|
<title>{{ po_model.po_number }}</title>
|
||||||
<style>
|
<style>
|
||||||
@ -100,38 +100,75 @@
|
|||||||
font-weight: bold;
|
font-weight: bold;
|
||||||
}
|
}
|
||||||
.text-right {
|
.text-right {
|
||||||
text-align: right;
|
text-align: left;
|
||||||
}
|
}
|
||||||
|
|
||||||
/* Footer Styles */
|
/* Footer Styles */
|
||||||
.document-footer {
|
.document-footer {
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: center;
|
||||||
|
position: relative;
|
||||||
|
bottom: 0;
|
||||||
|
left: 0;
|
||||||
|
width: 100%;
|
||||||
text-align: center;
|
text-align: center;
|
||||||
font-size: 10px;
|
font-size: 10px;
|
||||||
color: #888;
|
color: #888;
|
||||||
border-top: 1px solid #ddd;
|
|
||||||
padding-top: 15px;
|
padding-top: 15px;
|
||||||
margin-top: 30px;
|
margin: 0 auto; /* This change centers the footer container */
|
||||||
}
|
}
|
||||||
|
|
||||||
.footer-flex {
|
.footer-logo {
|
||||||
display: flex;
|
text-align: center;
|
||||||
justify-content: space-between;
|
|
||||||
align-items: center;
|
|
||||||
width: 100%;
|
|
||||||
}
|
}
|
||||||
|
.footer-logo img {
|
||||||
|
height: 20px;
|
||||||
|
width: 20px;
|
||||||
|
}
|
||||||
|
.footer-logo p {
|
||||||
|
font-size: 9px;
|
||||||
|
font-weight: bold;
|
||||||
|
}
|
||||||
|
.footer-powered p {
|
||||||
|
font-size: 11px;
|
||||||
|
}
|
||||||
|
.footer-powered span {
|
||||||
|
font-weight: lighter;
|
||||||
|
}
|
||||||
|
.footer-powered a {
|
||||||
|
color: #112e40;
|
||||||
|
text-decoration: none;
|
||||||
|
font-size: 9px;
|
||||||
|
}
|
||||||
|
|
||||||
</style>
|
</style>
|
||||||
</head>
|
</head>
|
||||||
<body>
|
<body>
|
||||||
<div class="document-header">
|
<div class="document-header">
|
||||||
<div>
|
<div>
|
||||||
<h1>{{ dealer.name }}</h1>
|
<h1>{{ dealer.name }}</h1>
|
||||||
<address>
|
<address>
|
||||||
Address: {{ dealer.address}}<br>
|
Address : {{ dealer.address}}<br>
|
||||||
Email: {{ dealer.user.email }}<br>
|
Email : {{ dealer.user.email }}<br>
|
||||||
Phone: {{dealer.phone_number }}<br>
|
Phone : {{dealer.phone_number }}<br>
|
||||||
CRN: {{dealer.crn}} | VRN: {{dealer.vrn}}
|
CRN : {{dealer.crn}} | VRN : {{dealer.vrn}}
|
||||||
</address>
|
</address>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
<div>
|
||||||
|
<div class="dealer-logo ">
|
||||||
|
{% if dealer.logo %}
|
||||||
|
<img class="rounded-soft"
|
||||||
|
style="max-width:100px;
|
||||||
|
max-height:100px"
|
||||||
|
src="{{dealer.logo.url|default:'' }}"
|
||||||
|
alt="Dealer Logo" />
|
||||||
|
{% endif %}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
<div>
|
<div>
|
||||||
<h1>PURCHASE ORDER</h1>
|
<h1>PURCHASE ORDER</h1>
|
||||||
<h2 style="font-size: 18px;">{{ po_model.po_number }}</h2>
|
<h2 style="font-size: 18px;">{{ po_model.po_number }}</h2>
|
||||||
@ -141,15 +178,15 @@
|
|||||||
<div class="document-details">
|
<div class="document-details">
|
||||||
<div class="section">
|
<div class="section">
|
||||||
<h2>BILL TO:</h2>
|
<h2>BILL TO:</h2>
|
||||||
<p><span class="label">Vendor: {{vendor.vendor_name}}</span> </p>
|
<p><span class="label">Vendor : {{vendor.vendor_name}}</span> </p>
|
||||||
<p><span class="label">Email: {{vendor.email}}</span> </p>
|
<p><span class="label">Email : {{vendor.email}}</span> </p>
|
||||||
<p><span class="label">Phone: {{vendor.phone}}</span> </p>
|
<p><span class="label">Phone : {{vendor.phone}}</span> </p>
|
||||||
<p><span class="label">Address: {{vendor.address_1}}</span> </p>
|
<p><span class="label">Address : {{vendor.address_1}}</span> </p>
|
||||||
</div>
|
</div>
|
||||||
<div class="section">
|
<div class="section">
|
||||||
<h2>DETAILS:</h2>
|
<h2>DETAILS:</h2>
|
||||||
<p><span class="label">PO Number:</span> {{ po_model.po_number }}</p>
|
<p><span class="label">PO Number : </span> {{ po_model.po_number }}</p>
|
||||||
<p><span class="label">Issue Date:</span> {{ po_model.date_fulfilled|date:"F j, Y" }}</p>
|
<p><span class="label">Issue Date : </span> {{ po_model.date_fulfilled|date:"F j, Y" }}</p>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div class="col-lg-12">
|
<div class="col-lg-12">
|
||||||
@ -161,14 +198,25 @@
|
|||||||
<div class="document-details" style="margin-top: 30px;">
|
<div class="document-details" style="margin-top: 30px;">
|
||||||
|
|
||||||
<div class="section text-right">
|
<div class="section text-right">
|
||||||
<p class="h4"><span class="label">Total Amount:</span> {{ po_total_amount|floatformat:'2g' }}<span class="icon-saudi_riyal"></span></p>
|
<p class="h4"><span class="label">Total Amount : </span> {{ po_total_amount|currency_format }}<span class="icon-saudi_riyal"></span></p>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<hr style="border-bottom:1px solid #ccc; ">
|
||||||
|
|
||||||
|
<div class="document-footer">
|
||||||
|
<div class="footer-logo">
|
||||||
|
<img src="{% static 'images/logos/logo-d-pdf.png' %}" alt="Logo" />
|
||||||
|
<p>
|
||||||
|
<span>Haikal</span> | <span>هيكل</span>
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
|
<div class="footer-powered">
|
||||||
|
<p>
|
||||||
|
<span>Powered by </span>
|
||||||
|
<a href="https://tenhal.sa"><span>TENHAL</span> | <span>تنحل</span></a>
|
||||||
|
</p>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class="document-footer footer-flex">
|
</body>
|
||||||
<p>© {% now "Y" %} All rights reserved <strong>Haikal</strong> </p>
|
|
||||||
<p>Powered By <strong>Tenhal</strong></p>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
</body>
|
|
||||||
</html>
|
</html>
|
||||||
@ -124,4 +124,4 @@
|
|||||||
{% include "message-illustration.html" with value1=_("You don't have a Vendor, Please add a Vendor before creating a Purchase Order.") value2=_("Create New Vendor") url=vendor_create_url %}
|
{% include "message-illustration.html" with value1=_("You don't have a Vendor, Please add a Vendor before creating a Purchase Order.") value2=_("Create New Vendor") url=vendor_create_url %}
|
||||||
{% endif %}
|
{% endif %}
|
||||||
{% endif %}
|
{% endif %}
|
||||||
{% endblock %}
|
{% endblock %}
|
||||||
|
|||||||
@ -58,4 +58,4 @@
|
|||||||
</table>
|
</table>
|
||||||
</div>
|
</div>
|
||||||
<div>
|
<div>
|
||||||
{% endblock content %}
|
{% endblock content %}
|
||||||
|
|||||||
@ -6,7 +6,7 @@
|
|||||||
<tr class="has-text-centered bg-body-highlight">
|
<tr class="has-text-centered bg-body-highlight">
|
||||||
<th>البند</th>
|
<th>البند</th>
|
||||||
<th>سعر الوحدة</th>
|
<th>سعر الوحدة</th>
|
||||||
<th>كمية أمر الشراء</th>
|
<th>الكمية</th>
|
||||||
<th>المبلغ</th>
|
<th>المبلغ</th>
|
||||||
</tr>
|
</tr>
|
||||||
</thead>
|
</thead>
|
||||||
@ -14,7 +14,7 @@
|
|||||||
{% for item in po_items_list %}
|
{% for item in po_items_list %}
|
||||||
<tr>
|
<tr>
|
||||||
<td>{{ item.item_model }}</td>
|
<td>{{ item.item_model }}</td>
|
||||||
<td class="has-text-centered">{{ item.po_unit_cost }}</td>
|
<td class="has-text-centered">{{ item.po_unit_cost|currency_format }}</td>
|
||||||
<td class="has-text-centered">{{ item.po_quantity }}</td>
|
<td class="has-text-centered">{{ item.po_quantity }}</td>
|
||||||
<td class="has-text-centered">
|
<td class="has-text-centered">
|
||||||
<span class="icon-saudi_riyal"></span>{{ item.po_total_amount | currency_format }}
|
<span class="icon-saudi_riyal"></span>{{ item.po_total_amount | currency_format }}
|
||||||
@ -26,12 +26,12 @@
|
|||||||
<tfoot>
|
<tfoot>
|
||||||
<tr>
|
<tr>
|
||||||
|
|
||||||
|
<td>إجمالي مبلغ أمر الشراء</td>
|
||||||
<td></td>
|
<td></td>
|
||||||
<td class="has-text-left">إجمالي مبلغ أمر الشراء</td>
|
<td class="has-text-left">{{po_quantity}}</td>
|
||||||
<td class="has-text-weight-bold has-text-centered">
|
<td class="has-text-weight-bold has-text-centered">
|
||||||
<span class="icon-saudi_riyal"></span>{{ po_model.po_amount | currency_format }}
|
<span class="icon-saudi_riyal"></span>{{ po_model.po_amount | currency_format }}
|
||||||
</td>
|
</td>
|
||||||
<td></td>
|
|
||||||
|
|
||||||
</tr>
|
</tr>
|
||||||
</tfoot>
|
</tfoot>
|
||||||
|
|||||||
@ -14,7 +14,7 @@
|
|||||||
{% for item in po_items_list %}
|
{% for item in po_items_list %}
|
||||||
<tr>
|
<tr>
|
||||||
<td>{{ item.item_model }}</td>
|
<td>{{ item.item_model }}</td>
|
||||||
<td class="has-text-centered">{{ item.po_unit_cost }}</td>
|
<td class="has-text-centered">{{ item.po_unit_cost|currency_format}}</td>
|
||||||
<td class="has-text-centered">{{ item.po_quantity }}</td>
|
<td class="has-text-centered">{{ item.po_quantity }}</td>
|
||||||
<td class="has-text-centered">
|
<td class="has-text-centered">
|
||||||
<span class="icon-saudi_riyal"></span>{{ item.po_total_amount | currency_format }}
|
<span class="icon-saudi_riyal"></span>{{ item.po_total_amount | currency_format }}
|
||||||
@ -26,12 +26,14 @@
|
|||||||
<tfoot>
|
<tfoot>
|
||||||
<tr>
|
<tr>
|
||||||
|
|
||||||
<td></td>
|
<td>Total PO Amount</td>
|
||||||
<td class="has-text-right">Total PO Amount</td>
|
<td class="has-text-right"></td>
|
||||||
|
|
||||||
|
<td>{{po_quantity}}</td>
|
||||||
|
|
||||||
<td class="has-text-weight-bold has-text-centered">
|
<td class="has-text-weight-bold has-text-centered">
|
||||||
<span class="icon-saudi_riyal"></span>{{ po_model.po_amount | currency_format }}
|
<span class="icon-saudi_riyal"></span>{{ po_model.po_amount | currency_format }}
|
||||||
</td>
|
</td>
|
||||||
<td></td>
|
|
||||||
|
|
||||||
</tr>
|
</tr>
|
||||||
</tfoot>
|
</tfoot>
|
||||||
|
|||||||
@ -47,8 +47,8 @@
|
|||||||
</button>
|
</button>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</form>
|
</form>
|
||||||
<!-- Results table -->
|
<!-- Results table -->
|
||||||
<div class="table-responsive mt-3">{% include "recalls/partials/recall_cars_table.html" %}</div>
|
<div class="table-responsive mt-3">{% include "recalls/partials/recall_cars_table.html" %}</div>
|
||||||
{% endblock content %}
|
{% endblock content %}
|
||||||
|
|||||||
@ -134,11 +134,17 @@
|
|||||||
{% if perms.django_ledger.change_estimatemodel %}
|
{% if perms.django_ledger.change_estimatemodel %}
|
||||||
<a href="{% url 'send_email' request.dealer.slug estimate.pk %}"
|
<a href="{% url 'send_email' request.dealer.slug estimate.pk %}"
|
||||||
class="btn btn-phoenix-primary me-2"><span class="fa-regular fa-paper-plane me-sm-2"></span><span class="d-none d-sm-inline-block">{% trans 'Send Quotation' %}</span></a>
|
class="btn btn-phoenix-primary me-2"><span class="fa-regular fa-paper-plane me-sm-2"></span><span class="d-none d-sm-inline-block">{% trans 'Send Quotation' %}</span></a>
|
||||||
<a href="{% url 'estimate_print' request.dealer.slug estimate.pk %}"
|
<a href="{% url 'estimate_print' request.dealer.slug estimate.pk %}?lang=en"
|
||||||
class="btn btn-phoenix-secondary"
|
class="btn btn-phoenix-secondary"
|
||||||
target="_blank">
|
target="_blank">
|
||||||
<span class="d-none d-sm-inline-block"><i class="fas fa-print me-2"></i>{% trans 'Print' %}</span>
|
<span class="d-none d-sm-inline-block"><i class="fas fa-print me-2"></i>{% trans 'Print EN' %}</span>
|
||||||
</a>
|
</a>
|
||||||
|
<a href="{% url 'estimate_print' request.dealer.slug estimate.pk %}?lang=ar"
|
||||||
|
class="btn btn-phoenix-secondary"
|
||||||
|
target="_blank">
|
||||||
|
<span class="d-none d-sm-inline-block"><i class="fas fa-print me-2"></i>{% trans 'Print AR' %}</span>
|
||||||
|
</a>
|
||||||
|
|
||||||
{% endif %}
|
{% endif %}
|
||||||
{% if estimate.sale_orders.first %}
|
{% if estimate.sale_orders.first %}
|
||||||
<!--if sale order exist-->
|
<!--if sale order exist-->
|
||||||
@ -287,8 +293,9 @@
|
|||||||
<tr class="bg-body-secondary total-sum">
|
<tr class="bg-body-secondary total-sum">
|
||||||
<td class="align-middle ps-4 fw-semibold text-body-highlight" colspan="7">{% trans "Additional Services" %}</td>
|
<td class="align-middle ps-4 fw-semibold text-body-highlight" colspan="7">{% trans "Additional Services" %}</td>
|
||||||
<td class="align-middle text-start fw-semibold">
|
<td class="align-middle text-start fw-semibold">
|
||||||
{% for service in data.additional_services.services %}
|
|
||||||
<small><span class="fw-semibold">+ {{ service.0.name }} - {{ service.0.price_|floatformat }}<span class="icon-saudi_riyal"></span></span></small>
|
{% for service in additional_finances %}
|
||||||
|
<small><span class="fw-semibold">+ {{ service.name }} - {{ service.price_|floatformat }}<span class="icon-saudi_riyal"></span></span></small>
|
||||||
<br>
|
<br>
|
||||||
{% endfor %}
|
{% endfor %}
|
||||||
{% if estimate.is_draft %}
|
{% if estimate.is_draft %}
|
||||||
@ -304,20 +311,20 @@
|
|||||||
<tr class="bg-body-secondary total-sum">
|
<tr class="bg-body-secondary total-sum">
|
||||||
<td class="align-middle ps-4 fw-bolder text-body-highlight" colspan="7">{% trans "Grand Total" %}</td>
|
<td class="align-middle ps-4 fw-bolder text-body-highlight" colspan="7">{% trans "Grand Total" %}</td>
|
||||||
<td class="align-middle text-start fw-bolder">
|
<td class="align-middle text-start fw-bolder">
|
||||||
<span id="grand-total">{{ data.grand_total|floatformat }}<span class="icon-saudi_riyal"></span></span>
|
<span id="grand-total">{{ grand_total|floatformat }}<span class="icon-saudi_riyal"></span></span>
|
||||||
</td>
|
</td>
|
||||||
</tr>
|
</tr>
|
||||||
</tbody>
|
</tbody>
|
||||||
</table>
|
</table>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<!-- end of .row-->
|
<!-- end of .row-->
|
||||||
</section>
|
</section>
|
||||||
<!-- <section> close ============================-->
|
<!-- <section> close ============================-->
|
||||||
<!-- ============================================-->
|
<!-- ============================================-->
|
||||||
<!-- add update Modal -->
|
<!-- add update Modal -->
|
||||||
<div class="modal fade"
|
<div class="modal fade"
|
||||||
id="additionalModal"
|
id="additionalModal"
|
||||||
tabindex="-1"
|
tabindex="-1"
|
||||||
aria-labelledby="additionalModalLabel"
|
aria-labelledby="additionalModalLabel"
|
||||||
@ -342,7 +349,7 @@
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
{% endblock %}
|
{% endblock %}
|
||||||
{% block customJS %}
|
{% block customJS %}
|
||||||
<script>
|
<script>
|
||||||
|
|||||||
@ -1,264 +0,0 @@
|
|||||||
{% load i18n static custom_filters num2words_tags %}
|
|
||||||
<!DOCTYPE html>
|
|
||||||
<html lang="ar" dir="rtl">
|
|
||||||
<head>
|
|
||||||
<meta charset="UTF-8">
|
|
||||||
<title>{% trans "Quotation" %}</title>
|
|
||||||
<style>
|
|
||||||
/* General Body and Font Styles */
|
|
||||||
body {
|
|
||||||
font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, "Helvetica Neue", Arial, sans-serif;
|
|
||||||
font-size: 12px;
|
|
||||||
color: #333;
|
|
||||||
margin: 0;
|
|
||||||
padding: 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
/* Page Layout and Margins for PDF */
|
|
||||||
@page {
|
|
||||||
size: A4;
|
|
||||||
margin: 20mm;
|
|
||||||
@top-left {
|
|
||||||
content: "صفحة " counter(page) " من " counter(pages);
|
|
||||||
font-size: 10px;
|
|
||||||
color: #555;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/* Header Styles */
|
|
||||||
.document-header {
|
|
||||||
display: flex;
|
|
||||||
justify-content: space-between;
|
|
||||||
align-items: center;
|
|
||||||
border-bottom: 2px solid #333;
|
|
||||||
padding-bottom: 15px;
|
|
||||||
margin-bottom: 20px;
|
|
||||||
}
|
|
||||||
.document-header .logo {
|
|
||||||
max-width: 150px;
|
|
||||||
height: auto;
|
|
||||||
}
|
|
||||||
.document-header h1 {
|
|
||||||
font-size: 24px;
|
|
||||||
margin: 0;
|
|
||||||
color: #0056b3; /* A professional blue */
|
|
||||||
}
|
|
||||||
.document-header address {
|
|
||||||
text-align: right;
|
|
||||||
font-style: normal;
|
|
||||||
font-size: 10px;
|
|
||||||
}
|
|
||||||
|
|
||||||
/* Document Details Section */
|
|
||||||
.document-details {
|
|
||||||
display: flex;
|
|
||||||
justify-content: space-between;
|
|
||||||
margin-bottom: 30px;
|
|
||||||
line-height: 1.6;
|
|
||||||
}
|
|
||||||
.document-details .section {
|
|
||||||
width: 48%;
|
|
||||||
}
|
|
||||||
.document-details h2 {
|
|
||||||
font-size: 14px;
|
|
||||||
border-bottom: 1px solid #ccc;
|
|
||||||
padding-bottom: 5px;
|
|
||||||
margin-bottom: 10px;
|
|
||||||
color: #555;
|
|
||||||
}
|
|
||||||
.document-details p {
|
|
||||||
margin: 0;
|
|
||||||
font-size: 12px;
|
|
||||||
}
|
|
||||||
.document-details .label {
|
|
||||||
font-weight: bold;
|
|
||||||
color: #555;
|
|
||||||
}
|
|
||||||
|
|
||||||
/* Table Styles */
|
|
||||||
.table {
|
|
||||||
width: 100%;
|
|
||||||
border-collapse: collapse;
|
|
||||||
margin-top: 20px;
|
|
||||||
}
|
|
||||||
.table th, .table td {
|
|
||||||
border: 1px solid #ddd;
|
|
||||||
padding: 8px;
|
|
||||||
text-align: right;
|
|
||||||
}
|
|
||||||
.table th {
|
|
||||||
background-color: #f2f2f2;
|
|
||||||
font-weight: bold;
|
|
||||||
}
|
|
||||||
.table tfoot td {
|
|
||||||
border-top: 2px solid #333;
|
|
||||||
font-weight: bold;
|
|
||||||
}
|
|
||||||
.text-right {
|
|
||||||
text-align: left;
|
|
||||||
}
|
|
||||||
.text-left {
|
|
||||||
text-align: right;
|
|
||||||
}
|
|
||||||
.text-center {
|
|
||||||
text-align: center;
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
/* Footer Styles */
|
|
||||||
.document-footer {
|
|
||||||
text-align: center;
|
|
||||||
font-size: 10px;
|
|
||||||
color: #888;
|
|
||||||
border-top: 1px solid #ddd;
|
|
||||||
padding-top: 15px;
|
|
||||||
margin-top: 30px;
|
|
||||||
}
|
|
||||||
.footer-flex {
|
|
||||||
display: flex;
|
|
||||||
justify-content: space-between;
|
|
||||||
align-items: center;
|
|
||||||
width: 100%;
|
|
||||||
}
|
|
||||||
</style>
|
|
||||||
</head>
|
|
||||||
<body>
|
|
||||||
<div class="document-header">
|
|
||||||
<div>
|
|
||||||
<h1>{{ request.dealer.name }}</h1>
|
|
||||||
<address>
|
|
||||||
العنوان: {{ request.dealer.address }}<br>
|
|
||||||
البريد الإلكتروني: {{ request.dealer.user.email }}<br>
|
|
||||||
الهاتف: {{ request.dealer.phone_number }}<br>
|
|
||||||
رقم السجل التجاري: {{ request.dealer.crn }} | الرقم الضريبي: {{ request.dealer.vrn }}
|
|
||||||
</address>
|
|
||||||
</div>
|
|
||||||
<div>
|
|
||||||
<h1>عرض سعر</h1>
|
|
||||||
<h2 style="font-size: 18px;">{{ estimate.estimate_number }}</h2>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div class="document-details">
|
|
||||||
<div class="section">
|
|
||||||
<h2>{{ estimate.customer.customer_name }}: إلى</h2>
|
|
||||||
<p><span class="label">العميل: {{ estimate.customer.customer_name }}</span></p>
|
|
||||||
<p><span class="label">البريد الإلكتروني: {{ estimate.customer.email |default:"N/A"}}</span></p>
|
|
||||||
<p><span class="label">الهاتف: {{ estimate.customer.phone_number|default:"N/A" }}</span></p>
|
|
||||||
<p><span class="label">العنوان: {{ estimate.customer.address_1|default:"N/A" }}</span></p>
|
|
||||||
</div>
|
|
||||||
<div class="section text-left">
|
|
||||||
<h2>التفاصيل:</h2>
|
|
||||||
<p><span class="label">رقم عرض السعر:</span> {{ estimate.estimate_number }}</p>
|
|
||||||
<p><span class="label">تاريخ الإصدار:</span> {{ estimate.date_approved|date:"Y/m/d" }}</p>
|
|
||||||
<p><span class="label">طريقة الدفع:</span> {{ estimate.get_terms_display }}</p>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div class="col-lg-12">
|
|
||||||
<div class="table-responsive">
|
|
||||||
<div class="d-flex justify-content-between">
|
|
||||||
<span class="fs-9 fw-thin">تفاصيل السيارة</span>
|
|
||||||
</div>
|
|
||||||
<table class="table table-sm table-bordered m-1">
|
|
||||||
<thead>
|
|
||||||
<tr>
|
|
||||||
<th class="text-wrap text-center align-content-center">
|
|
||||||
<span class="fs-10">الصانع</span>
|
|
||||||
</th>
|
|
||||||
<th class="text-wrap text-center align-content-center">
|
|
||||||
<span class="fs-10">الموديل</span>
|
|
||||||
</th>
|
|
||||||
<th class="text-wrap text-center align-content-center">
|
|
||||||
<span class="fs-10">السلسلة</span>
|
|
||||||
</th>
|
|
||||||
<th class="text-wrap text-center align-content-center">
|
|
||||||
<span class="fs-10">الفئة</span>
|
|
||||||
</th>
|
|
||||||
<th class="text-wrap text-center align-content-center">
|
|
||||||
<span class="fs-10">السنة</span>
|
|
||||||
</th>
|
|
||||||
<th class="text-wrap text-center align-content-center">
|
|
||||||
<span class="fs-10">رقم الهيكل</span>
|
|
||||||
</th>
|
|
||||||
<th class="text-wrap text-center align-content-center">
|
|
||||||
<span class="fs-10">الكمية</span>
|
|
||||||
</th>
|
|
||||||
<th class="text-wrap text-center align-content-center">
|
|
||||||
<span class="fs-10">سعر الوحدة</span>
|
|
||||||
</th>
|
|
||||||
<th class="text-wrap text-center align-content-center">
|
|
||||||
<span class="fs-10">الخصم</span>
|
|
||||||
</th>
|
|
||||||
<th class="text-wrap text-center align-content-center">
|
|
||||||
<span class="fs-10">الضريبة</span>
|
|
||||||
</th>
|
|
||||||
<th class="text-wrap text-center align-content-center">
|
|
||||||
<span class="fs-10">الإجمالي</span>
|
|
||||||
</th>
|
|
||||||
</tr>
|
|
||||||
</thead>
|
|
||||||
<tbody>
|
|
||||||
<tr>
|
|
||||||
<td class="ps-1 fs-10 align-content-center">{{ data.car.id_car_make.name }}</td>
|
|
||||||
<td class="ps-1 fs-10 align-content-center">{{ data.car.id_car_model.name }}</td>
|
|
||||||
<td class="ps-1 fs-10 align-content-center">{{ data.car.id_car_serie.name }}</td>
|
|
||||||
<td class="ps-1 fs-10 align-content-center">{{ data.car.id_car_trim.name }}</td>
|
|
||||||
<td class="text-center fs-10 align-content-center">{{ data.car.year }}</td>
|
|
||||||
<td class="ps-1 fs-10 align-content-center">{{ data.car.vin }}</td>
|
|
||||||
<td class="text-center fs-10 align-content-center">1</td>
|
|
||||||
<td class="text-center fs-10 align-content-center">{{ data.car.marked_price|floatformat:2 }}</td>
|
|
||||||
<td class="text-center fs-10 align-content-center">{{ data.discount_amount|floatformat:2 }}</td>
|
|
||||||
<td class="text-center fs-10 align-content-center">{{ data.vat_amount|floatformat:2 }}</td>
|
|
||||||
<td class="text-center fs-10 align-content-center">{{ data.final_price|floatformat:2 }}</td>
|
|
||||||
</tr>
|
|
||||||
</tbody>
|
|
||||||
</table>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
{% if data.additional_services %}
|
|
||||||
<div class="col-lg-12">
|
|
||||||
<div class="table-responsive">
|
|
||||||
<div class="d-flex justify-content-between">
|
|
||||||
<span class="fs-9 fw-thin">الخدمات الإضافية</span>
|
|
||||||
</div>
|
|
||||||
<table class="table table-sm table-bordered m-1">
|
|
||||||
<thead>
|
|
||||||
<tr>
|
|
||||||
<th class="text-center fs-10 align-content-center">النوع</th>
|
|
||||||
<th class="text-center fs-10 align-content-center">القيمة</th>
|
|
||||||
<th class="text-center fs-10 align-content-center">ضريبة الخدمة</th>
|
|
||||||
<th class="text-center fs-10 align-content-center">الإجمالي</th>
|
|
||||||
</tr>
|
|
||||||
</thead>
|
|
||||||
<tbody>
|
|
||||||
{% for service in data.additional_services.services %}
|
|
||||||
<tr>
|
|
||||||
<td class="ps-1 text-center fs-10 align-content-center">{{ service.0.name }}</td>
|
|
||||||
<td class="ps-1 text-center fs-10 align-content-center">{{ service.0.price|floatformat:2 }}</td>
|
|
||||||
<td class="ps-1 text-center fs-10 align-content-center">{{ service.1|floatformat:2 }}</td>
|
|
||||||
<td class="ps-1 text-center fs-10 align-content-center">{{ service.0.price_|floatformat:2 }}</td>
|
|
||||||
</tr>
|
|
||||||
{% endfor %}
|
|
||||||
</tbody>
|
|
||||||
</table>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
{% endif %}
|
|
||||||
|
|
||||||
<div class="document-details" style="margin-top: 30px;">
|
|
||||||
<div class="section text-left">
|
|
||||||
<p><span class="label">إجمالي ضريبة القيمة المضافة:</span> {{ data.total_vat|floatformat:'2g' }}</p>
|
|
||||||
<p><span class="label">الإجمالي الكلي:</span> {{ data.grand_total|floatformat:'2g' }}</p>
|
|
||||||
<p><span class="label">كتابةً:</span> {{ data.grand_total|num_to_words }}</p>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div class="document-footer footer-flex">
|
|
||||||
<p>© {% now "Y" %} جميع الحقوق محفوظة <strong>هيكل</strong></p>
|
|
||||||
<p>بواسطة <strong>تنحل</strong></p>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
</body>
|
|
||||||
</html>
|
|
||||||
357
templates/sales/estimates/estimate_preview_ar.html
Normal file
357
templates/sales/estimates/estimate_preview_ar.html
Normal file
@ -0,0 +1,357 @@
|
|||||||
|
{% load i18n static custom_filters num2words_tags %}
|
||||||
|
<!DOCTYPE html>
|
||||||
|
<html lang="ar" dir="rtl">
|
||||||
|
<head>
|
||||||
|
<meta charset="UTF-8">
|
||||||
|
<title>عرض سعر</title>
|
||||||
|
|
||||||
|
<style>
|
||||||
|
/* General Body and Font Styles */
|
||||||
|
body {
|
||||||
|
font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, "Helvetica Neue", Arial, sans-serif;
|
||||||
|
font-size: 12px;
|
||||||
|
color: #333;
|
||||||
|
margin: 0;
|
||||||
|
padding: 0;
|
||||||
|
position: relative;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Page Layout and Margins for PDF */
|
||||||
|
@page {
|
||||||
|
size: A4;
|
||||||
|
margin: 20mm;
|
||||||
|
@top-left {
|
||||||
|
content: "صفحة " counter(page) " من " counter(pages);
|
||||||
|
font-size: 10px;
|
||||||
|
color: #555;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Header Styles */
|
||||||
|
.document-header {
|
||||||
|
display: flex;
|
||||||
|
justify-content: space-between;
|
||||||
|
align-items: center;
|
||||||
|
border-bottom: 2px solid #333;
|
||||||
|
padding-bottom: 15px;
|
||||||
|
margin-bottom: 20px;
|
||||||
|
}
|
||||||
|
.document-header .logo {
|
||||||
|
max-width: 150px;
|
||||||
|
height: auto;
|
||||||
|
}
|
||||||
|
.document-header h1 {
|
||||||
|
font-size: 24px;
|
||||||
|
margin: 0;
|
||||||
|
color: #0056b3;
|
||||||
|
}
|
||||||
|
.document-header address {
|
||||||
|
text-align: right;
|
||||||
|
font-style: normal;
|
||||||
|
font-size: 10px;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Document Details Section */
|
||||||
|
.document-details {
|
||||||
|
display: flex;
|
||||||
|
justify-content: space-between;
|
||||||
|
margin-bottom: 30px;
|
||||||
|
line-height: 1.6;
|
||||||
|
}
|
||||||
|
.document-details .section {
|
||||||
|
width: 48%;
|
||||||
|
}
|
||||||
|
.document-details h2 {
|
||||||
|
font-size: 14px;
|
||||||
|
border-bottom: 1px solid #ccc;
|
||||||
|
padding-bottom: 5px;
|
||||||
|
margin-bottom: 10px;
|
||||||
|
color: #555;
|
||||||
|
}
|
||||||
|
.document-details p {
|
||||||
|
margin: 0;
|
||||||
|
font-size: 12px;
|
||||||
|
}
|
||||||
|
.document-details .label {
|
||||||
|
font-weight: bold;
|
||||||
|
color: #555;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Table Styles */
|
||||||
|
.table {
|
||||||
|
width: 80%;
|
||||||
|
border-collapse: collapse;
|
||||||
|
margin: 20px auto; /* Centering the table */
|
||||||
|
}
|
||||||
|
.table th, .table td {
|
||||||
|
border: 1px solid #ddd;
|
||||||
|
padding: 8px;
|
||||||
|
text-align: right;
|
||||||
|
}
|
||||||
|
.table th {
|
||||||
|
background-color: #f2f2f2;
|
||||||
|
font-weight: bold;
|
||||||
|
}
|
||||||
|
.table tfoot td {
|
||||||
|
border-top: 2px solid #333;
|
||||||
|
font-weight: bold;
|
||||||
|
}
|
||||||
|
.text-right {
|
||||||
|
text-align: left;
|
||||||
|
}
|
||||||
|
.text-left {
|
||||||
|
text-align: right;
|
||||||
|
}
|
||||||
|
.text-center {
|
||||||
|
text-align: center;2px solid #333;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Responsive and layout helpers */
|
||||||
|
.table-container {
|
||||||
|
width: 100%;
|
||||||
|
overflow-x: auto;
|
||||||
|
margin-bottom: 20px;
|
||||||
|
}
|
||||||
|
.table-header {
|
||||||
|
display: flex;
|
||||||
|
justify-content: space-between;
|
||||||
|
align-items: center;
|
||||||
|
margin-bottom: 5px;
|
||||||
|
}
|
||||||
|
.table-header span {
|
||||||
|
font-size: 9px;
|
||||||
|
font-weight: lighter;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Footer Styles */
|
||||||
|
.document-footer {
|
||||||
|
position: relative;
|
||||||
|
bottom: 0;
|
||||||
|
left: 40px;
|
||||||
|
width: 100%;
|
||||||
|
text-align: center;
|
||||||
|
font-size: 10px;
|
||||||
|
color: #888;
|
||||||
|
|
||||||
|
padding-top: 15px;
|
||||||
|
margin: 0 20mm;
|
||||||
|
|
||||||
|
}
|
||||||
|
.footer-flex {
|
||||||
|
display: flex;
|
||||||
|
justify-content: space-between;
|
||||||
|
align-items: center;
|
||||||
|
width: 100%;
|
||||||
|
}
|
||||||
|
.footer-logo {
|
||||||
|
text-align: center;
|
||||||
|
}
|
||||||
|
.footer-logo img {
|
||||||
|
height: 20px;
|
||||||
|
width: 20px;
|
||||||
|
}
|
||||||
|
.footer-logo p {
|
||||||
|
font-size: 9px;
|
||||||
|
font-weight: bold;
|
||||||
|
}
|
||||||
|
.footer-powered p {
|
||||||
|
font-size: 11px;
|
||||||
|
}
|
||||||
|
.footer-powered span {
|
||||||
|
font-weight: lighter;
|
||||||
|
}
|
||||||
|
.footer-powered a {
|
||||||
|
color: #112e40;
|
||||||
|
text-decoration: none;
|
||||||
|
font-size: 9px;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
</style>
|
||||||
|
</head>
|
||||||
|
<body>
|
||||||
|
|
||||||
|
<div class="document-header">
|
||||||
|
<div>
|
||||||
|
<h1>{{ dealer_info.name }}</h1>
|
||||||
|
<address>
|
||||||
|
العنوان : {{ dealer_info.address }}<br>
|
||||||
|
البريد الإلكتروني : {{ dealer_info.user.email }}<br>
|
||||||
|
الهاتف : {{ dealer_info.phone_number }}<br>
|
||||||
|
رقم السجل التجاري : {{dealer_info.crn }}<br>
|
||||||
|
الرقم الضريبي : {{ dealer_info.vrn }}
|
||||||
|
</address>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div>
|
||||||
|
<div class="dealer-logo ">
|
||||||
|
{% if dealer_info.logo %}
|
||||||
|
<img class="rounded-soft"
|
||||||
|
style="max-width:100px;
|
||||||
|
max-height:100px"
|
||||||
|
src="{{dealer_info.logo.url|default:'' }}"
|
||||||
|
alt="Dealer Logo" />
|
||||||
|
{% endif %}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div>
|
||||||
|
<h1>عرض سعر</h1>
|
||||||
|
<h2 style="font-size: 18px;">{{ estimate.estimate_number }}</h2>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="document-details">
|
||||||
|
<div class="section">
|
||||||
|
<h2>{{ customer_obj.full_name }}: إلى</h2>
|
||||||
|
<p><span class="label">العميل : {{ customer_obj.full_name }}</span></p>
|
||||||
|
<p><span class="label">البريد الإلكتروني : {{ customer_obj.email |default:"N/A"}}</span></p>
|
||||||
|
<p><span class="label">الهاتف : {{ customer_obj.phone_number|default:"N/A" }}</span></p>
|
||||||
|
<p><span class="label">العنوان : {{ customer_obj.address|default:"N/A" }}</span></p>
|
||||||
|
</div>
|
||||||
|
<div class="section text-left">
|
||||||
|
<h2>التفاصيل:</h2>
|
||||||
|
<p><span class="label">رقم عرض السعر : </span> {{estimate.estimate_number }}</p>
|
||||||
|
<p><span class="label">تاريخ الإصدار : </span> {{ estimate.date_approved|date:"Y/m/d" }}</p>
|
||||||
|
<p><span class="label">طريقة الدفع : </span> {{ estimate.get_terms_display }}</p>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="table-container">
|
||||||
|
<div class="table-header">
|
||||||
|
<span>تفاصيل السيارة</span>
|
||||||
|
</div>
|
||||||
|
<table class="table">
|
||||||
|
<thead>
|
||||||
|
<tr>
|
||||||
|
<th class="text-center">
|
||||||
|
<span>الصانع</span>
|
||||||
|
</th>
|
||||||
|
<th class="text-center">
|
||||||
|
<span>الموديل</span>
|
||||||
|
</th>
|
||||||
|
<th class="text-center">
|
||||||
|
<span>السلسلة</span>
|
||||||
|
</th>
|
||||||
|
<th class="text-center">
|
||||||
|
<span>الفئة</span>
|
||||||
|
</th>
|
||||||
|
<th class="text-center">
|
||||||
|
<span>السنة</span>
|
||||||
|
</th>
|
||||||
|
<th class="text-center">
|
||||||
|
<span>رقم الهيكل</span>
|
||||||
|
</th>
|
||||||
|
</tr>
|
||||||
|
</thead>
|
||||||
|
<tbody>
|
||||||
|
<tr>
|
||||||
|
<td class="text-center">{{ data.car.id_car_make.name }}</td>
|
||||||
|
<td class="text-center">{{ data.car.id_car_model.name }}</td>
|
||||||
|
<td class="text-center">{{ data.car.id_car_serie.name }}</td>
|
||||||
|
<td class="text-center">{{ data.car.id_car_trim.name }}</td>
|
||||||
|
<td class="text-center">{{ data.car.year }}</td>
|
||||||
|
<td class="text-center">{{ data.car.vin }}</td>
|
||||||
|
</tr>
|
||||||
|
</tbody>
|
||||||
|
</table>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="table-container">
|
||||||
|
<div class="table-header">
|
||||||
|
<span>التفاصيل المالية</span>
|
||||||
|
</div>
|
||||||
|
<table class="table">
|
||||||
|
<thead>
|
||||||
|
<tr>
|
||||||
|
<th class="text-center">
|
||||||
|
<span>الكمية</span>
|
||||||
|
</th>
|
||||||
|
<th class="text-center">
|
||||||
|
<span>سعر الوحدة</span>
|
||||||
|
</th>
|
||||||
|
<th class="text-center">
|
||||||
|
<span>الخصم</span>
|
||||||
|
</th>
|
||||||
|
<th class="text-center">
|
||||||
|
<span>الضريبة</span>
|
||||||
|
</th>
|
||||||
|
<th class="text-center">
|
||||||
|
<span>الإجمالي</span>
|
||||||
|
</th>
|
||||||
|
</tr>
|
||||||
|
</thead>
|
||||||
|
<tbody>
|
||||||
|
<tr>
|
||||||
|
<td class="text-center">1</td>
|
||||||
|
<td class="text-center">{{ data.car.marked_price|currency_format}}</td>
|
||||||
|
<td class="text-center">{{ data.discount_amount|currency_format}}</td>
|
||||||
|
<td class="text-center">{{ data.vat_amount|currency_format }}</td>
|
||||||
|
<td class="text-center">{{ data.final_price|currency_format }}</td>
|
||||||
|
</tr>
|
||||||
|
</tbody>
|
||||||
|
</table>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
{% if data.additional_services %}
|
||||||
|
<div class="table-container">
|
||||||
|
<div class="table-header">
|
||||||
|
<span>الخدمات الإضافية</span>
|
||||||
|
</div>
|
||||||
|
<table class="table">
|
||||||
|
<thead>
|
||||||
|
<tr>
|
||||||
|
<th class="text-center">النوع</th>
|
||||||
|
<th class="text-center">القيمة</th>
|
||||||
|
<th class="text-center">ضريبة الخدمة</th>
|
||||||
|
<th class="text-center">الإجمالي</th>
|
||||||
|
</tr>
|
||||||
|
</thead>
|
||||||
|
<tbody>
|
||||||
|
{% for service in data.additional_services.services %}
|
||||||
|
<tr>
|
||||||
|
<td class="text-center">{{ service.0.name }}</td>
|
||||||
|
<td class="text-center">{{ service.0.price|currency_format }}</td>
|
||||||
|
<td class="text-center">{{ service.1|currency_format}}</td>
|
||||||
|
<td class="text-center">{{ service.0.price_|currency_format}}</td>
|
||||||
|
</tr>
|
||||||
|
{% endfor %}
|
||||||
|
</tbody>
|
||||||
|
<tfoot>
|
||||||
|
<tr>
|
||||||
|
<td class="text-center"></td>
|
||||||
|
<td class="text-center">{{ data.total_services_amount|currency_format}}</td>
|
||||||
|
<td class="text-center">{{ data.total_services_vat|currency_format}}</td>
|
||||||
|
<td class="text-center">{{ data.total_services_amount_|currency_format}}</td>
|
||||||
|
</tr>
|
||||||
|
</tfoot>
|
||||||
|
|
||||||
|
</table>
|
||||||
|
</div>
|
||||||
|
{% endif %}
|
||||||
|
|
||||||
|
<div class="document-details" style="margin-top: 30px; ">
|
||||||
|
<div class="section text-left">
|
||||||
|
<p><span class="label">إجمالي ضريبة القيمة المضافة : </span> {{ data.total_vat|currency_format }}</p>
|
||||||
|
<p><span class="label">الإجمالي الكلي : </span> {{ data.grand_total|currency_format}}</p>
|
||||||
|
<p><span class="label">كتابةً : </span> {{ data.grand_total|num_to_words }}</p>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<hr style="border-bottom:1px solid #ccc; ">
|
||||||
|
|
||||||
|
<div class="document-footer">
|
||||||
|
<div class="footer-logo">
|
||||||
|
<img src="{% static 'images/logos/logo-d-pdf.png' %}" alt="Logo" />
|
||||||
|
<p>
|
||||||
|
<span>Haikal</span> | <span>هيكل</span>
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
|
<div class="footer-powered">
|
||||||
|
<p>
|
||||||
|
<span>Powered by </span>
|
||||||
|
<a href="https://tenhal.sa"><span>TENHAL</span> | <span>تنحل</span></a>
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</body>
|
||||||
|
</html>
|
||||||
360
templates/sales/estimates/estimate_preview_en.html
Normal file
360
templates/sales/estimates/estimate_preview_en.html
Normal file
@ -0,0 +1,360 @@
|
|||||||
|
{% load static custom_filters num2words_tags %}
|
||||||
|
<!DOCTYPE html>
|
||||||
|
<html lang="en" dir="ltr">
|
||||||
|
<head>
|
||||||
|
<meta charset="UTF-8">
|
||||||
|
<title>Quotation</title>
|
||||||
|
|
||||||
|
<style>
|
||||||
|
/* General Body and Font Styles */
|
||||||
|
body {
|
||||||
|
font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, "Helvetica Neue", Arial, sans-serif;
|
||||||
|
font-size: 12px;
|
||||||
|
color: #333;
|
||||||
|
margin: 0;
|
||||||
|
padding: 0;
|
||||||
|
position: relative;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Page Layout and Margins for PDF */
|
||||||
|
@page {
|
||||||
|
size: A4;
|
||||||
|
margin: 20mm;
|
||||||
|
@top-left {
|
||||||
|
content: "صفحة " counter(page) " من " counter(pages);
|
||||||
|
font-size: 10px;
|
||||||
|
color: #555;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Header Styles */
|
||||||
|
.document-header {
|
||||||
|
display: flex;
|
||||||
|
justify-content: space-between;
|
||||||
|
align-items: center;
|
||||||
|
border-bottom: 2px solid #333;
|
||||||
|
padding-bottom: 15px;
|
||||||
|
margin-bottom: 20px;
|
||||||
|
}
|
||||||
|
.document-header .logo {
|
||||||
|
max-width: 150px;
|
||||||
|
height: auto;
|
||||||
|
}
|
||||||
|
.document-header h1 {
|
||||||
|
font-size: 24px;
|
||||||
|
margin: 0;
|
||||||
|
color: #0056b3;
|
||||||
|
}
|
||||||
|
.document-header address {
|
||||||
|
text-align: right;
|
||||||
|
font-style: normal;
|
||||||
|
font-size: 10px;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Document Details Section */
|
||||||
|
.document-details {
|
||||||
|
display: flex;
|
||||||
|
justify-content: space-between;
|
||||||
|
margin-bottom: 30px;
|
||||||
|
line-height: 1.6;
|
||||||
|
}
|
||||||
|
.document-details .section {
|
||||||
|
width: 48%;
|
||||||
|
}
|
||||||
|
.document-details h2 {
|
||||||
|
font-size: 14px;
|
||||||
|
border-bottom: 1px solid #ccc;
|
||||||
|
padding-bottom: 5px;
|
||||||
|
margin-bottom: 10px;
|
||||||
|
color: #555;
|
||||||
|
}
|
||||||
|
.document-details p {
|
||||||
|
margin: 0;
|
||||||
|
font-size: 12px;
|
||||||
|
}
|
||||||
|
.document-details .label {
|
||||||
|
font-weight: bold;
|
||||||
|
color: #555;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Table Styles */
|
||||||
|
.table {
|
||||||
|
width: 80%;
|
||||||
|
border-collapse: collapse;
|
||||||
|
margin: 20px auto; /* Centering the table */
|
||||||
|
}
|
||||||
|
.table th, .table td {
|
||||||
|
border: 1px solid #ddd;
|
||||||
|
padding: 8px;
|
||||||
|
text-align: right;
|
||||||
|
}
|
||||||
|
.table th {
|
||||||
|
background-color: #f2f2f2;
|
||||||
|
font-weight: bold;
|
||||||
|
}
|
||||||
|
.table tfoot td {
|
||||||
|
border-top: 2px solid #333;
|
||||||
|
font-weight: bold;
|
||||||
|
}
|
||||||
|
.text-right {
|
||||||
|
text-align: left;
|
||||||
|
}
|
||||||
|
.text-left {
|
||||||
|
text-align: right;
|
||||||
|
}
|
||||||
|
.text-center {
|
||||||
|
text-align: center;2px solid #333;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Responsive and layout helpers */
|
||||||
|
.table-container {
|
||||||
|
width: 100%;
|
||||||
|
overflow-x: auto;
|
||||||
|
margin-bottom: 20px;
|
||||||
|
}
|
||||||
|
.table-header {
|
||||||
|
display: flex;
|
||||||
|
justify-content: space-between;
|
||||||
|
align-items: center;
|
||||||
|
margin-bottom: 5px;
|
||||||
|
}
|
||||||
|
.table-header span {
|
||||||
|
font-size: 9px;
|
||||||
|
font-weight: lighter;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Footer Styles */
|
||||||
|
.document-footer {
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: center;
|
||||||
|
position: relative;
|
||||||
|
bottom: 0;
|
||||||
|
left: 0;
|
||||||
|
width: 100%;
|
||||||
|
text-align: center;
|
||||||
|
font-size: 10px;
|
||||||
|
color: #888;
|
||||||
|
padding-top: 15px;
|
||||||
|
margin: 0 auto; /* This change centers the footer container */
|
||||||
|
}
|
||||||
|
|
||||||
|
.footer-flex {
|
||||||
|
display: flex;
|
||||||
|
justify-content: space-between;
|
||||||
|
align-items: center;
|
||||||
|
width: 100%;
|
||||||
|
}
|
||||||
|
.footer-logo {
|
||||||
|
text-align: center;
|
||||||
|
}
|
||||||
|
.footer-logo img {
|
||||||
|
height: 20px;
|
||||||
|
width: 20px;
|
||||||
|
}
|
||||||
|
.footer-logo p {
|
||||||
|
font-size: 9px;
|
||||||
|
font-weight: bold;
|
||||||
|
}
|
||||||
|
.footer-powered p {
|
||||||
|
font-size: 11px;
|
||||||
|
}
|
||||||
|
.footer-powered span {
|
||||||
|
font-weight: lighter;
|
||||||
|
}
|
||||||
|
.footer-powered a {
|
||||||
|
color: #112e40;
|
||||||
|
text-decoration: none;
|
||||||
|
font-size: 9px;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
</style>
|
||||||
|
</head>
|
||||||
|
<body>
|
||||||
|
|
||||||
|
<div class="document-header">
|
||||||
|
<div>
|
||||||
|
<h1>{{ dealer_info.name }}</h1>
|
||||||
|
<address>
|
||||||
|
Address : {{ dealer_info.address }}<br>
|
||||||
|
Email : {{ dealer_info.user.email }}<br>
|
||||||
|
Phone : {{ dealer_info.phone_number }}<br>
|
||||||
|
Commercial Registration No. : {{dealer_info.crn }}<br>
|
||||||
|
VAT No. : {{ dealer_info.vrn }}
|
||||||
|
</address>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div>
|
||||||
|
<div class="dealer-logo ">
|
||||||
|
{% if dealer_info.logo %}
|
||||||
|
<img class="rounded-soft"
|
||||||
|
style="max-width:100px;
|
||||||
|
max-height:100px"
|
||||||
|
src="{{dealer_info.logo.url|default:'' }}"
|
||||||
|
alt="Dealer Logo" />
|
||||||
|
{% endif %}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div>
|
||||||
|
<h1>Quotation</h1>
|
||||||
|
<h2 style="font-size: 18px;">{{ estimate.estimate_number }}</h2>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="document-details">
|
||||||
|
<div class="section">
|
||||||
|
<h2>To: {{ customer_obj.full_name }}</h2>
|
||||||
|
<p><span class="label">Customer : {{ customer_obj.full_name }}</span></p>
|
||||||
|
<p><span class="label">Email : {{ customer_obj.email |default:"N/A"}}</span></p>
|
||||||
|
<p><span class="label">Phone : {{ customer_obj.phone_number|default:"N/A" }}</span></p>
|
||||||
|
<p><span class="label">Address : {{ customer_obj.address|default:"N/A" }}</span></p>
|
||||||
|
</div>
|
||||||
|
<div class="section text-right">
|
||||||
|
<h2>Details:</h2>
|
||||||
|
<p><span class="label">Quotation Number : </span> {{ estimate.estimate_number }}</p>
|
||||||
|
<p><span class="label">Issue Date : </span> {{ estimate.date_approved|date:"Y/m/d" }}</p>
|
||||||
|
<p><span class="label">Payment Method : </span> {{ estimate.get_terms_display }}</p>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="table-container">
|
||||||
|
<div class="table-header">
|
||||||
|
<span>Car Details</span>
|
||||||
|
</div>
|
||||||
|
<table class="table">
|
||||||
|
<thead>
|
||||||
|
<tr>
|
||||||
|
<th class="text-center">
|
||||||
|
<span>Make</span>
|
||||||
|
</th>
|
||||||
|
<th class="text-center">
|
||||||
|
<span>Model</span>
|
||||||
|
</th>
|
||||||
|
<th class="text-center">
|
||||||
|
<span>Series</span>
|
||||||
|
</th>
|
||||||
|
<th class="text-center">
|
||||||
|
<span>Trim</span>
|
||||||
|
</th>
|
||||||
|
<th class="text-center">
|
||||||
|
<span>Year</span>
|
||||||
|
</th>
|
||||||
|
<th class="text-center">
|
||||||
|
<span>VIN</span>
|
||||||
|
</th>
|
||||||
|
</tr>
|
||||||
|
</thead>
|
||||||
|
<tbody>
|
||||||
|
<tr>
|
||||||
|
<td class="text-center">{{ data.car.id_car_make.name }}</td>
|
||||||
|
<td class="text-center">{{ data.car.id_car_model.name }}</td>
|
||||||
|
<td class="text-center">{{ data.car.id_car_serie.name }}</td>
|
||||||
|
<td class="text-center">{{ data.car.id_car_trim.name }}</td>
|
||||||
|
<td class="text-center">{{ data.car.year }}</td>
|
||||||
|
<td class="text-center">{{ data.car.vin }}</td>
|
||||||
|
</tr>
|
||||||
|
</tbody>
|
||||||
|
</table>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="table-container">
|
||||||
|
<div class="table-header">
|
||||||
|
<span>Financial Details</span>
|
||||||
|
</div>
|
||||||
|
<table class="table">
|
||||||
|
<thead>
|
||||||
|
<tr>
|
||||||
|
<th class="text-center">
|
||||||
|
<span>Quantity</span>
|
||||||
|
</th>
|
||||||
|
<th class="text-center">
|
||||||
|
<span>Unit Price</span>
|
||||||
|
</th>
|
||||||
|
<th class="text-center">
|
||||||
|
<span>Discount</span>
|
||||||
|
</th>
|
||||||
|
<th class="text-center">
|
||||||
|
<span>VAT</span>
|
||||||
|
</th>
|
||||||
|
<th class="text-center">
|
||||||
|
<span>Total</span>
|
||||||
|
</th>
|
||||||
|
</tr>
|
||||||
|
</thead>
|
||||||
|
<tbody>
|
||||||
|
<tr>
|
||||||
|
<td class="text-center">1</td>
|
||||||
|
<td class="text-center">{{ data.car.marked_price|currency_format}}</td>
|
||||||
|
<td class="text-center">{{ data.discount_amount|currency_format }}</td>
|
||||||
|
<td class="text-center">{{ data.vat_amount|currency_format}}</td>
|
||||||
|
<td class="text-center">{{ data.final_price|currency_format}}</td>
|
||||||
|
</tr>
|
||||||
|
</tbody>
|
||||||
|
</table>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
{% if data.additional_services %}
|
||||||
|
<div class="table-container">
|
||||||
|
<div class="table-header">
|
||||||
|
<span>Additional Services</span>
|
||||||
|
</div>
|
||||||
|
<table class="table">
|
||||||
|
<thead>
|
||||||
|
<tr>
|
||||||
|
<th class="text-center">Type</th>
|
||||||
|
<th class="text-center">Value</th>
|
||||||
|
<th class="text-center">Service VAT</th>
|
||||||
|
<th class="text-center">Total</th>
|
||||||
|
</tr>
|
||||||
|
</thead>
|
||||||
|
<tbody>
|
||||||
|
{% for service in data.additional_services.services %}
|
||||||
|
<tr>
|
||||||
|
<td class="text-center">{{ service.0.name }}</td>
|
||||||
|
<td class="text-center">{{ service.0.price|currency_format }}</td>
|
||||||
|
<td class="text-center">{{ service.1|currency_format}}</td>
|
||||||
|
<td class="text-center">{{ service.0.price_|currency_format}}</td>
|
||||||
|
</tr>
|
||||||
|
{% endfor %}
|
||||||
|
</tbody>
|
||||||
|
<tfoot>
|
||||||
|
<tr>
|
||||||
|
<td class="text-center"></td>
|
||||||
|
<td class="text-center">{{ data.total_services_amount|currency_format}}</td>
|
||||||
|
<td class="text-center">{{ data.total_services_vat|currency_format}}</td>
|
||||||
|
<td class="text-center">{{ data.total_services_amount_|currency_format }}</td>
|
||||||
|
</tr>
|
||||||
|
</tfoot>
|
||||||
|
|
||||||
|
</table>
|
||||||
|
</div>
|
||||||
|
{% endif %}
|
||||||
|
|
||||||
|
<div class="document-details" style="margin-top: 30px; ">
|
||||||
|
<div class="section text-right">
|
||||||
|
<p><span class="label">Total VAT : </span> {{ data.total_vat|currency_format}}</p>
|
||||||
|
<p><span class="label">Grand Total : </span> {{ data.grand_total|currency_format }}</p>
|
||||||
|
<p><span class="label">In words : </span> {{ data.grand_total|num_to_words }}</p>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<hr style="border-bottom:1px solid #ccc; ">
|
||||||
|
|
||||||
|
<div class="document-footer">
|
||||||
|
<div class="footer-logo">
|
||||||
|
<img src="{% static 'images/logos/logo-d-pdf.png' %}" alt="Logo" />
|
||||||
|
<p>
|
||||||
|
<span>Haikal</span> | <span>هيكل</span>
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
|
<div class="footer-powered">
|
||||||
|
<p>
|
||||||
|
<span>Powered by </span>
|
||||||
|
<a href="https://tenhal.sa"><span>TENHAL</span> | <span>تنحل</span></a>
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</body>
|
||||||
|
</html>
|
||||||
@ -28,7 +28,7 @@
|
|||||||
<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 'home' request.dealer.slug %}"
|
<a href="{% url 'estimate_detail' request.dealer.slug estimate.pk %}"
|
||||||
class="btn btn-phoenix-secondary btn-lg">
|
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" %}
|
||||||
@ -40,3 +40,4 @@
|
|||||||
</div>
|
</div>
|
||||||
</main>
|
</main>
|
||||||
{% endblock content %}
|
{% endblock content %}
|
||||||
|
|
||||||
|
|||||||
@ -143,7 +143,17 @@
|
|||||||
{% endif %}
|
{% endif %}
|
||||||
{% endif %}
|
{% endif %}
|
||||||
<a href="{% url 'invoice_preview' request.dealer.slug invoice.pk %}"
|
<a href="{% url 'invoice_preview' request.dealer.slug invoice.pk %}"
|
||||||
class="btn btn-phoenix-primary"><span class="d-none d-sm-inline-block"><i class="fa-regular fa-eye"></i> {% trans 'Preview' %}</span></a>
|
class="btn btn-phoenix-primary" target="_blank" rel="noopener noreferrer"><span class="d-none d-sm-inline-block" ><i class="fa-regular fa-eye"></i> {% trans 'Preview' %}</span></a>
|
||||||
|
<a href="{% url 'invoice_preview' request.dealer.slug invoice.pk %}?lang=en"
|
||||||
|
class="btn btn-phoenix-secondary"
|
||||||
|
target="_blank">
|
||||||
|
<span class="d-none d-sm-inline-block"><i class="fas fa-print me-2"></i>{% trans 'Print EN' %}</span>
|
||||||
|
</a>
|
||||||
|
<a href="{% url 'invoice_preview' request.dealer.slug invoice.pk %}?lang=ar"
|
||||||
|
class="btn btn-phoenix-secondary"
|
||||||
|
target="_blank">
|
||||||
|
<span class="d-none d-sm-inline-block"><i class="fas fa-print me-2"></i>{% trans 'Print AR' %}</span>
|
||||||
|
</a>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
{{ invoice.amount_owned }}
|
{{ invoice.amount_owned }}
|
||||||
@ -369,7 +379,7 @@
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</section>
|
</section>
|
||||||
{% endblock %}
|
{% endblock %}
|
||||||
{% block customJS %}
|
{% block customJS %}
|
||||||
<script>
|
<script>
|
||||||
|
|||||||
@ -4,69 +4,80 @@
|
|||||||
{{ _("Invoices") }}
|
{{ _("Invoices") }}
|
||||||
{% endblock title %}
|
{% endblock title %}
|
||||||
{% block content %}
|
{% block content %}
|
||||||
|
<div class="container-fluid py-5">
|
||||||
{% if invoices or request.GET.q %}
|
{% if invoices or request.GET.q %}
|
||||||
<div class="row mt-4">
|
<div class="card shadow-lg border-0 rounded-4">
|
||||||
<div class="row g-3 justify-content-between mb-4">
|
<div class="card-body p-4 p-md-5">
|
||||||
<div class="col-auto">
|
|
||||||
<div class="d-md-flex justify-content-between">
|
<div class="d-flex flex-column flex-md-row justify-content-between align-items-md-center mb-4">
|
||||||
<h2 class="mb-3">
|
<div class="mb-3 mb-md-0">
|
||||||
{% trans "Invoices" %}<i class="fa-solid fa-receipt ms-2 text-primary"></i>
|
<h1 class="display-5 fw-bolder mb-0">
|
||||||
</h2>
|
{% trans "Invoices" %}
|
||||||
|
<i class="fa-solid fa-receipt ms-2 text-primary"></i>
|
||||||
|
</h1>
|
||||||
|
<p class="text-secondary mt-1 mb-0">
|
||||||
|
{% trans "Manage and track all your customer invoices." %}
|
||||||
|
</p>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div class="col-auto">
|
|
||||||
<div class="d-flex">{% include 'partials/search_box.html' %}</div>
|
{% comment %}
|
||||||
|
<div class="d-flex justify-content-end mb-4">
|
||||||
|
{% include 'partials/search_box.html' %}
|
||||||
</div>
|
</div>
|
||||||
</div>
|
{% endcomment %}
|
||||||
<div class="table-responsive px-1 scrollbar">
|
|
||||||
<table class="table align-items-center table-flush">
|
<div class="table-responsive">
|
||||||
<thead>
|
<table class="table table-hover align-middle">
|
||||||
<tr class="bg-body-highlight">
|
<thead class="bg-light">
|
||||||
<th class="sort white-space-nowrap align-middle" scope="col">{% trans "Invoice Number" %}</th>
|
<tr class="text-uppercase text-secondary fw-bold small">
|
||||||
<th class="sort white-space-nowrap align-middle" scope="col">{% trans "Customer" %}</th>
|
<th scope="col" style="width:20%">{% trans "Invoice Number" %}</th>
|
||||||
<th class="sort white-space-nowrap align-middle" scope="col">{% trans "Status" %}</th>
|
<th scope="col" style="width:20%">{% trans "Customer" %}</th>
|
||||||
<th class="sort white-space-nowrap align-middle" scope="col">{% trans "Status Date" %}</th>
|
<th scope="col" style="width:15%">{% trans "Status" %}</th>
|
||||||
<th class="sort white-space-nowrap align-middle" scope="col">{% trans "Created" %}</th>
|
<th scope="col" style="width:20%">{% trans "Status Date" %}</th>
|
||||||
<th class="sort white-space-nowrap align-middle" scope="col">{% trans "Actions" %}</th>
|
<th scope="col" style="width:15%">{% trans "Created" %}</th>
|
||||||
|
<th scope="col" style="width:10%" class="text-end">{% trans "Actions" %}</th>
|
||||||
</tr>
|
</tr>
|
||||||
</thead>
|
</thead>
|
||||||
<tbody class="list">
|
<tbody>
|
||||||
{% for invoice in invoices %}
|
{% for invoice in invoices %}
|
||||||
<tr class="hover-actions-trigger btn-reveal-trigger position-static">
|
<tr class="hover-shadow">
|
||||||
<td class="align-middle product white-space-nowrap py-0 px-1">{{ invoice.invoice_number }}</td>
|
<td class="align-middle fw-semibold ">{{ invoice.invoice_number }}</td>
|
||||||
<td class="align-middle product white-space-nowrap">{{ invoice.customer }}</td>
|
<td class="align-middle text-body-tertiary">{{ invoice.customer }}</td>
|
||||||
<td class="align-middle product white-space-nowrap text-success">
|
<td class="align-middle">
|
||||||
{% if invoice.is_past_due %}
|
{% if invoice.is_past_due %}
|
||||||
<span class="badge badge-phoenix badge-phoenix-danger">{% trans "Past Due" %}</span>
|
<span class="badge rounded-pill text-bg-danger">{% trans "Past Due" %}</span>
|
||||||
{% elif invoice.is_approved %}
|
{% elif invoice.is_approved %}
|
||||||
<span class="badge badge-phoenix badge-phoenix-success">{% trans "Approved" %}</span>
|
<span class="badge rounded-pill text-bg-success">{% trans "Approved" %}</span>
|
||||||
{% elif invoice.is_canceled %}
|
{% elif invoice.is_canceled %}
|
||||||
<span class="badge badge-phoenix badge-phoenix-secondary">{% trans "Canceled" %}</span>
|
<span class="badge rounded-pill text-bg-secondary">{% trans "Canceled" %}</span>
|
||||||
{% elif invoice.is_draft %}
|
{% elif invoice.is_draft %}
|
||||||
<span class="badge badge-phoenix badge-phoenix-warning">{% trans "Draft" %}</span>
|
<span class="badge rounded-pill text-bg-warning">{% trans "Draft" %}</span>
|
||||||
{% elif invoice.is_review %}
|
{% elif invoice.is_review %}
|
||||||
<span class="badge badge-phoenix badge-phoenix-info">{% trans "In Review" %}</span>
|
<span class="badge rounded-pill text-bg-info">{% trans "In Review" %}</span>
|
||||||
{% elif invoice.is_paid %}
|
{% elif invoice.is_paid %}
|
||||||
<span class="badge badge-phoenix badge-phoenix-success">{% trans "Paid" %}</span>
|
<span class="badge rounded-pill text-bg-success">{% trans "Paid" %}</span>
|
||||||
{% endif %}
|
{% endif %}
|
||||||
</td>
|
</td>
|
||||||
<td class="align-middle product white-space-nowrap">
|
<td class="align-middle text-body-tertiary">
|
||||||
{% if invoice.invoice_status == "in_review" %}
|
{% if invoice.invoice_status == "in_review" %}
|
||||||
{{ invoice.date_in_review }}
|
{{ invoice.date_in_review|date:"M d, Y"|default:"N/A" }}
|
||||||
{% elif invoice.invoice_status == "approved" %}
|
{% elif invoice.invoice_status == "approved" %}
|
||||||
{{ invoice.date_approved }}
|
{{ invoice.date_approved|date:"M d, Y"|default:"N/A" }}
|
||||||
{% elif invoice.invoice_status == "canceled" %}
|
{% elif invoice.invoice_status == "canceled" %}
|
||||||
{{ invoice.date_canceled }}
|
{{ invoice.date_canceled|date:"M d, Y"|default:"N/A" }}
|
||||||
{% elif invoice.invoice_status == "draft" %}
|
{% elif invoice.invoice_status == "draft" %}
|
||||||
{{ invoice.date_draft }}
|
{{ invoice.date_draft|date:"M d, Y"|default:"N/A" }}
|
||||||
{% elif invoice.invoice_status == "paid" %}
|
{% elif invoice.invoice_status == "paid" %}
|
||||||
{{ invoice.date_paid }}
|
{{ invoice.date_paid|date:"M d, Y"|default:"N/A" }}
|
||||||
{% endif %}
|
{% endif %}
|
||||||
</td>
|
</td>
|
||||||
<td class="align-middle product white-space-nowrap">{{ invoice.created }}</td>
|
<td class="align-middle text-body-tertiary">
|
||||||
<td class="align-middle product white-space-nowrap">
|
{{ invoice.created|date:"M d, Y" }}
|
||||||
|
</td>
|
||||||
|
<td class="align-middle text-end">
|
||||||
<a href="{% url 'invoice_detail' request.dealer.slug request.entity.slug invoice.pk %}"
|
<a href="{% url 'invoice_detail' request.dealer.slug request.entity.slug invoice.pk %}"
|
||||||
class="btn btn-sm btn-phoenix-success">
|
class="btn btn-sm btn-primary">
|
||||||
<i class="fa-regular fa-eye me-1"></i>
|
<i class="fa-regular fa-eye me-1"></i>
|
||||||
{% trans "View" %}
|
{% trans "View" %}
|
||||||
</a>
|
</a>
|
||||||
@ -74,20 +85,24 @@
|
|||||||
</tr>
|
</tr>
|
||||||
{% empty %}
|
{% empty %}
|
||||||
<tr>
|
<tr>
|
||||||
<td colspan="6" class="text-center">{% trans "No Invoice Found" %}</td>
|
<td colspan="6" class="text-center py-5 text-muted">{% trans "No invoices found." %}</td>
|
||||||
</tr>
|
</tr>
|
||||||
{% endfor %}
|
{% endfor %}
|
||||||
</tbody>
|
</tbody>
|
||||||
</table>
|
</table>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
{% if page_obj.paginator.num_pages > 1 %}
|
{% if page_obj.paginator.num_pages > 1 %}
|
||||||
<div class="d-flex justify-content-end mt-3">
|
<div class="d-flex justify-content-end mt-4">
|
||||||
<div class="d-flex">{% include 'partials/pagination.html' %}</div>
|
{% include 'partials/pagination.html' %}
|
||||||
</div>
|
</div>
|
||||||
{% endif %}
|
{% endif %}
|
||||||
|
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
{% else %}
|
{% else %}
|
||||||
{% url 'estimate_create' request.dealer.slug as url %}
|
{% url 'estimate_create' request.dealer.slug as create_url %}
|
||||||
{% include "empty-illustration-page.html" with value=_("invoice") url=url %}
|
{% include "empty-illustration-page.html" with title=_("No Invoices Yet") subtitle=_("Looks like you haven't created any invoices. Start by creating a new invoice or quotation.") url=create_url button_text=_("Create First Invoice") %}
|
||||||
{% endif %}
|
{% endif %}
|
||||||
|
</div>
|
||||||
{% endblock %}
|
{% endblock %}
|
||||||
@ -1,367 +1,333 @@
|
|||||||
{% load i18n static custom_filters num2words_tags %}
|
{% load i18n static custom_filters num2words_tags %}
|
||||||
<!DOCTYPE html>
|
<!DOCTYPE html>
|
||||||
<html lang="ar" dir="rtl">
|
<html lang="ar">
|
||||||
<head>
|
<head>
|
||||||
<meta charset="UTF-8">
|
<meta charset="UTF-8">
|
||||||
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
<title>{% trans "Invoice Preview" %}</title>
|
||||||
<title>{% trans "Invoice" %}</title>
|
|
||||||
<link href="{% static 'css/theme.min.css' %}"
|
|
||||||
type="text/css"
|
|
||||||
rel="stylesheet"
|
|
||||||
id="style-default">
|
|
||||||
<link href="{% static 'css/user.min.css' %}"
|
|
||||||
type="text/css"
|
|
||||||
rel="stylesheet"
|
|
||||||
id="user-style-default">
|
|
||||||
<link href="{% static 'css/custom.css' %}" type="text/css" rel="stylesheet">
|
|
||||||
<link href="https://fonts.googleapis.com/css2?family=Roboto:wght@300;400;500;700&display=swap"
|
|
||||||
rel="stylesheet">
|
|
||||||
<style>
|
<style>
|
||||||
|
/* General Body and Font Styles */
|
||||||
body {
|
body {
|
||||||
font-family: 'Roboto', sans-serif;
|
font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, "Helvetica Neue", Arial, sans-serif;
|
||||||
margin: 0;
|
|
||||||
padding: 0;
|
|
||||||
background-color: #f8f9fa;
|
|
||||||
}
|
|
||||||
|
|
||||||
.invoice-container {
|
|
||||||
width: 210mm;
|
|
||||||
min-height: 297mm;
|
|
||||||
padding: 10mm;
|
|
||||||
margin: 10mm auto;
|
|
||||||
background: white;
|
|
||||||
border-radius: 5px;
|
|
||||||
box-shadow: 0 0 5px rgba(0, 0, 0, 0.1);
|
|
||||||
display: flex;
|
|
||||||
flex-direction: column;
|
|
||||||
}
|
|
||||||
|
|
||||||
.invoice-content {
|
|
||||||
flex-grow: 1;
|
|
||||||
}
|
|
||||||
|
|
||||||
.invoice-header {
|
|
||||||
text-align: center;
|
|
||||||
border-bottom: 2px solid #dee2e6;
|
|
||||||
padding-bottom: 10px;
|
|
||||||
margin-bottom: 20px;
|
|
||||||
}
|
|
||||||
|
|
||||||
.qr-code {
|
|
||||||
text-align: center;
|
|
||||||
margin-top: 10px;
|
|
||||||
}
|
|
||||||
|
|
||||||
.qr-code img {
|
|
||||||
width: 3cm;
|
|
||||||
height: 3cm;
|
|
||||||
border-radius: 0.3333333333rem;
|
|
||||||
}
|
|
||||||
|
|
||||||
.invoice-details, .invoice-table {
|
|
||||||
font-size: 14px;
|
font-size: 14px;
|
||||||
|
color: #333;
|
||||||
|
background-color: #f7f7f7;
|
||||||
|
margin: 0;
|
||||||
|
padding: 20px;
|
||||||
}
|
}
|
||||||
|
|
||||||
.invoice-table th {
|
/* Container for the document content */
|
||||||
background-color: #f8f9fa;
|
.document-container {
|
||||||
font-weight: 600;
|
max-width: 800px;
|
||||||
|
margin: 0 auto;
|
||||||
|
background-color: #fff;
|
||||||
|
padding: 40px;
|
||||||
|
box-shadow: 0 4px 10px rgba(0, 0, 0, 0.1);
|
||||||
|
border-radius: 8px;
|
||||||
}
|
}
|
||||||
|
|
||||||
.invoice-total {
|
/* Header Styles */
|
||||||
text-align: right;
|
.document-header {
|
||||||
font-size: 16px;
|
|
||||||
font-weight: 600;
|
|
||||||
margin-top: 10px;
|
|
||||||
}
|
|
||||||
|
|
||||||
.footer-note {
|
|
||||||
margin-top: auto;
|
|
||||||
padding-top: 10mm;
|
|
||||||
font-size: 13px;
|
|
||||||
display: flex;
|
display: flex;
|
||||||
justify-content: space-between;
|
justify-content: space-between;
|
||||||
align-items: center;
|
align-items: center;
|
||||||
border-top: 2px solid #dee2e6; /* Add a top border to separate from content */
|
border-bottom: 2px solid #333;
|
||||||
|
padding-bottom: 15px;
|
||||||
|
margin-bottom: 20px;
|
||||||
|
}
|
||||||
|
.document-header .logo {
|
||||||
|
max-width: 150px;
|
||||||
|
height: auto;
|
||||||
|
}
|
||||||
|
.document-header h1 {
|
||||||
|
font-size: 24px;
|
||||||
|
margin: 0;
|
||||||
|
color: #0056b3;
|
||||||
|
}
|
||||||
|
.document-header h2 {
|
||||||
|
font-size: 18px;
|
||||||
|
margin: 0;
|
||||||
|
}
|
||||||
|
.document-header address {
|
||||||
|
text-align: right;
|
||||||
|
font-style: normal;
|
||||||
|
font-size: 10px;
|
||||||
}
|
}
|
||||||
|
|
||||||
.logo-img img {
|
/* Document Details Section */
|
||||||
width: 10mm;
|
.document-details {
|
||||||
height: 10mm;
|
display: flex;
|
||||||
|
justify-content: space-between;
|
||||||
|
margin-bottom: 30px;
|
||||||
|
line-height: 1.6;
|
||||||
|
}
|
||||||
|
.document-details .section {
|
||||||
|
width: 48%;
|
||||||
|
}
|
||||||
|
.document-details h2 {
|
||||||
|
font-size: 14px;
|
||||||
|
border-bottom: 1px solid #ccc;
|
||||||
|
padding-bottom: 5px;
|
||||||
|
margin-bottom: 10px;
|
||||||
|
color: #555;
|
||||||
|
}
|
||||||
|
.document-details p {
|
||||||
|
margin: 0;
|
||||||
|
font-size: 12px;
|
||||||
|
}
|
||||||
|
.document-details .label {
|
||||||
|
font-weight: bold;
|
||||||
|
color: #555;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Table Styles */
|
||||||
|
.table {
|
||||||
|
width: 100%;
|
||||||
|
border-collapse: collapse;
|
||||||
|
margin: 20px 0;
|
||||||
|
font-size: 12px;
|
||||||
|
}
|
||||||
|
.table th, .table td {
|
||||||
|
border: 1px solid #ddd;
|
||||||
|
padding: 8px;
|
||||||
|
text-align: right;
|
||||||
|
}
|
||||||
|
.table th {
|
||||||
|
background-color: #f2f2f2;
|
||||||
|
font-weight: bold;
|
||||||
|
}
|
||||||
|
.table tfoot td {
|
||||||
|
border-top: 2px solid #333;
|
||||||
|
font-weight: bold;
|
||||||
|
}
|
||||||
|
.text-right {
|
||||||
|
text-align: left;
|
||||||
|
}
|
||||||
|
.text-left {
|
||||||
|
text-align: right;
|
||||||
|
}
|
||||||
|
.text-center {
|
||||||
|
text-align: center;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Responsive and layout helpers */
|
||||||
|
.table-container {
|
||||||
|
width: 100%;
|
||||||
|
overflow-x: auto;
|
||||||
|
margin-bottom: 20px;
|
||||||
|
}
|
||||||
|
.table-header {
|
||||||
|
display: flex;
|
||||||
|
justify-content: space-between;
|
||||||
|
align-items: center;
|
||||||
|
margin-bottom: 5px;
|
||||||
|
}
|
||||||
|
.table-header span {
|
||||||
|
font-size: 14px;
|
||||||
|
font-weight: normal;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Footer Styles */
|
||||||
|
.document-footer {
|
||||||
|
text-align: center;
|
||||||
|
font-size: 10px;
|
||||||
|
color: #888;
|
||||||
|
border-top: 1px solid #ddd;
|
||||||
|
padding-top: 15px;
|
||||||
|
margin-top: 30px;
|
||||||
|
}
|
||||||
|
.footer-logo img {
|
||||||
|
height: 20px;
|
||||||
|
width: 20px;
|
||||||
|
}
|
||||||
|
.footer-logo p {
|
||||||
|
font-size: 9px;
|
||||||
|
font-weight: bold;
|
||||||
|
margin: 5px 0 0;
|
||||||
|
}
|
||||||
|
.footer-powered p {
|
||||||
|
font-size: 11px;
|
||||||
|
margin: 0;
|
||||||
|
}
|
||||||
|
.footer-powered span {
|
||||||
|
font-weight: lighter;
|
||||||
|
}
|
||||||
|
.footer-powered a {
|
||||||
|
color: #112e40;
|
||||||
|
text-decoration: none;
|
||||||
|
font-size: 9px;
|
||||||
}
|
}
|
||||||
</style>
|
</style>
|
||||||
</head>
|
</head>
|
||||||
<body>
|
<body>
|
||||||
<div class="row p-2">
|
<div class="document-container">
|
||||||
<div class="col-2">
|
<div class="document-header">
|
||||||
<button class="btn btn-sm btn-phoenix-danger w-100"
|
<div>
|
||||||
onclick="window.history.back()">الرجوع / Back</button>
|
<h1>{{ dealer_info.name }}</h1>
|
||||||
|
<address>
|
||||||
|
العنوان : {{ dealer_info.address }}<br>
|
||||||
|
البريد الإلكتروني : {{ dealer_info.user.email }}<br>
|
||||||
|
الهاتف : {{ dealer_info.phone_number }}<br>
|
||||||
|
رقم السجل التجاري : {{dealer_info.crn }} | الرقم الضريبي : {{ dealer_info.vrn }}
|
||||||
|
</address>
|
||||||
</div>
|
</div>
|
||||||
<div class="col-2">
|
<div>
|
||||||
<button class="btn btn-sm btn-phoenix-primary w-100" id="download-pdf">تحميل / Download</button>
|
<h1>عرض سعر</h1>
|
||||||
</div>
|
<h2>{{ estimate.estimate_number }}</h2>
|
||||||
<div class="col-8"></div>
|
|
||||||
</div>
|
|
||||||
<div class="invoice-container" id="invoice-content">
|
|
||||||
<div class="invoice-content">
|
|
||||||
<div class="invoice-header">
|
|
||||||
<h5 class="fs-5">
|
|
||||||
<span>Invoice</span> / <span>فاتورة</span>
|
|
||||||
</h5>
|
|
||||||
</div>
|
|
||||||
<div class="invoice-details p-1">
|
|
||||||
<div class="d-flex justify-content-around align-items-end">
|
|
||||||
<div class="d-flex justify-content-start align-items-center">
|
|
||||||
<div class="qr-code">
|
|
||||||
<img src="{% static 'qr_code/Marwan_qr.png' %}" alt="QR Code">
|
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div class="dealer-logo ">
|
|
||||||
{% if request.dealer.logo %}
|
<div class="document-details">
|
||||||
<img class="rounded-soft"
|
<div class="section">
|
||||||
style="max-width:150px;
|
<h2>{{ estimate.customer.customer_name }}: إلى</h2>
|
||||||
max-height:150px"
|
<p><span class="label">العميل : {{ customer_obj.full_name }}</span></p>
|
||||||
src="{{ request.dealer.logo.url|default:'' }}"
|
<p><span class="label">البريد الإلكتروني : {{ customer_obj.email |default:"N/A"}}</span></p>
|
||||||
alt="Dealer Logo" />
|
<p><span class="label">الهاتف : {{ customer_obj.phone_number|default:"N/A" }}</span></p>
|
||||||
{% endif %}
|
<p><span class="label">العنوان : {{ customer_obj.address|default:"N/A" }}</span></p>
|
||||||
|
</div>
|
||||||
|
<div class="section text-left">
|
||||||
|
<h2>التفاصيل:</h2>
|
||||||
|
<p><span class="label">رقم عرض السعر : </span> {{ estimate.estimate_number }}</p>
|
||||||
|
<p><span class="label">تاريخ الإصدار : </span> {{ estimate.date_approved|date:"Y/m/d" }}</p>
|
||||||
|
<p><span class="label">طريقة الدفع : </span> {{ estimate.get_terms_display }}</p>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<table class="table table-sm table-responsive border-gray-50">
|
|
||||||
<tr>
|
<div class="table-container">
|
||||||
<td class="ps-1">
|
<div class="table-header">
|
||||||
<strong>Dealership Name</strong>
|
<span>تفاصيل السيارة</span>
|
||||||
</td>
|
|
||||||
<td class="text-center">{{ request.dealer.name }}</td>
|
|
||||||
<td class="text-end">
|
|
||||||
<strong>اسم الوكالة</strong>
|
|
||||||
</td>
|
|
||||||
</tr>
|
|
||||||
<tr>
|
|
||||||
<td class="ps-1">
|
|
||||||
<strong>Dealership Address</strong>
|
|
||||||
</td>
|
|
||||||
<td class="text-center">{{ request.dealer.address }}</td>
|
|
||||||
<td class="text-end">
|
|
||||||
<strong>عنوان الوكالة</strong>
|
|
||||||
</td>
|
|
||||||
</tr>
|
|
||||||
<tr>
|
|
||||||
<td class="ps-1">
|
|
||||||
<strong>Phone</strong>
|
|
||||||
</td>
|
|
||||||
<td class="text-center">{{ request.dealer.phone_number }}</td>
|
|
||||||
<td class="text-end">
|
|
||||||
<strong>جوال</strong>
|
|
||||||
</td>
|
|
||||||
</tr>
|
|
||||||
<tr>
|
|
||||||
<td>
|
|
||||||
<strong>VAT Number</strong>
|
|
||||||
</td>
|
|
||||||
<td class="text-center">{{ request.dealer.vrn }}</td>
|
|
||||||
<td class="text-end">
|
|
||||||
<strong>الرقم الضريبي</strong>
|
|
||||||
</td>
|
|
||||||
</tr>
|
|
||||||
</table>
|
|
||||||
<table class="table table-sm table-bordered border-gray-50 ">
|
|
||||||
<tr>
|
|
||||||
<td class="ps-1">
|
|
||||||
<strong>Invoice Number</strong>
|
|
||||||
</td>
|
|
||||||
<td class="text-center">{{ invoice.invoice_number }}</td>
|
|
||||||
<td class="text-end p-1">
|
|
||||||
<strong>رقم الفاتورة</strong>
|
|
||||||
</td>
|
|
||||||
</tr>
|
|
||||||
<tr>
|
|
||||||
<td class="ps-1">
|
|
||||||
<strong>Date</strong>
|
|
||||||
</td>
|
|
||||||
<td class="text-center">{{ invoice.date_approved| date:"Y/m/d" }}</td>
|
|
||||||
<td class="text-end p-1">
|
|
||||||
<strong>التاريخ</strong>
|
|
||||||
</td>
|
|
||||||
</tr>
|
|
||||||
<tr>
|
|
||||||
<td class="ps-1">
|
|
||||||
<strong>Customer Name</strong>
|
|
||||||
</td>
|
|
||||||
<td class="text-center">{{ invoice.customer.customer_name }}</td>
|
|
||||||
<td class="text-end p-1">
|
|
||||||
<strong>اسم العميل</strong>
|
|
||||||
</td>
|
|
||||||
</tr>
|
|
||||||
<tr>
|
|
||||||
<td class="ps-1">
|
|
||||||
<strong>Email</strong>
|
|
||||||
</td>
|
|
||||||
<td class="text-center">{{ invoice.customer.email |default:"N/A" }}</td>
|
|
||||||
<td class="text-end p-1">
|
|
||||||
<strong>البريد الإلكتروني</strong>
|
|
||||||
</td>
|
|
||||||
</tr>
|
|
||||||
<tr>
|
|
||||||
<td class="ps-1">
|
|
||||||
<strong>Terms</strong>
|
|
||||||
</td>
|
|
||||||
<td class="text-center">{{ invoice.get_terms_display }}</td>
|
|
||||||
<td class="text-end p-1">
|
|
||||||
<strong>شروط الدفع</strong>
|
|
||||||
</td>
|
|
||||||
</tr>
|
|
||||||
</table>
|
|
||||||
</div>
|
</div>
|
||||||
<div class="d-flex justify-content-between">
|
<table class="table">
|
||||||
<span class="fs-9 fw-thin">Car Details</span>
|
|
||||||
<span class="fs-9 fw-thin">تفاصيل السيارة</span>
|
|
||||||
</div>
|
|
||||||
<div class="invoice-table p-1">
|
|
||||||
<table class="table table-sm table-bordered m-1">
|
|
||||||
<thead>
|
<thead>
|
||||||
<tr>
|
<tr>
|
||||||
<th class="text-wrap text-center align-content-center">
|
<th class="text-center">
|
||||||
<span class="fs-10">Make</span> / <span class="fs-10">الصانع</span>
|
<span>الصانع</span>
|
||||||
</th>
|
</th>
|
||||||
<th class="text-wrap text-center align-content-center">
|
<th class="text-center">
|
||||||
<span class="fs-10">Model</span> / <span class="fs-10">الموديل</span>
|
<span>الموديل</span>
|
||||||
</th>
|
</th>
|
||||||
<th class="text-wrap text-center align-content-center">
|
<th class="text-center">
|
||||||
<span class="fs-10">Series</span> / <span class="fs-10">السلسلة</span>
|
<span>السلسلة</span>
|
||||||
</th>
|
</th>
|
||||||
<th class="text-wrap text-center align-content-center">
|
<th class="text-center">
|
||||||
<span class="fs-10">Trim</span> / <span class="fs-10">الفئة</span>
|
<span>الفئة</span>
|
||||||
</th>
|
</th>
|
||||||
<th class="text-wrap text-center align-content-center">
|
<th class="text-center">
|
||||||
<span class="fs-10">Year</span> / <span class="fs-10">السنة</span>
|
<span>السنة</span>
|
||||||
</th>
|
</th>
|
||||||
<th class="text-wrap text-center align-content-center">
|
<th class="text-center">
|
||||||
<span class="fs-10">VIN</span> / <span class="fs-10">رقم الهيكل</span>
|
<span>رقم الهيكل</span>
|
||||||
</th>
|
|
||||||
<th class="text-wrap text-center align-content-center">
|
|
||||||
<span class="fs-10">Quantity</span> / <span class="fs-10">الكمية</span>
|
|
||||||
</th>
|
|
||||||
<th class="text-wrap text-center align-content-center">
|
|
||||||
<span class="fs-10">Unit Price</span> / <span class="fs-10">سعر الوحدة</span>
|
|
||||||
</th>
|
|
||||||
<th class="text-wrap text-center align-content-center">
|
|
||||||
<span class="fs-10">Discount</span> / <span class="fs-10">الخصم</span>
|
|
||||||
</th>
|
|
||||||
<th class="text-wrap text-center align-content-center">
|
|
||||||
<span class="fs-10">VAT</span> / <span class="fs-10">الضريبة</span>
|
|
||||||
</th>
|
|
||||||
<th class="text-wrap text-center align-content-center">
|
|
||||||
<span class="fs-10">Total</span> / <span class="fs-10">الإجمالي</span>
|
|
||||||
</th>
|
</th>
|
||||||
</tr>
|
</tr>
|
||||||
</thead>
|
</thead>
|
||||||
<tbody>
|
<tbody>
|
||||||
<tr>
|
<tr>
|
||||||
<td class="ps-1 fs-10 align-content-center">{{ data.car.id_car_make.name }}</td>
|
<td class="text-center">{{ data.car.id_car_make.name }}</td>
|
||||||
<td class="ps-1 fs-10 align-content-center">{{ data.car.id_car_model.name }}</td>
|
<td class="text-center">{{ data.car.id_car_model.name }}</td>
|
||||||
<td class="ps-1 fs-10 align-content-center">{{ data.car.id_car_serie.name }}</td>
|
<td class="text-center">{{ data.car.id_car_serie.name }}</td>
|
||||||
<td class="ps-1 fs-10 align-content-center">{{ data.car.id_car_trim.name }}</td>
|
<td class="text-center">{{ data.car.id_car_trim.name }}</td>
|
||||||
<td class="text-center fs-10 align-content-center">{{ data.car.year }}</td>
|
<td class="text-center">{{ data.car.year }}</td>
|
||||||
<td class="ps-1 fs-10 align-content-center">{{ data.car.vin }}</td>
|
<td class="text-center">{{ data.car.vin }}</td>
|
||||||
<td class="text-center fs-10 align-content-center">1</td>
|
|
||||||
<td class="text-center fs-10 align-content-center">{{ data.car.marked_price |floatformat:2 }}</td>
|
|
||||||
<td class="text-center fs-10 align-content-center">{{ data.discount_amount |floatformat:2 }}</td>
|
|
||||||
<td class="text-center fs-10 align-content-center">{{ data.vat_amount|floatformat:2 }}</td>
|
|
||||||
<td class="text-center fs-10 align-content-center">{{ data.final_price|floatformat:2 }}</td>
|
|
||||||
</tr>
|
</tr>
|
||||||
</tbody>
|
</tbody>
|
||||||
</table>
|
</table>
|
||||||
</div>
|
</div>
|
||||||
<div class="d-flex justify-content-between">
|
|
||||||
<span class="fs-9 fw-thin">Additional Services</span>
|
<div class="table-container">
|
||||||
<span class="fs-9 fw-thin">الخدمات الإضافية</span>
|
<div class="table-header">
|
||||||
|
<span>التفاصيل المالية</span>
|
||||||
</div>
|
</div>
|
||||||
{% if data.additional_services %}
|
<table class="table">
|
||||||
<div class="invoice-table p-1">
|
|
||||||
<table class="table table-sm table-bordered m-1">
|
|
||||||
<thead>
|
<thead>
|
||||||
<tr>
|
<tr>
|
||||||
<th class="text-center fs-10 align-content-center">Type / النوع</th>
|
<th class="text-center">
|
||||||
<th class="text-center fs-10 align-content-center">Price / السعر</th>
|
<span>الكمية</span>
|
||||||
<th class="text-center fs-10 align-content-center">Service VAT / ضريبة الخدمة</th>
|
|
||||||
<th class="text-center fs-10 align-content-center">
|
|
||||||
<span class="fs-10">Total</span> / <span class="fs-10">الإجمالي</span>
|
|
||||||
</th>
|
</th>
|
||||||
|
<th class="text-center">
|
||||||
|
<span>سعر الوحدة</span>
|
||||||
|
</th>
|
||||||
|
<th class="text-center">
|
||||||
|
<span>الخصم</span>
|
||||||
|
</th>
|
||||||
|
<th class="text-center">
|
||||||
|
<span>الضريبة</span>
|
||||||
|
</th>
|
||||||
|
<th class="text-center">
|
||||||
|
<span>الإجمالي</span>
|
||||||
|
</th>
|
||||||
|
</tr>
|
||||||
|
</thead>
|
||||||
|
<tbody>
|
||||||
|
<tr>
|
||||||
|
<td class="text-center">1</td>
|
||||||
|
<td class="text-center">{{ data.car.marked_price|floatformat:2 }}</td>
|
||||||
|
<td class="text-center">{{ data.discount_amount|floatformat:2 }}</td>
|
||||||
|
<td class="text-center">{{ data.vat_amount|floatformat:2 }}</td>
|
||||||
|
<td class="text-center">{{ data.final_price|floatformat:2 }}</td>
|
||||||
|
</tr>
|
||||||
|
</tbody>
|
||||||
|
</table>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
{% if data.additional_services %}
|
||||||
|
<div class="table-container">
|
||||||
|
<div class="table-header">
|
||||||
|
<span>الخدمات الإضافية</span>
|
||||||
|
</div>
|
||||||
|
<table class="table">
|
||||||
|
<thead>
|
||||||
|
<tr>
|
||||||
|
<th class="text-center">النوع</th>
|
||||||
|
<th class="text-center">القيمة</th>
|
||||||
|
<th class="text-center">ضريبة الخدمة</th>
|
||||||
|
<th class="text-center">الإجمالي</th>
|
||||||
</tr>
|
</tr>
|
||||||
</thead>
|
</thead>
|
||||||
<tbody>
|
<tbody>
|
||||||
{% for service in data.additional_services.services %}
|
{% for service in data.additional_services.services %}
|
||||||
<tr>
|
<tr>
|
||||||
<td class="ps-1 text-start fs-10 align-content-center">{{ service.0.name }}</td>
|
<td class="text-center">{{ service.0.name }}</td>
|
||||||
<td class="ps-1 text-center fs-10 align-content-center">{{ service.0.price|floatformat }}</td>
|
<td class="text-center">{{ service.0.price|floatformat:2 }}</td>
|
||||||
<td class="ps-1 text-center fs-10 align-content-center">{{ service.1|floatformat }}</td>
|
<td class="text-center">{{ service.1|floatformat:2 }}</td>
|
||||||
<td class="ps-1 text-center fs-10 align-content-center">{{ service.0.price_|floatformat }}</td>
|
<td class="text-center">{{ service.0.price_|floatformat:2 }}</td>
|
||||||
</tr>
|
</tr>
|
||||||
{% endfor %}
|
{% endfor %}
|
||||||
</tbody>
|
</tbody>
|
||||||
|
<tfoot>
|
||||||
|
<tr>
|
||||||
|
<td class="text-center"></td>
|
||||||
|
<td class="text-center">{{ data.total_services_amount|floatformat:'2g'}}</td>
|
||||||
|
<td class="text-center">{{ data.total_services_vat|floatformat:'2g'}}</td>
|
||||||
|
<td class="text-center">{{ data.total_services_amount_|floatformat:'2g' }}</td>
|
||||||
|
</tr>
|
||||||
|
</tfoot>
|
||||||
</table>
|
</table>
|
||||||
</div>
|
</div>
|
||||||
{% endif %}
|
{% endif %}
|
||||||
<div class="invoice-total d-flex justify-content-end">
|
|
||||||
<div class="table-responsive">
|
<div class="document-details" style="margin-top: 30px; ">
|
||||||
<table class="table table-sm table-responsive">
|
<div class="section text-left">
|
||||||
<tr>
|
<p><span class="label">إجمالي ضريبة القيمة المضافة : </span> {{ data.total_vat|floatformat:'2g' }}</p>
|
||||||
<td class="text-start ps-1">
|
<p><span class="label">الإجمالي الكلي : </span> {{ data.grand_total|floatformat:'2g' }}</p>
|
||||||
<strong class="fs-9">Total VAT</strong>
|
<p><span class="label">كتابةً : </span> {{ data.grand_total|num_to_words }}</p>
|
||||||
</td>
|
|
||||||
<td class="text-center">
|
|
||||||
<span class="fs-9">{{ data.total_vat|floatformat }} <span class="icon-saudi_riyal"></span></span>
|
|
||||||
</td>
|
|
||||||
<td class="text-end">
|
|
||||||
<strong class="fs-9">إجمالي ضريبة القيمة المضافة</strong>
|
|
||||||
</td>
|
|
||||||
</tr>
|
|
||||||
<tr>
|
|
||||||
<td class="text-start ps-1">
|
|
||||||
<strong class="fs-9">Grand Total</strong>
|
|
||||||
</td>
|
|
||||||
<td class="text-center">
|
|
||||||
<span class="fs-9">{{ data.grand_total|floatformat }} <span class="icon-saudi_riyal"></span></span>
|
|
||||||
</td>
|
|
||||||
<td class="text-end">
|
|
||||||
<strong class="fs-9">الإجمالي الكلي</strong>
|
|
||||||
</td>
|
|
||||||
</tr>
|
|
||||||
<tr>
|
|
||||||
<td class="text-end" colspan="3">
|
|
||||||
<span class="fs-9 fw-bold">كتابةً: </span><span class="fs-9">{{ data.grand_total|num_to_words }} <span class="icon-saudi_riyal"></span></span>
|
|
||||||
</td>
|
|
||||||
</tr>
|
|
||||||
</table>
|
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
<hr style="border-bottom:1px solid #ccc; ">
|
||||||
<div class="footer-note">
|
|
||||||
<div class="logo-img text-center">
|
<div class="document-footer">
|
||||||
|
<div class="footer-logo">
|
||||||
<img src="{% static 'images/logos/logo-d-pdf.png' %}" alt="Logo" />
|
<img src="{% static 'images/logos/logo-d-pdf.png' %}" alt="Logo" />
|
||||||
<p class="fs-9 fw-bold">
|
<p>
|
||||||
<span>Haikal</span> | <span>هيكل</span>
|
<span>Haikal</span> | <span>هيكل</span>
|
||||||
</p>
|
</p>
|
||||||
</div>
|
</div>
|
||||||
<p class="fs-11">
|
<div class="footer-powered">
|
||||||
<span class="fw-thin">Powered by </span>
|
<p>
|
||||||
<a class="text-decoration-none fs-9"
|
<span>Powered by </span>
|
||||||
href="https://tenhal.sa"
|
<a href="https://tenhal.sa"><span>TENHAL</span> | <span>تنحل</span></a>
|
||||||
style="color: #112e40"><span>TENHAL</span> | <span>تنحل</span></a>
|
|
||||||
</p>
|
</p>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<script src="{% static 'vendors/bootstrap/bootstrap.min.js' %}"></script>
|
</div>
|
||||||
<script src="{% static 'js/html2pdf.bundle.min.js' %}"></script>
|
|
||||||
<script>
|
|
||||||
document.getElementById('download-pdf').addEventListener('click', function () {
|
|
||||||
html2pdf().from(document.getElementById('invoice-content')).set({
|
|
||||||
margin: 0,
|
|
||||||
filename: "{{ invoice.invoice_number }}_{{ invoice.customer.customer_name }}_{{invoice.date_approved|date:'Y-m-d' }}.pdf",
|
|
||||||
image: { type: 'jpeg', quality: 0.98 },
|
|
||||||
html2canvas: { scale: 3 },
|
|
||||||
jsPDF: { unit: 'mm', format: 'a3', orientation: 'portrait' }
|
|
||||||
}).save();
|
|
||||||
});
|
|
||||||
</script>
|
|
||||||
</body>
|
</body>
|
||||||
</html>
|
</html>
|
||||||
359
templates/sales/invoices/invoice_preview_ar.html
Normal file
359
templates/sales/invoices/invoice_preview_ar.html
Normal file
@ -0,0 +1,359 @@
|
|||||||
|
{% load static custom_filters num2words_tags %}
|
||||||
|
<!DOCTYPE html>
|
||||||
|
<html lang="ar" dir="rtl">
|
||||||
|
<head>
|
||||||
|
<meta charset="UTF-8">
|
||||||
|
|
||||||
|
<title>فاتورة</title>
|
||||||
|
|
||||||
|
<style>
|
||||||
|
/* General Body and Font Styles */
|
||||||
|
body {
|
||||||
|
font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, "Helvetica Neue", Arial, sans-serif;
|
||||||
|
font-size: 12px;
|
||||||
|
color: #333;
|
||||||
|
margin: 0;
|
||||||
|
padding: 0;
|
||||||
|
position: relative;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Page Layout and Margins for PDF */
|
||||||
|
|
||||||
|
@page {
|
||||||
|
size: A4;
|
||||||
|
margin: 20mm;
|
||||||
|
@top-left {
|
||||||
|
content: "صفحة " counter(page) " من " counter(pages);
|
||||||
|
font-size: 10px;
|
||||||
|
color: #555;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Header Styles */
|
||||||
|
.document-header {
|
||||||
|
display: flex;
|
||||||
|
justify-content: space-between;
|
||||||
|
align-items: center;
|
||||||
|
border-bottom: 2px solid #333;
|
||||||
|
padding-bottom: 15px;
|
||||||
|
margin-bottom: 20px;
|
||||||
|
}
|
||||||
|
.document-header .logo {
|
||||||
|
max-width: 150px;
|
||||||
|
height: auto;
|
||||||
|
}
|
||||||
|
.document-header h1 {
|
||||||
|
font-size: 24px;
|
||||||
|
margin: 0;
|
||||||
|
color: #0056b3;
|
||||||
|
}
|
||||||
|
.document-header address {
|
||||||
|
text-align: right;
|
||||||
|
font-style: normal;
|
||||||
|
font-size: 10px;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Document Details Section */
|
||||||
|
.document-details {
|
||||||
|
display: flex;
|
||||||
|
justify-content: space-between;
|
||||||
|
margin-bottom: 30px;
|
||||||
|
line-height: 1.6;
|
||||||
|
}
|
||||||
|
.document-details .section {
|
||||||
|
width: 48%;
|
||||||
|
}
|
||||||
|
.document-details h2 {
|
||||||
|
font-size: 14px;
|
||||||
|
border-bottom: 1px solid #ccc;
|
||||||
|
padding-bottom: 5px;
|
||||||
|
margin-bottom: 10px;
|
||||||
|
color: #555;
|
||||||
|
}
|
||||||
|
.document-details p {
|
||||||
|
margin: 0;
|
||||||
|
font-size: 12px;
|
||||||
|
}
|
||||||
|
.document-details .label {
|
||||||
|
font-weight: bold;
|
||||||
|
color: #555;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Table Styles */
|
||||||
|
.table {
|
||||||
|
width: 80%;
|
||||||
|
border-collapse: collapse;
|
||||||
|
margin: 20px auto; /* Centering the table */
|
||||||
|
}
|
||||||
|
.table th, .table td {
|
||||||
|
border: 1px solid #ddd;
|
||||||
|
padding: 8px;
|
||||||
|
text-align: right;
|
||||||
|
}
|
||||||
|
.table th {
|
||||||
|
background-color: #f2f2f2;
|
||||||
|
font-weight: bold;
|
||||||
|
}
|
||||||
|
.table tfoot td {
|
||||||
|
border-top: 2px solid #333;
|
||||||
|
font-weight: bold;
|
||||||
|
}
|
||||||
|
.text-right {
|
||||||
|
text-align: left;
|
||||||
|
}
|
||||||
|
.text-left {
|
||||||
|
text-align: right;
|
||||||
|
}
|
||||||
|
.text-center {
|
||||||
|
text-align: center;2px solid #333;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Responsive and layout helpers */
|
||||||
|
.table-container {
|
||||||
|
width: 100%;
|
||||||
|
overflow-x: auto;
|
||||||
|
margin-bottom: 20px;
|
||||||
|
}
|
||||||
|
.table-header {
|
||||||
|
display: flex;
|
||||||
|
justify-content: space-between;
|
||||||
|
align-items: center;
|
||||||
|
margin-bottom: 5px;
|
||||||
|
}
|
||||||
|
.table-header span {
|
||||||
|
font-size: 9px;
|
||||||
|
font-weight: lighter;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Footer Styles */
|
||||||
|
.document-footer {
|
||||||
|
position: relative;
|
||||||
|
bottom: 0;
|
||||||
|
left: 40px;
|
||||||
|
width: 100%;
|
||||||
|
text-align: center;
|
||||||
|
font-size: 10px;
|
||||||
|
color: #888;
|
||||||
|
|
||||||
|
padding-top: 15px;
|
||||||
|
margin: 0 20mm;
|
||||||
|
|
||||||
|
}
|
||||||
|
.footer-flex {
|
||||||
|
display: flex;
|
||||||
|
justify-content: space-between;
|
||||||
|
align-items: center;
|
||||||
|
width: 100%;
|
||||||
|
}
|
||||||
|
.footer-logo {
|
||||||
|
text-align: center;
|
||||||
|
}
|
||||||
|
.footer-logo img {
|
||||||
|
height: 20px;
|
||||||
|
width: 20px;
|
||||||
|
}
|
||||||
|
.footer-logo p {
|
||||||
|
font-size: 9px;
|
||||||
|
font-weight: bold;
|
||||||
|
}
|
||||||
|
.footer-powered p {
|
||||||
|
font-size: 11px;
|
||||||
|
}
|
||||||
|
.footer-powered span {
|
||||||
|
font-weight: lighter;
|
||||||
|
}
|
||||||
|
.footer-powered a {
|
||||||
|
color: #112e40;
|
||||||
|
text-decoration: none;
|
||||||
|
font-size: 9px;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
</style>
|
||||||
|
</head>
|
||||||
|
<body>
|
||||||
|
|
||||||
|
<div class="document-header">
|
||||||
|
<div>
|
||||||
|
<h1>{{ dealer_info.name }}</h1>
|
||||||
|
<address>
|
||||||
|
العنوان : {{ dealer_info.address }}<br>
|
||||||
|
البريد الإلكتروني : {{ dealer_info.user.email }}<br>
|
||||||
|
الهاتف : {{ dealer_info.phone_number }}<br>
|
||||||
|
رقم السجل التجاري : {{dealer_info.crn }}<br>
|
||||||
|
الرقم الضريبي : {{ dealer_info.vrn }}
|
||||||
|
</address>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div>
|
||||||
|
<div class="dealer-logo ">
|
||||||
|
{% if dealer_info.logo %}
|
||||||
|
<img class="rounded-soft"
|
||||||
|
style="max-width:100px;
|
||||||
|
max-height:100px"
|
||||||
|
src="{{dealer_info.logo.url|default:'' }}"
|
||||||
|
alt="Dealer Logo" />
|
||||||
|
{% endif %}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div>
|
||||||
|
<h1>فاتورة</h1>
|
||||||
|
<h2 style="font-size: 18px;">{{ invoice.invoice_number }}</h2>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="document-details">
|
||||||
|
<div class="section">
|
||||||
|
<h2>{{ customer_obj.full_name }}: إلى</h2>
|
||||||
|
<p><span class="label">العميل : {{ customer_obj.full_name }}</span></p>
|
||||||
|
<p><span class="label">البريد الإلكتروني : {{ customer_obj.email |default:"N/A"}}</span></p>
|
||||||
|
<p><span class="label">الهاتف : {{ customer_obj.phone_number|default:"N/A" }}</span></p>
|
||||||
|
<p><span class="label">العنوان : {{ customer_obj.address|default:"N/A" }}</span></p>
|
||||||
|
</div>
|
||||||
|
<div class="section text-left">
|
||||||
|
<h2>التفاصيل:</h2>
|
||||||
|
<p><span class="label">رقم عرض السعر : </span> {{ invoice.invoice_number }}</p>
|
||||||
|
<p><span class="label">تاريخ الإصدار : </span> {{ invoice.date_approved|date:"Y/m/d" }}</p>
|
||||||
|
<p><span class="label">طريقة الدفع : </span> {{ invoice.get_terms_display }}</p>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="table-container">
|
||||||
|
<div class="table-header">
|
||||||
|
<span>تفاصيل السيارة</span>
|
||||||
|
</div>
|
||||||
|
<table class="table">
|
||||||
|
<thead>
|
||||||
|
<tr>
|
||||||
|
<th class="text-center">
|
||||||
|
<span>الصانع</span>
|
||||||
|
</th>
|
||||||
|
<th class="text-center">
|
||||||
|
<span>الموديل</span>
|
||||||
|
</th>
|
||||||
|
<th class="text-center">
|
||||||
|
<span>السلسلة</span>
|
||||||
|
</th>
|
||||||
|
<th class="text-center">
|
||||||
|
<span>الفئة</span>
|
||||||
|
</th>
|
||||||
|
<th class="text-center">
|
||||||
|
<span>السنة</span>
|
||||||
|
</th>
|
||||||
|
<th class="text-center">
|
||||||
|
<span>رقم الهيكل</span>
|
||||||
|
</th>
|
||||||
|
</tr>
|
||||||
|
</thead>
|
||||||
|
<tbody>
|
||||||
|
<tr>
|
||||||
|
<td class="text-center">{{ data.car.id_car_make.name }}</td>
|
||||||
|
<td class="text-center">{{ data.car.id_car_model.name }}</td>
|
||||||
|
<td class="text-center">{{ data.car.id_car_serie.name }}</td>
|
||||||
|
<td class="text-center">{{ data.car.id_car_trim.name }}</td>
|
||||||
|
<td class="text-center">{{ data.car.year }}</td>
|
||||||
|
<td class="text-center">{{ data.car.vin }}</td>
|
||||||
|
</tr>
|
||||||
|
</tbody>
|
||||||
|
</table>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="table-container">
|
||||||
|
<div class="table-header">
|
||||||
|
<span>التفاصيل المالية</span>
|
||||||
|
</div>
|
||||||
|
<table class="table">
|
||||||
|
<thead>
|
||||||
|
<tr>
|
||||||
|
<th class="text-center">
|
||||||
|
<span>الكمية</span>
|
||||||
|
</th>
|
||||||
|
<th class="text-center">
|
||||||
|
<span>سعر الوحدة</span>
|
||||||
|
</th>
|
||||||
|
<th class="text-center">
|
||||||
|
<span>الخصم</span>
|
||||||
|
</th>
|
||||||
|
<th class="text-center">
|
||||||
|
<span>الضريبة</span>
|
||||||
|
</th>
|
||||||
|
<th class="text-center">
|
||||||
|
<span>الإجمالي</span>
|
||||||
|
</th>
|
||||||
|
</tr>
|
||||||
|
</thead>
|
||||||
|
<tbody>
|
||||||
|
<tr>
|
||||||
|
<td class="text-center">1</td>
|
||||||
|
<td class="text-center">{{ data.car.marked_price|currency_format }}</td>
|
||||||
|
<td class="text-center">{{ data.discount_amount|currency_format}}</td>
|
||||||
|
<td class="text-center">{{ data.vat_amount|currency_format}}</td>
|
||||||
|
<td class="text-center">{{ data.final_price|currency_format }}</td>
|
||||||
|
</tr>
|
||||||
|
</tbody>
|
||||||
|
</table>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
{% if data.additional_services %}
|
||||||
|
<div class="table-container">
|
||||||
|
<div class="table-header">
|
||||||
|
<span>الخدمات الإضافية</span>
|
||||||
|
</div>
|
||||||
|
<table class="table">
|
||||||
|
<thead>
|
||||||
|
<tr>
|
||||||
|
<th class="text-center">النوع</th>
|
||||||
|
<th class="text-center">القيمة</th>
|
||||||
|
<th class="text-center">ضريبة الخدمة</th>
|
||||||
|
<th class="text-center">الإجمالي</th>
|
||||||
|
</tr>
|
||||||
|
</thead>
|
||||||
|
<tbody>
|
||||||
|
{% for service in data.additional_services.services %}
|
||||||
|
<tr>
|
||||||
|
<td class="text-center">{{ service.0.name }}</td>
|
||||||
|
<td class="text-center">{{ service.0.price|currency_format}}</td>
|
||||||
|
<td class="text-center">{{ service.1|currency_format }}</td>
|
||||||
|
<td class="text-center">{{ service.0.price_|currency_format}}</td>
|
||||||
|
</tr>
|
||||||
|
{% endfor %}
|
||||||
|
</tbody>
|
||||||
|
<tfoot>
|
||||||
|
<tr>
|
||||||
|
<td class="text-center"></td>
|
||||||
|
<td class="text-center">{{ data.total_services_amount|currency_format}}</td>
|
||||||
|
<td class="text-center">{{ data.total_services_vat|currency_format}}</td>
|
||||||
|
<td class="text-center">{{ data.total_services_amount_|currency_format }}</td>
|
||||||
|
</tr>
|
||||||
|
</tfoot>
|
||||||
|
|
||||||
|
</table>
|
||||||
|
</div>
|
||||||
|
{% endif %}
|
||||||
|
|
||||||
|
<div class="document-details" style="margin-top: 30px; ">
|
||||||
|
<div class="section text-left">
|
||||||
|
<p><span class="label">إجمالي ضريبة القيمة المضافة : </span> {{ data.total_vat|currency_format }}</p>
|
||||||
|
<p><span class="label">الإجمالي الكلي : </span> {{ data.grand_total|currency_format}}</p>
|
||||||
|
<p><span class="label">كتابةً : </span> {{ data.grand_total|num_to_words }}</p>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<hr style="border-bottom:1px solid #ccc; ">
|
||||||
|
|
||||||
|
<div class="document-footer">
|
||||||
|
<div class="footer-logo">
|
||||||
|
<img src="{% static 'images/logos/logo-d-pdf.png' %}" alt="Logo" />
|
||||||
|
<p>
|
||||||
|
<span>Haikal</span> | <span>هيكل</span>
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
|
<div class="footer-powered">
|
||||||
|
<p>
|
||||||
|
<span>Powered by </span>
|
||||||
|
<a href="https://tenhal.sa"><span>TENHAL</span> | <span>تنحل</span></a>
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</body>
|
||||||
|
</html>
|
||||||
361
templates/sales/invoices/invoice_preview_en.html
Normal file
361
templates/sales/invoices/invoice_preview_en.html
Normal file
@ -0,0 +1,361 @@
|
|||||||
|
{% load static custom_filters num2words_tags %}
|
||||||
|
<!DOCTYPE html>
|
||||||
|
<html lang="en" dir="ltr">
|
||||||
|
<head>
|
||||||
|
<meta charset="UTF-8">
|
||||||
|
<title>Invoice</title>
|
||||||
|
|
||||||
|
<style>
|
||||||
|
/* General Body and Font Styles */
|
||||||
|
body {
|
||||||
|
font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, "Helvetica Neue", Arial, sans-serif;
|
||||||
|
font-size: 12px;
|
||||||
|
color: #333;
|
||||||
|
margin: 0;
|
||||||
|
padding: 0;
|
||||||
|
position: relative;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Page Layout and Margins for PDF */
|
||||||
|
@page {
|
||||||
|
size: A4;
|
||||||
|
margin: 20mm;
|
||||||
|
@top-left {
|
||||||
|
content: "صفحة " counter(page) " من " counter(pages);
|
||||||
|
font-size: 10px;
|
||||||
|
color: #555;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Header Styles */
|
||||||
|
.document-header {
|
||||||
|
display: flex;
|
||||||
|
justify-content: space-between;
|
||||||
|
align-items: center;
|
||||||
|
border-bottom: 2px solid #333;
|
||||||
|
padding-bottom: 15px;
|
||||||
|
margin-bottom: 20px;
|
||||||
|
}
|
||||||
|
.document-header .logo {
|
||||||
|
max-width: 150px;
|
||||||
|
height: auto;
|
||||||
|
}
|
||||||
|
.document-header h1 {
|
||||||
|
font-size: 24px;
|
||||||
|
margin: 0;
|
||||||
|
color: #0056b3;
|
||||||
|
}
|
||||||
|
.document-header address {
|
||||||
|
text-align: right;
|
||||||
|
font-style: normal;
|
||||||
|
font-size: 10px;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Document Details Section */
|
||||||
|
.document-details {
|
||||||
|
display: flex;
|
||||||
|
justify-content: space-between;
|
||||||
|
margin-bottom: 30px;
|
||||||
|
line-height: 1.6;
|
||||||
|
}
|
||||||
|
.document-details .section {
|
||||||
|
width: 48%;
|
||||||
|
}
|
||||||
|
.document-details h2 {
|
||||||
|
font-size: 14px;
|
||||||
|
border-bottom: 1px solid #ccc;
|
||||||
|
padding-bottom: 5px;
|
||||||
|
margin-bottom: 10px;
|
||||||
|
color: #555;
|
||||||
|
}
|
||||||
|
.document-details p {
|
||||||
|
margin: 0;
|
||||||
|
font-size: 12px;
|
||||||
|
}
|
||||||
|
.document-details .label {
|
||||||
|
font-weight: bold;
|
||||||
|
color: #555;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Table Styles */
|
||||||
|
.table {
|
||||||
|
width: 80%;
|
||||||
|
border-collapse: collapse;
|
||||||
|
margin: 20px auto; /* Centering the table */
|
||||||
|
}
|
||||||
|
.table th, .table td {
|
||||||
|
border: 1px solid #ddd;
|
||||||
|
padding: 8px;
|
||||||
|
text-align: right;
|
||||||
|
}
|
||||||
|
.table th {
|
||||||
|
background-color: #f2f2f2;
|
||||||
|
font-weight: bold;
|
||||||
|
}
|
||||||
|
.table tfoot td {
|
||||||
|
border-top: 2px solid #333;
|
||||||
|
font-weight: bold;
|
||||||
|
}
|
||||||
|
.text-right {
|
||||||
|
text-align: left;
|
||||||
|
}
|
||||||
|
.text-left {
|
||||||
|
text-align: right;
|
||||||
|
}
|
||||||
|
.text-center {
|
||||||
|
text-align: center;2px solid #333;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Responsive and layout helpers */
|
||||||
|
.table-container {
|
||||||
|
width: 100%;
|
||||||
|
overflow-x: auto;
|
||||||
|
margin-bottom: 20px;
|
||||||
|
}
|
||||||
|
.table-header {
|
||||||
|
display: flex;
|
||||||
|
justify-content: space-between;
|
||||||
|
align-items: center;
|
||||||
|
margin-bottom: 5px;
|
||||||
|
}
|
||||||
|
.table-header span {
|
||||||
|
font-size: 9px;
|
||||||
|
font-weight: lighter;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Footer Styles */
|
||||||
|
.document-footer {
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: center;
|
||||||
|
position: relative;
|
||||||
|
bottom: 0;
|
||||||
|
left: 0;
|
||||||
|
width: 100%;
|
||||||
|
text-align: center;
|
||||||
|
font-size: 10px;
|
||||||
|
color: #888;
|
||||||
|
padding-top: 15px;
|
||||||
|
margin: 0 auto; /* This change centers the footer container */
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
.footer-flex {
|
||||||
|
display: flex;
|
||||||
|
justify-content: space-between;
|
||||||
|
align-items: center;
|
||||||
|
width: 100%;
|
||||||
|
}
|
||||||
|
.footer-logo {
|
||||||
|
text-align: center;
|
||||||
|
}
|
||||||
|
.footer-logo img {
|
||||||
|
height: 20px;
|
||||||
|
width: 20px;
|
||||||
|
}
|
||||||
|
.footer-logo p {
|
||||||
|
font-size: 9px;
|
||||||
|
font-weight: bold;
|
||||||
|
}
|
||||||
|
.footer-powered p {
|
||||||
|
font-size: 11px;
|
||||||
|
}
|
||||||
|
.footer-powered span {
|
||||||
|
font-weight: lighter;
|
||||||
|
}
|
||||||
|
.footer-powered a {
|
||||||
|
color: #112e40;
|
||||||
|
text-decoration: none;
|
||||||
|
font-size: 9px;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
</style>
|
||||||
|
</head>
|
||||||
|
<body>
|
||||||
|
|
||||||
|
<div class="document-header">
|
||||||
|
<div>
|
||||||
|
<h1>{{ dealer_info.name }}</h1>
|
||||||
|
<address>
|
||||||
|
Address : {{ dealer_info.address }}<br>
|
||||||
|
Email : {{ dealer_info.user.email }}<br>
|
||||||
|
Phone : {{ dealer_info.phone_number }}<br>
|
||||||
|
Commercial Registration No. : {{dealer_info.crn }}<br>
|
||||||
|
VAT No. : {{ dealer_info.vrn }}
|
||||||
|
</address>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div>
|
||||||
|
<div class="dealer-logo ">
|
||||||
|
{% if dealer_info.logo %}
|
||||||
|
<img class="rounded-soft"
|
||||||
|
style="max-width:100px;
|
||||||
|
max-height:100px"
|
||||||
|
src="{{dealer_info.logo.url|default:'' }}"
|
||||||
|
alt="Dealer Logo" />
|
||||||
|
{% endif %}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div>
|
||||||
|
<h1>Invoice</h1>
|
||||||
|
<h2 style="font-size: 18px;">{{ invoice.invoice_number }}</h2>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="document-details">
|
||||||
|
<div class="section">
|
||||||
|
<h2>To: {{ customer_obj.full_name }}</h2>
|
||||||
|
<p><span class="label">Customer : {{ customer_obj.full_name }}</span></p>
|
||||||
|
<p><span class="label">Email : {{ customer_obj.email |default:"N/A"}}</span></p>
|
||||||
|
<p><span class="label">Phone : {{ customer_obj.phone_number|default:"N/A" }}</span></p>
|
||||||
|
<p><span class="label">Address : {{ customer_obj.address|default:"N/A" }}</span></p>
|
||||||
|
</div>
|
||||||
|
<div class="section text-right">
|
||||||
|
<h2>Details:</h2>
|
||||||
|
<p><span class="label">Quotation Number : </span> {{ invoice.invoice_number }}</p>
|
||||||
|
<p><span class="label">Issue Date : </span> {{ invoice.date_approved|date:"Y/m/d" }}</p>
|
||||||
|
<p><span class="label">Payment Method : </span> {{ invoiceget_terms_display }}</p>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="table-container">
|
||||||
|
<div class="table-header">
|
||||||
|
<span>Car Details</span>
|
||||||
|
</div>
|
||||||
|
<table class="table">
|
||||||
|
<thead>
|
||||||
|
<tr>
|
||||||
|
<th class="text-center">
|
||||||
|
<span>Make</span>
|
||||||
|
</th>
|
||||||
|
<th class="text-center">
|
||||||
|
<span>Model</span>
|
||||||
|
</th>
|
||||||
|
<th class="text-center">
|
||||||
|
<span>Series</span>
|
||||||
|
</th>
|
||||||
|
<th class="text-center">
|
||||||
|
<span>Trim</span>
|
||||||
|
</th>
|
||||||
|
<th class="text-center">
|
||||||
|
<span>Year</span>
|
||||||
|
</th>
|
||||||
|
<th class="text-center">
|
||||||
|
<span>VIN</span>
|
||||||
|
</th>
|
||||||
|
</tr>
|
||||||
|
</thead>
|
||||||
|
<tbody>
|
||||||
|
<tr>
|
||||||
|
<td class="text-center">{{ data.car.id_car_make.name }}</td>
|
||||||
|
<td class="text-center">{{ data.car.id_car_model.name }}</td>
|
||||||
|
<td class="text-center">{{ data.car.id_car_serie.name }}</td>
|
||||||
|
<td class="text-center">{{ data.car.id_car_trim.name }}</td>
|
||||||
|
<td class="text-center">{{ data.car.year }}</td>
|
||||||
|
<td class="text-center">{{ data.car.vin }}</td>
|
||||||
|
</tr>
|
||||||
|
</tbody>
|
||||||
|
</table>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="table-container">
|
||||||
|
<div class="table-header">
|
||||||
|
<span>Financial Details</span>
|
||||||
|
</div>
|
||||||
|
<table class="table">
|
||||||
|
<thead>
|
||||||
|
<tr>
|
||||||
|
<th class="text-center">
|
||||||
|
<span>Quantity</span>
|
||||||
|
</th>
|
||||||
|
<th class="text-center">
|
||||||
|
<span>Unit Price</span>
|
||||||
|
</th>
|
||||||
|
<th class="text-center">
|
||||||
|
<span>Discount</span>
|
||||||
|
</th>
|
||||||
|
<th class="text-center">
|
||||||
|
<span>VAT</span>
|
||||||
|
</th>
|
||||||
|
<th class="text-center">
|
||||||
|
<span>Total</span>
|
||||||
|
</th>
|
||||||
|
</tr>
|
||||||
|
</thead>
|
||||||
|
<tbody>
|
||||||
|
<tr>
|
||||||
|
<td class="text-center">1</td>
|
||||||
|
<td class="text-center">{{ data.car.marked_price|currency_format}}</td>
|
||||||
|
<td class="text-center">{{ data.discount_amount|currency_format }}</td>
|
||||||
|
<td class="text-center">{{ data.vat_amount|currency_format}}</td>
|
||||||
|
<td class="text-center">{{ data.final_price|currency_format }}</td>
|
||||||
|
</tr>
|
||||||
|
</tbody>
|
||||||
|
</table>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
{% if data.additional_services %}
|
||||||
|
<div class="table-container">
|
||||||
|
<div class="table-header">
|
||||||
|
<span>Additional Services</span>
|
||||||
|
</div>
|
||||||
|
<table class="table">
|
||||||
|
<thead>
|
||||||
|
<tr>
|
||||||
|
<th class="text-center">Type</th>
|
||||||
|
<th class="text-center">Value</th>
|
||||||
|
<th class="text-center">Service VAT</th>
|
||||||
|
<th class="text-center">Total</th>
|
||||||
|
</tr>
|
||||||
|
</thead>
|
||||||
|
<tbody>
|
||||||
|
{% for service in data.additional_services.services %}
|
||||||
|
<tr>
|
||||||
|
<td class="text-center">{{ service.0.name }}</td>
|
||||||
|
<td class="text-center">{{ service.0.price|currency_format }}</td>
|
||||||
|
<td class="text-center">{{ service.1|currency_format}}</td>
|
||||||
|
<td class="text-center">{{ service.0.price_|currency_format}}</td>
|
||||||
|
</tr>
|
||||||
|
{% endfor %}
|
||||||
|
</tbody>
|
||||||
|
<tfoot>
|
||||||
|
<tr>
|
||||||
|
<td class="text-center"></td>
|
||||||
|
<td class="text-center">{{ data.total_services_amount|currency_format}}</td>
|
||||||
|
<td class="text-center">{{ data.total_services_vat|currency_format}}</td>
|
||||||
|
<td class="text-center">{{ data.total_services_amount_|currency_format }}</td>
|
||||||
|
</tr>
|
||||||
|
</tfoot>
|
||||||
|
|
||||||
|
</table>
|
||||||
|
</div>
|
||||||
|
{% endif %}
|
||||||
|
|
||||||
|
<div class="document-details" style="margin-top: 30px; ">
|
||||||
|
<div class="section text-right">
|
||||||
|
<p><span class="label">Total VAT : </span> {{ data.total_vat|currency_format}}</p>
|
||||||
|
<p><span class="label">Grand Total : </span> {{ data.grand_total|currency_format}}</p>
|
||||||
|
<p><span class="label">In words : </span> {{ data.grand_total|num_to_words }}</p>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<hr style="border-bottom:1px solid #ccc; ">
|
||||||
|
|
||||||
|
<div class="document-footer">
|
||||||
|
<div class="footer-logo">
|
||||||
|
<img src="{% static 'images/logos/logo-d-pdf.png' %}" alt="Logo" />
|
||||||
|
<p>
|
||||||
|
<span>Haikal</span> | <span>هيكل</span>
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
|
<div class="footer-powered">
|
||||||
|
<p>
|
||||||
|
<span>Powered by </span>
|
||||||
|
<a href="https://tenhal.sa"><span>TENHAL</span> | <span>تنحل</span></a>
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</body>
|
||||||
|
</html>
|
||||||
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