update the preview

This commit is contained in:
ismail 2025-05-07 12:20:54 +03:00
parent 9d6d69f1d0
commit b44fb270fa
18 changed files with 631 additions and 471 deletions

View File

@ -10,6 +10,8 @@ from django.core.validators import MinLengthValidator
from django import forms
from plans.models import PlanPricing
from django.contrib.auth import get_user_model
from inventory.validators import SaudiPhoneNumberValidator
from .models import CustomGroup, Status, Stage
from .mixins import AddClassMixin
from django_ledger.forms.invoice import (
@ -46,6 +48,7 @@ from .models import (
CarModel,
SaleOrder,
CarMake,
Customer,
DealerSettings
)
from django_ledger import models as ledger_models
@ -58,6 +61,14 @@ import django_tables2 as tables
User = get_user_model()
class SaudiPhoneNumberField(forms.CharField):
def __init__(self, *args, **kwargs):
kwargs.setdefault('min_length', 10)
kwargs.setdefault('max_length', 13)
super().__init__(*args, **kwargs)
self.validators.append(SaudiPhoneNumberValidator())
class AdditionalServiceForm(forms.ModelForm):
"""
A form used for creating and updating instances of the
@ -104,20 +115,14 @@ class StaffForm(forms.ModelForm):
widget=forms.CheckboxSelectMultiple(attrs={"class": "form-check-input"}),
queryset=Service.objects.all(),
required=False,)
phone_number = forms.CharField(
phone_number = SaudiPhoneNumberField(
required=False,
max_length=10,
min_length=10,
widget=forms.TextInput(attrs={
'class': 'form-control',
'placeholder': _('Phone Number'),
'id': 'phone'
}),
label=_('Phone Number'),
validators=[RegexValidator(
regex=r'^05[0-9]{8}$',
message=_('Enter a valid phone number (8-15 digits, starting with 05)')
)]
)
class Meta:
model = Staff
@ -147,7 +152,9 @@ class DealerForm(forms.ModelForm):
:type address: str
:ivar logo: Logo of the dealer.
:type logo: File
"""
phone_number = SaudiPhoneNumberField(label=_('Phone Number'))
class Meta:
model = Dealer
fields = [
@ -161,61 +168,78 @@ class DealerForm(forms.ModelForm):
]
class CustomerForm(forms.Form):
"""
Represents a form for collecting customer information.
class CustomerForm(forms.ModelForm):
phone_number = SaudiPhoneNumberField(label=_('Phone Number'))
This form is used to gather and validate customer details such as name,
email, phone number, national ID, tax registration details, and address.
It includes several fields with validation constraints and specific
requirements. It is designed to handle both required and optional fields,
ensuring the correctness of user inputs.
class Meta:
model = Customer
fields = [
'title',
"first_name",
"last_name",
"email",
"phone_number",
"national_id",
"dob",
"address",
'image',
]
# class CustomerForm(forms.Form):
# """
# Represents a form for collecting customer information.
:ivar first_name: Customer's first name.
:type first_name: forms.CharField
:ivar last_name: Customer's last name.
:type last_name: forms.CharField
:ivar arabic_name: Customer's name in Arabic.
:type arabic_name: forms.CharField
:ivar email: Customer's email address.
:type email: forms.EmailField
:ivar phone_number: Customer's phone number. Validates the format and
ensures the number is in the Saudi Arabia region.
:type phone_number: PhoneNumberField
:ivar national_id: Customer's national ID. Optional field limited to
a maximum length of 10 characters.
:type national_id: forms.CharField
:ivar crn: Commercial registration number (CRN) of the customer. Optional field.
:type crn: forms.CharField
:ivar vrn: Value-added tax registration number (VRN) of the customer.
Optional field.
:type vrn: forms.CharField
:ivar address: Customer's address.
:type address: forms.CharField
"""
first_name = forms.CharField()
last_name = forms.CharField()
arabic_name = forms.CharField()
email = forms.EmailField()
# phone_number = PhoneNumberField(
# label=_("Phone Number"),
# widget=forms.TextInput(
# attrs={
# "placeholder": _("Phone"),
# }
# ),
# region="SA",
# error_messages={
# "required": _("This field is required."),
# "invalid": _("Phone number must be in the format 05xxxxxxxx"),
# },
# required=True,
# )
phone_number = forms.CharField(label=_("Phone Number"),min_length=10,max_length=10,validators=[RegexValidator(regex='^05[0-9]{8}$')], required=True)
national_id = forms.CharField(max_length=10,required=False)
crn = forms.CharField(required=False)
vrn = forms.CharField(required=False)
address = forms.CharField()
# This form is used to gather and validate customer details such as name,
# email, phone number, national ID, tax registration details, and address.
# It includes several fields with validation constraints and specific
# requirements. It is designed to handle both required and optional fields,
# ensuring the correctness of user inputs.
# :ivar first_name: Customer's first name.
# :type first_name: forms.CharField
# :ivar last_name: Customer's last name.
# :type last_name: forms.CharField
# :ivar arabic_name: Customer's name in Arabic.
# :type arabic_name: forms.CharField
# :ivar email: Customer's email address.
# :type email: forms.EmailField
# :ivar phone_number: Customer's phone number. Validates the format and
# ensures the number is in the Saudi Arabia region.
# :type phone_number: PhoneNumberField
# :ivar national_id: Customer's national ID. Optional field limited to
# a maximum length of 10 characters.
# :type national_id: forms.CharField
# :ivar crn: Commercial registration number (CRN) of the customer. Optional field.
# :type crn: forms.CharField
# :ivar vrn: Value-added tax registration number (VRN) of the customer.
# Optional field.
# :type vrn: forms.CharField
# :ivar address: Customer's address.
# :type address: forms.CharField
# """
# first_name = forms.CharField()
# last_name = forms.CharField()
# arabic_name = forms.CharField()
# email = forms.EmailField()
# # phone_number = PhoneNumberField(
# # label=_("Phone Number"),
# # widget=forms.TextInput(
# # attrs={
# # "placeholder": _("Phone"),
# # }
# # ),
# # region="SA",
# # error_messages={
# # "required": _("This field is required."),
# # "invalid": _("Phone number must be in the format 05xxxxxxxx"),
# # },
# # required=True,
# # )
# phone_number = forms.CharField(label=_("Phone Number"),min_length=10,max_length=10,validators=[RegexValidator(regex='^05[0-9]{8}$')], required=True)
# national_id = forms.CharField(max_length=10,required=False)
# crn = forms.CharField(required=False)
# vrn = forms.CharField(required=False)
# address = forms.CharField()
# image = forms.ImageField(required=False)
class OrganizationForm(CustomerForm):
@ -479,18 +503,7 @@ class VendorForm(forms.ModelForm):
:ivar Meta: Inner class to define metadata for the Vendor form.
:type Meta: Type[VendorForm.Meta]
"""
phone_number = forms.CharField(
label=_("Phone Number"),
min_length=10,
max_length=13,
validators=[
RegexValidator(
regex=r'^(\+9665|05)[0-9]{8}$',
message=_("Enter a valid Saudi phone number (05XXXXXXXX or +9665XXXXXXXX)")
)
],
required=True
)
phone_number = SaudiPhoneNumberField(label=_('Phone Number'))
contact_person = forms.CharField(label=_("Contact Person"))
class Meta:
@ -577,6 +590,8 @@ class RepresentativeForm(forms.ModelForm):
:ivar Meta.fields: The fields from the model to include in the form.
:type Meta.fields: list of str
"""
phone_number = SaudiPhoneNumberField(label=_('Phone Number'))
class Meta:
model = Representative
fields = [
@ -795,21 +810,7 @@ class WizardForm2(forms.Form):
# },
# required=True,
# )
phone_number = forms.CharField(
required=False,
max_length=10,
min_length=10,
widget=forms.TextInput(attrs={
'class': 'form-control',
'placeholder': _('Phone Number'),
'id': 'phone'
}),
label=_('Phone Number'),
validators=[RegexValidator(
regex=r'^05[0-9]{8}$',
message=_('Enter a valid phone number (10 digits, starting with 05)')
)]
)
phone_number = SaudiPhoneNumberField(label=_('Phone Number'))
class WizardForm3(forms.Form):
@ -1006,6 +1007,8 @@ class LeadForm(forms.ModelForm):
options are displayed until a car make is selected.
:type id_car_model: ModelChoiceField
"""
phone_number = SaudiPhoneNumberField(label=_('Phone Number'))
id_car_make = forms.ModelChoiceField(
label=_("Make"),
queryset=CarMake.objects.filter(is_sa_import=True),
@ -1213,71 +1216,75 @@ class SaleOrderForm(forms.ModelForm):
}
class EstimateModelCreateForm(EstimateModelCreateFormBase):
"""
Defines the EstimateModelCreateForm class, which is used to create and manage
forms for EstimateModel. This form handles the rendering and validation
of specific fields, their input widgets, and labels.
class EstimateModelCreateForm(forms.Form):
title = forms.CharField(max_length=255)
customer = forms.ModelChoiceField(queryset=Customer.objects.none())
The purpose of this class is to provide a structured way to handle
EstimateModel instances and their related fields, ensuring a user-friendly
form interface with proper field configuration. It facilitates fetching
related data, such as customer queries, based on user-specific parameters.
# class EstimateModelCreateForm(EstimateModelCreateFormBase):
# """
# Defines the EstimateModelCreateForm class, which is used to create and manage
# forms for EstimateModel. This form handles the rendering and validation
# of specific fields, their input widgets, and labels.
:ivar ENTITY_SLUG: A string that represents the entity context in which the
form operates.
:type ENTITY_SLUG: str
:ivar USER_MODEL: The user model that provides methods and objects needed
to filter and query customers.
:type USER_MODEL: Any
:ivar fields: A dictionary representing fields included in the form such as
"title", "customer", and "terms".
:type fields: dict
:ivar widgets: A dictionary defining custom input widgets for form fields,
enabling specific attributes like classes and identifiers for styling or
functionality purposes.
:type widgets: dict
:ivar labels: A dictionary specifying the human-readable labels for form fields.
:type labels: dict
"""
class Meta:
model = ledger_models.EstimateModel
fields = ["title","customer", "terms"]
widgets = {
"customer": forms.Select(
attrs={
"id": "djl-customer-estimate-customer-input",
"class": "input",
"label": _("Customer"),
}
),
'terms': forms.Select(attrs={
'id': 'djl-customer-estimate-terms-input',
'class': 'input',
'label': _('Terms'),
}),
'title': forms.TextInput(attrs={
'id': 'djl-customer-job-title-input',
'class': 'input' + ' is-large',
'label': _('Title'),
})
}
labels = {
'title': _('Title'),
'terms': _('Terms'),
"customer": _("Customer"),
}
# The purpose of this class is to provide a structured way to handle
# EstimateModel instances and their related fields, ensuring a user-friendly
# form interface with proper field configuration. It facilitates fetching
# related data, such as customer queries, based on user-specific parameters.
def __init__(self, *args, entity_slug, user_model, **kwargs):
super(EstimateModelCreateForm, self).__init__(
*args, entity_slug=entity_slug, user_model=user_model, **kwargs
)
self.ENTITY_SLUG = entity_slug
self.USER_MODEL = user_model
self.fields["customer"].queryset = self.get_customer_queryset()
# :ivar ENTITY_SLUG: A string that represents the entity context in which the
# form operates.
# :type ENTITY_SLUG: str
# :ivar USER_MODEL: The user model that provides methods and objects needed
# to filter and query customers.
# :type USER_MODEL: Any
# :ivar fields: A dictionary representing fields included in the form such as
# "title", "customer", and "terms".
# :type fields: dict
# :ivar widgets: A dictionary defining custom input widgets for form fields,
# enabling specific attributes like classes and identifiers for styling or
# functionality purposes.
# :type widgets: dict
# :ivar labels: A dictionary specifying the human-readable labels for form fields.
# :type labels: dict
# """
# class Meta:
# model = ledger_models.EstimateModel
# fields = ["title","customer", "terms"]
# widgets = {
# "customer": forms.Select(
# attrs={
# "id": "djl-customer-estimate-customer-input",
# "class": "input",
# "label": _("Customer"),
# }
# ),
# 'terms': forms.Select(attrs={
# 'id': 'djl-customer-estimate-terms-input',
# 'class': 'input',
# 'label': _('Terms'),
# }),
# 'title': forms.TextInput(attrs={
# 'id': 'djl-customer-job-title-input',
# 'class': 'input' + ' is-large',
# 'label': _('Title'),
# })
# }
# labels = {
# 'title': _('Title'),
# 'terms': _('Terms'),
# "customer": _("Customer"),
# }
def get_customer_queryset(self):
return self.USER_MODEL.dealer.entity.get_customers()
# def __init__(self, *args, entity_slug, user_model, **kwargs):
# super(EstimateModelCreateForm, self).__init__(
# *args, entity_slug=entity_slug, user_model=user_model, **kwargs
# )
# self.ENTITY_SLUG = entity_slug
# self.USER_MODEL = user_model
# self.fields["customer"].queryset = self.get_customer_queryset()
# def get_customer_queryset(self):
# return self.USER_MODEL.dealer.entity.get_customers()
class OpportunityStatusForm(forms.Form):
@ -1589,21 +1596,7 @@ class PaymentPlanForm(forms.Form):
label=_('Email Address')
)
phone = forms.CharField(
required=False,
max_length=10,
min_length=10,
widget=forms.TextInput(attrs={
'class': 'form-control',
'placeholder': _('Phone Number'),
'id': 'phone'
}),
label=_('Phone Number'),
validators=[RegexValidator(
regex=r'^05[0-9]{8}$',
message=_('Enter a valid phone number (10 digits, starting with 05)')
)]
)
phone_number = SaudiPhoneNumberField(label=_('Phone Number'))
# Credit Card Fields (not saved to database)
card_number = CreditCardField(

View File

@ -0,0 +1,18 @@
# Generated by Django 5.1.7 on 2025-05-06 14:49
from django.db import migrations, models
class Migration(migrations.Migration):
dependencies = [
('inventory', '0003_alter_car_vendor'),
]
operations = [
migrations.AddField(
model_name='customer',
name='image',
field=models.ImageField(blank=True, null=True, upload_to='customers/', verbose_name='Image'),
),
]

View File

@ -0,0 +1,20 @@
# Generated by Django 5.1.7 on 2025-05-06 14:54
import django.db.models.deletion
from django.db import migrations, models
class Migration(migrations.Migration):
dependencies = [
('django_ledger', '0021_alter_bankaccountmodel_account_model_and_more'),
('inventory', '0004_customer_image'),
]
operations = [
migrations.AddField(
model_name='customer',
name='customer_model',
field=models.ForeignKey(null=True, on_delete=django.db.models.deletion.SET_NULL, to='django_ledger.customermodel'),
),
]

View File

@ -0,0 +1,22 @@
# Generated by Django 5.1.7 on 2025-05-06 15:38
from django.db import migrations, models
class Migration(migrations.Migration):
dependencies = [
('inventory', '0005_customer_customer_model'),
]
operations = [
migrations.RemoveField(
model_name='customer',
name='middle_name',
),
migrations.AddField(
model_name='customer',
name='active',
field=models.BooleanField(default=True, verbose_name='Active'),
),
]

View File

@ -0,0 +1,18 @@
# Generated by Django 5.1.7 on 2025-05-06 16:35
from django.db import migrations, models
class Migration(migrations.Migration):
dependencies = [
('inventory', '0006_remove_customer_middle_name_customer_active'),
]
operations = [
migrations.AddField(
model_name='customer',
name='customer_type',
field=models.CharField(blank=True, choices=[('customer', 'Customer'), ('organization', 'Organization')], default='customer', max_length=15, null=True, verbose_name='Customer Type'),
),
]

View File

@ -1032,18 +1032,22 @@ class Priority(models.TextChoices):
HIGH = "high", _("High")
class Customer(models.Model):
dealer = models.ForeignKey(
Dealer, on_delete=models.CASCADE, related_name="customers"
)
customer_model = models.ForeignKey(
CustomerModel, on_delete=models.SET_NULL, null=True
)
user = models.OneToOneField(User, on_delete=models.CASCADE, related_name='customer_profile')
title = models.CharField(
choices=Title.choices, default=Title.NA, max_length=10, verbose_name=_("Title")
)
first_name = models.CharField(max_length=50, verbose_name=_("First Name"))
middle_name = models.CharField(
max_length=50, blank=True, null=True, verbose_name=_("Middle Name")
)
# middle_name = models.CharField(
# max_length=50, blank=True, null=True, verbose_name=_("Middle Name")
# )
last_name = models.CharField(max_length=50, verbose_name=_("Last Name"))
gender = models.CharField(
choices=[("m", _("Male")), ("f", _("Female"))],
@ -1061,6 +1065,14 @@ class Customer(models.Model):
address = models.CharField(
max_length=200, blank=True, null=True, verbose_name=_("Address")
)
active = models.BooleanField(default=True, verbose_name=_("Active"))
image = models.ImageField(
upload_to="customers/", blank=True, null=True, verbose_name=_("Image")
)
customer_type = models.CharField(
choices=[("customer", _("Customer")),("organization", _("Organization"))], default="customer", max_length=15, verbose_name=_("Customer Type"),
null=True,blank=True
)
created = models.DateTimeField(auto_now_add=True, verbose_name=_("Created"))
updated = models.DateTimeField(auto_now=True, verbose_name=_("Updated"))
@ -1069,13 +1081,64 @@ class Customer(models.Model):
verbose_name_plural = _("Customers")
def __str__(self):
middle = f" {self.middle_name}" if self.middle_name else ""
return f"{self.first_name}{middle} {self.last_name}"
# middle = f" {self.middle_name}" if self.middle_name else ""
return f"{self.first_name} {self.last_name}"
@property
def get_full_name(self):
return f"{self.first_name} {self.middle_name} {self.last_name}"
def full_name(self):
return f"{self.first_name} {self.last_name}"
def create_customer_model(self):
customer_dict = to_dict(self)
customer = self.dealer.entity.create_customer(
commit=False,
customer_model_kwargs={
"customer_name": self.full_name,
"address_1": self.address,
"phone": self.phone_number,
"email": self.email,
},
)
try:
customer.additional_info.update({"customer_info": customer_dict})
customer.additional_info.update({"type": "customer"})
except Exception:
pass
customer.save()
return customer
def update_user_model(self):
user = self.user
user.first_name = self.first_name
user.last_name = self.last_name
user.email = self.email
user.save()
return user
def update_customer_model(self):
customer_dict = to_dict(self)
customer = self.customer_model
customer.customer_name = self.full_name
customer.address_1 = self.address
customer.phone = self.phone_number
customer.email = self.email
try:
customer.additional_info.update({"customer_info": customer_dict})
customer.additional_info.update({"type": "customer"})
except Exception:
pass
customer.save()
return customer
def create_user_model(self):
user = User.objects.create_user(
username=self.email,
email=self.email,
first_name=self.first_name,
last_name=self.last_name,
)
user.set_password("Tenhal@123")
user.save()
return user
class Organization(models.Model, LocalizedNameMixin):
dealer = models.ForeignKey(

View File

@ -195,39 +195,6 @@ def create_ledger_vendor(sender, instance, created, **kwargs):
else:
instance.update_vendor_model()
@receiver(post_save, sender=models.CustomerModel)
def create_customer_user(sender, instance, created, **kwargs):
"""
Connects to the `post_save` signal of the `CustomerModel` to create a user object
associated with the customer instance whenever a new `CustomerModel` instance is
created. Retrieves customer-specific information from `additional_info` to initialize
and configure the associated user object. Ensures the user object created has
unusable passwords by default.
:param sender: The model class that sends the signal (`CustomerModel`).
:param instance: The instance of `CustomerModel` that triggered the signal.
:param created: A boolean indicating whether a new instance was created.
:param kwargs: Additional keyword arguments passed by the signal.
:return: None
"""
if created:
try:
first_name = instance.additional_info.get("customer_info").get("first_name")
last_name = instance.additional_info.get("customer_info").get("last_name")
user = User.objects.create(
username=instance.email,
email=instance.email,
first_name=first_name if first_name else '',
last_name=last_name if last_name else '',
)
instance.additional_info.update({"user_info": to_dict(user)})
user.set_unusable_password()
user.save()
instance.user = user
instance.save()
except Exception as e:
print(e)
# Create Item
@receiver(post_save, sender=models.Car)
def create_item_model(sender, instance, created, **kwargs):
@ -769,7 +736,13 @@ def update_finance_cost(sender, instance, created, **kwargs):
if created:
entity = instance.car.dealer.entity
vendor = instance.car.vendor
name = f"{instance.car.vin}-{instance.car.id_car_make.name}-{instance.car.id_car_model.name}-{instance.car.year}-{vendor.vendor_name}"
vin = instance.car.vin if instance.car.vin else ""
make = instance.car.id_car_make.name if instance.car.id_car_make else ""
model = instance.car.id_car_model.name if instance.car.id_car_model else ""
year = instance.car.year
vendor_name = vendor.name if vendor else ""
name = f"{vin}-{make}-{model}-{year}-{vendor_name}"
ledger,_ = LedgerModel.objects.get_or_create(name=name, entity=entity)
save_journal(instance,ledger,vendor)

View File

@ -76,26 +76,26 @@ urlpatterns = [
# CRM URLs
path("customers/", views.CustomerListView.as_view(), name="customer_list"),
path(
"customers/<uuid:pk>/",
"customers/<int:pk>/",
views.CustomerDetailView.as_view(),
name="customer_detail",
),
path(
"customers/create/", views.CustomerCreateView, name="customer_create"
"customers/create/", views.CustomerCreateView.as_view(), name="customer_create"
),
path(
"customers/<uuid:pk>/update/",
views.CustomerUpdateView,
"customers/<int:pk>/update/",
views.CustomerUpdateView.as_view(),
name="customer_update",
),
path("customers/<uuid:pk>/delete/", views.delete_customer, name="customer_delete"),
path("customers/<int:pk>/delete/", views.delete_customer, name="customer_delete"),
path(
"customers/<str:customer_id>/opportunities/create/",
views.OpportunityCreateView.as_view(),
name="create_opportunity",
),
path(
"customers/<uuid:pk>/add-note/",
"customers/<int:pk>/add-note/",
views.add_note_to_customer,
name="add_note_to_customer",
),
@ -380,7 +380,7 @@ path(
),
path(
"organizations/create/",
views.OrganizationCreateView,
views.OrganizationCreateView.as_view(),
name="organization_create",
),
path(

View File

@ -388,7 +388,7 @@ def get_financial_values(model):
if i.item_model.additional_info["additional_services"]:
additional_services.extend(
[
{"name": x.name, "price": x.price}
{"name": x['name'], "price": x["price"]}
for x in i.item_model.additional_info["additional_services"]
]
)
@ -1042,6 +1042,7 @@ class CarFinanceCalculator:
total_vat_amount = total_price_discounted * self.vat_rate
return {
"total_price_before_discount": round(total_price, 2), # total_price_before_discount,
"total_price": round(total_price_discounted, 2), # total_price_discounted,
"total_vat_amount": round(total_vat_amount, 2), # total_vat_amount,
"total_discount": round(total_discount,2),
@ -1055,6 +1056,7 @@ class CarFinanceCalculator:
"cars": [self._get_car_data(item) for item in self.item_transactions],
"quantity": sum(self._get_quantity(item) for item in self.item_transactions),
"total_price": totals['total_price'],
"total_price_before_discount": totals['total_price_before_discount'],
"total_vat": totals['total_vat_amount'] + totals['total_price'],
"total_vat_amount": totals['total_vat_amount'],
"total_discount": totals['total_discount'],

View File

@ -0,0 +1,9 @@
from django.core.validators import RegexValidator
from django.utils.translation import gettext_lazy as _
class SaudiPhoneNumberValidator(RegexValidator):
def __init__(self):
super().__init__(
regex=r'^(\+9665|05)[0-9]{8}$',
message=_("Enter a valid Saudi phone number (05XXXXXXXX or +9665XXXXXXXX)")
)

View File

@ -1884,7 +1884,7 @@ class CustomerListView(LoginRequiredMixin, PermissionRequiredMixin, ListView):
:ivar permission_required: A list of permissions required to access the view.
:type permission_required: list
"""
model = CustomerModel
model = models.Customer
home_label = _("customers")
context_object_name = "customers"
paginate_by = 10
@ -1895,9 +1895,7 @@ class CustomerListView(LoginRequiredMixin, PermissionRequiredMixin, ListView):
def get_queryset(self):
query = self.request.GET.get("q")
dealer = get_user_type(self.request)
customers = dealer.entity.get_customers().filter(
additional_info__type="customer"
)
customers = dealer.customers.all()
return apply_search_filters(customers, query)
def get_context_data(self, **kwargs):
@ -1923,7 +1921,7 @@ class CustomerDetailView(LoginRequiredMixin, PermissionRequiredMixin, DetailView
:ivar permission_required: The list of permissions required to access this view.
:type permission_required: list[str]
"""
model = CustomerModel
model = models.Customer
template_name = "customers/view_customer.html"
context_object_name = "customer"
permission_required = ["django_ledger.view_customermodel"]
@ -1935,9 +1933,9 @@ class CustomerDetailView(LoginRequiredMixin, PermissionRequiredMixin, DetailView
context["customer_notes"] = models.Notes.objects.filter(
object_id=self.object.pk
)
estimates = entity.get_estimates().filter(customer=self.object)
invoices = entity.get_invoices().filter(customer=self.object)
# txs = entity. transactions(customer=self.object)
estimates = entity.get_estimates().filter(customer=self.object.customer_model)
invoices = entity.get_invoices().filter(customer=self.object.customer_model)
total = estimates.count() + invoices.count()
context["estimates"] = estimates
@ -1947,7 +1945,7 @@ class CustomerDetailView(LoginRequiredMixin, PermissionRequiredMixin, DetailView
@login_required
def add_note_to_customer(request, customer_id):
def add_note_to_customer(request, pk):
"""
This function allows authenticated users to add a note to a specific customer. The
note creation is handled by a form, which is validated after submission. If the form
@ -1964,7 +1962,7 @@ def add_note_to_customer(request, customer_id):
POST request, it renders the note form template with context including
the form and customer.
"""
customer = get_object_or_404(CustomerModel, pk=customer_id)
customer = get_object_or_404(models.Customer, pk=pk)
if request.method == "POST":
form = forms.NoteForm(request.POST)
if form.is_valid():
@ -2018,131 +2016,163 @@ def add_activity_to_customer(request, pk):
)
@login_required
@permission_required("django_ledger.add_customermodel", raise_exception=True)
def CustomerCreateView(request):
"""
Handles the creation of a new customer within the system. This view ensures that proper permissions
and request methods are utilized. It provides feedback to the user about the success or failure of
the customer creation process. When the form is submitted and valid, it checks for duplicate
customers based on the email provided before proceeding with the customer creation.
class CustomerCreateView(LoginRequiredMixin, PermissionRequiredMixin, CreateView):
model = models.Customer
form_class = forms.CustomerForm
permission_required = ["django_ledger.add_customermodel"]
template_name = "customers/customer_form.html"
success_url = reverse_lazy("customer_list")
success_message = "Customer created successfully"
:param request: The HTTP request object containing metadata about the request initiated by the user.
:type request: HttpRequest
:return: The rendered form page or a redirect to the customer list page upon successful creation.
:rtype: HttpResponse
:raises PermissionDenied: If the user does not have the required permissions to access the view.
"""
form = forms.CustomerForm()
if request.method == "POST":
form = forms.CustomerForm(request.POST)
dealer = get_user_type(request)
def form_valid(self, form):
dealer = get_user_type(self.request)
form.instance.dealer = dealer
user = form.instance.create_user_model()
customer = form.instance.create_customer_model()
if form.is_valid():
if (
dealer.entity.get_customers()
.filter(email=form.cleaned_data["email"])
.exists()
):
messages.error(request, _("Customer with this email already exists"))
else:
customer_name = (
f"{form.cleaned_data['first_name']} "
f"{form.cleaned_data['last_name']}"
)
customer_dict = {
x: request.POST[x]
for x in request.POST
if x != "csrfmiddlewaretoken"
}
try:
customer = dealer.entity.create_customer(
commit=False,
customer_model_kwargs={
"customer_name": customer_name,
"address_1": form.cleaned_data["address"],
"phone": form.cleaned_data["phone_number"],
"email": form.cleaned_data["email"],
},
)
customer.additional_info.update({"customer_info": customer_dict})
customer.additional_info.update({"type": "customer"})
customer.save()
form.instance.user = user
form.instance.customer_model = customer
messages.success(request, _("Customer created successfully"))
return redirect("customer_list")
return super().form_valid(form)
except Exception as e:
messages.error(request, _(f"An error occurred: {str(e)}"))
else:
messages.error(request, _("Please correct the errors below"))
# @login_required
# @permission_required("django_ledger.add_customermodel", raise_exception=True)
# def CustomerCreateView(request):
# """
# Handles the creation of a new customer within the system. This view ensures that proper permissions
# and request methods are utilized. It provides feedback to the user about the success or failure of
# the customer creation process. When the form is submitted and valid, it checks for duplicate
# customers based on the email provided before proceeding with the customer creation.
return render(request, "customers/customer_form.html", {"form": form})
# :param request: The HTTP request object containing metadata about the request initiated by the user.
# :type request: HttpRequest
# :return: The rendered form page or a redirect to the customer list page upon successful creation.
# :rtype: HttpResponse
# :raises PermissionDenied: If the user does not have the required permissions to access the view.
# """
# form = forms.CustomerForm()
# if request.method == "POST":
# form = forms.CustomerForm(request.POST)
# dealer = get_user_type(request)
# if form.is_valid():
# if (
# dealer.entity.get_customers()
# .filter(email=form.cleaned_data["email"])
# .exists()
# ):
# messages.error(request, _("Customer with this email already exists"))
# else:
# customer_name = (
# f"{form.cleaned_data['first_name']} "
# f"{form.cleaned_data['last_name']}"
# )
# customer_dict = {
# x: request.POST[x]
# for x in request.POST
# if x != "csrfmiddlewaretoken"
# }
# try:
# customer = dealer.entity.create_customer(
# commit=False,
# customer_model_kwargs={
# "customer_name": customer_name,
# "address_1": form.cleaned_data["address"],
# "phone": form.cleaned_data["phone_number"],
# "email": form.cleaned_data["email"],
# },
# )
# customer.additional_info.update({"customer_info": customer_dict})
# customer.additional_info.update({"type": "customer"})
# customer.save()
# messages.success(request, _("Customer created successfully"))
# return redirect("customer_list")
# except Exception as e:
# messages.error(request, _(f"An error occurred: {str(e)}"))
# else:
# messages.error(request, _("Please correct the errors below"))
# return render(request, "customers/customer_form.html", {"form": form})
@login_required
@permission_required("django_ledger.change_customermodel", raise_exception=True)
def CustomerUpdateView(request, pk):
"""
Updates the details of an existing customer in the database. This view is
accessible only to logged-in users with the appropriate permissions. It
handles both GET (form rendering with pre-filled customer data) and POST
(submitting updates) requests. Data validation and customer updates are
conducted based on the received form data.
class CustomerUpdateView(LoginRequiredMixin, PermissionRequiredMixin, UpdateView):
model = models.Customer
form_class = forms.CustomerForm
permission_required = ["django_ledger.change_customermodel"]
template_name = "customers/customer_form.html"
success_url = reverse_lazy("customer_list")
success_message = "Customer updated successfully"
:param request: The HTTP request object used to determine the request method,
access user session details, and provide request data such as POST content.
Expected to contain the updated customer data if request method is POST.
:type request: HttpRequest
def form_valid(self, form):
form.instance.update_user_model()
form.instance.update_customer_model()
return super().form_valid(form)
:param pk: The primary key of the CustomerModel object that is to be updated.
:type pk: int
# @login_required
# @permission_required("django_ledger.change_customermodel", raise_exception=True)
# def CustomerUpdateView(request, pk):
# """
# Updates the details of an existing customer in the database. This view is
# accessible only to logged-in users with the appropriate permissions. It
# handles both GET (form rendering with pre-filled customer data) and POST
# (submitting updates) requests. Data validation and customer updates are
# conducted based on the received form data.
:return: A rendered HTML template displaying the customer form pre-filled
with existing data if a GET request is received. On successful form
submission (POST request), redirects to the customer list page
and displays a success message. In case of invalid data or errors,
returns the rendered form template with the validation errors.
:rtype: HttpResponse
"""
customer = get_object_or_404(CustomerModel, pk=pk)
if request.method == "POST":
# form = forms.CustomerForm(request.POST, instance=customer)
customer_dict = {
x: request.POST[x] for x in request.POST if x != "csrfmiddlewaretoken"
}
dealer = get_user_type(request)
customer_name = customer_dict["first_name"] + " " + customer_dict["last_name"]
# :param request: The HTTP request object used to determine the request method,
# access user session details, and provide request data such as POST content.
# Expected to contain the updated customer data if request method is POST.
# :type request: HttpRequest
instance = dealer.entity.get_customers().get(pk=pk)
instance.customer_name = customer_name
instance.address_1 = customer_dict["address"]
instance.phone = customer_dict["phone_number"]
instance.email = customer_dict["email"]
# :param pk: The primary key of the CustomerModel object that is to be updated.
# :type pk: int
customer_dict["pk"] = str(instance.pk)
instance.additional_info.update({"customer_info": customer_dict})
try:
user = User.objects.filter(
pk=int(instance.additional_info["user_info"]["id"])
).first()
if user:
user.username = customer_dict["email"]
user.email = customer_dict["email"]
user.save()
except Exception as e:
raise Exception(e)
# :return: A rendered HTML template displaying the customer form pre-filled
# with existing data if a GET request is received. On successful form
# submission (POST request), redirects to the customer list page
# and displays a success message. In case of invalid data or errors,
# returns the rendered form template with the validation errors.
# :rtype: HttpResponse
# """
# customer = get_object_or_404(CustomerModel, pk=pk)
# if request.method == "POST":
# # form = forms.CustomerForm(request.POST, instance=customer)
# customer_dict = {
# x: request.POST[x] for x in request.POST if x != "csrfmiddlewaretoken"
# }
# dealer = get_user_type(request)
# customer_name = customer_dict["first_name"] + " " + customer_dict["last_name"]
instance.save()
messages.success(request, _("Customer updated successfully"))
return redirect("customer_list")
else:
form = forms.CustomerForm(
initial=customer.additional_info["customer_info"]
if "customer_info" in customer.additional_info
else {}
)
return render(request, "customers/customer_form.html", {"form": form})
# instance = dealer.entity.get_customers().get(pk=pk)
# instance.customer_name = customer_name
# instance.address_1 = customer_dict["address"]
# instance.phone = customer_dict["phone_number"]
# instance.email = customer_dict["email"]
# customer_dict["pk"] = str(instance.pk)
# instance.additional_info.update({"customer_info": customer_dict})
# try:
# user = User.objects.filter(
# pk=int(instance.additional_info["user_info"]["id"])
# ).first()
# if user:
# user.username = customer_dict["email"]
# user.email = customer_dict["email"]
# user.save()
# except Exception as e:
# raise Exception(e)
# instance.save()
# messages.success(request, _("Customer updated successfully"))
# return redirect("customer_list")
# else:
# form = forms.CustomerForm(
# initial=customer.additional_info["customer_info"]
# if "customer_info" in customer.additional_info
# else {}
# )
# return render(request, "customers/customer_form.html", {"form": form})
@login_required
@ -2162,9 +2192,11 @@ def delete_customer(request, pk):
:return: A redirect response to the customer list page.
:rtype: HttpResponseRedirect
"""
customer = get_object_or_404(models.CustomerModel, pk=pk)
user = User.objects.get(email=customer.email)
customer = get_object_or_404(models.Customer, pk=pk)
customer_model = customer.customer_model
user = customer.user
customer.active = False
customer_model.active = False
user.is_active = False
customer.save()
user.save()
@ -2838,59 +2870,77 @@ class OrganizationDetailView(LoginRequiredMixin, DetailView):
context_object_name = "organization"
@login_required
def OrganizationCreateView(request):
"""
Handles the creation of a new organization via a web form. This view allows the
authenticated user to submit data for creating an organization. If a POST request
is received, it validates the data, checks for duplicate organizations, and
creates a customer linked to the organization, including its associated
information such as address, phone number, and logo. Upon success, the user
is redirected to the organization list, and a success message is displayed.
class OrganizationCreateView(LoginRequiredMixin, PermissionRequiredMixin, CreateView):
model = models.Organization
form_class = forms.OrganizationForm
permission_required = ["django_ledger.add_customermodel"]
template_name = "customers/organization_form.html"
success_url = reverse_lazy("organization_list")
success_message = "Customer created successfully"
:param request: The HTTP request object containing data for creating an organization.
:type request: HttpRequest
:return: An HTTP response object rendering the organization create form page or
redirecting the user after a successful creation.
:rtype: HttpResponse
"""
if request.method == "POST":
form = forms.OrganizationForm(request.POST)
if CustomerModel.objects.filter(email=request.POST["email"]).exists():
messages.error(
request, _("An organization with this email already exists.")
)
return redirect("organization_create")
def form_valid(self, form):
dealer = get_user_type(self.request)
form.instance.dealer = dealer
user = form.instance.create_user_model()
customer = form.instance.create_customer_model()
form.instance.customer_type = 'organization'
form.instance.user = user
form.instance.customer_model = customer
organization_dict = {
x: request.POST[x] for x in request.POST if x != "csrfmiddlewaretoken"
}
dealer = get_user_type(request)
name = organization_dict["first_name"] + " " + organization_dict["last_name"]
customer = dealer.entity.create_customer(
commit=False,
customer_model_kwargs={
"customer_name": name,
"address_1": organization_dict["address"],
"phone": organization_dict["phone_number"],
"email": organization_dict["email"],
},
)
image = request.FILES.get("logo")
if image:
file_name = default_storage.save("images/{}".format(image.name), image)
file_url = default_storage.url(file_name)
return super().form_valid(form)
# @login_required
# def OrganizationCreateView(request):
# """
# Handles the creation of a new organization via a web form. This view allows the
# authenticated user to submit data for creating an organization. If a POST request
# is received, it validates the data, checks for duplicate organizations, and
# creates a customer linked to the organization, including its associated
# information such as address, phone number, and logo. Upon success, the user
# is redirected to the organization list, and a success message is displayed.
organization_dict["logo"] = file_url
organization_dict["pk"] = str(customer.pk)
customer.additional_info.update({"customer_info": organization_dict})
customer.additional_info.update({"type": "organization"})
customer.save()
messages.success(request, _("Organization created successfully"))
return redirect("organization_list")
else:
form = forms.OrganizationForm()
return render(request, "organizations/organization_form.html", {"form": form})
# :param request: The HTTP request object containing data for creating an organization.
# :type request: HttpRequest
# :return: An HTTP response object rendering the organization create form page or
# redirecting the user after a successful creation.
# :rtype: HttpResponse
# """
# if request.method == "POST":
# form = forms.OrganizationForm(request.POST)
# if CustomerModel.objects.filter(email=request.POST["email"]).exists():
# messages.error(
# request, _("An organization with this email already exists.")
# )
# return redirect("organization_create")
# organization_dict = {
# x: request.POST[x] for x in request.POST if x != "csrfmiddlewaretoken"
# }
# dealer = get_user_type(request)
# name = organization_dict["first_name"] + " " + organization_dict["last_name"]
# customer = dealer.entity.create_customer(
# commit=False,
# customer_model_kwargs={
# "customer_name": name,
# "address_1": organization_dict["address"],
# "phone": organization_dict["phone_number"],
# "email": organization_dict["email"],
# },
# )
# image = request.FILES.get("logo")
# if image:
# file_name = default_storage.save("images/{}".format(image.name), image)
# file_url = default_storage.url(file_name)
# organization_dict["logo"] = file_url
# organization_dict["pk"] = str(customer.pk)
# customer.additional_info.update({"customer_info": organization_dict})
# customer.additional_info.update({"type": "organization"})
# customer.save()
# messages.success(request, _("Organization created successfully"))
# return redirect("organization_list")
# else:
# form = forms.OrganizationForm()
# return render(request, "organizations/organization_form.html", {"form": form})
@login_required
@ -3639,8 +3689,9 @@ def create_estimate(request, pk=None):
data = json.loads(request.body)
title = data.get("title")
customer_id = data.get("customer")
terms = data.get("terms")
customer = entity.get_customers().filter(pk=customer_id).first()
# terms = data.get("terms")
# customer = entity.get_customers().filter(pk=customer_id).first()
customer = models.Customer.objects.filter(pk=customer_id).first()
items = data.get("item", [])
quantities = data.get("quantity", [])
@ -3679,7 +3730,7 @@ def create_estimate(request, pk=None):
{"status": "error", "message": _("Quantity must be less than or equal to the number of cars in stock")},
)
estimate = entity.create_estimate(
estimate_title=title, customer_model=customer, contract_terms=terms
estimate_title=title, customer_model=customer.customer_model, contract_terms="fixed"
)
if isinstance(items, list):
item_quantity_map = {}
@ -3773,12 +3824,11 @@ def create_estimate(request, pk=None):
}
)
form = forms.EstimateModelCreateForm(
entity_slug=entity.slug, user_model=entity.admin
)
form.fields["customer"].queryset = entity.get_customers().filter(
active=True, additional_info__type="customer"
)
# form = forms.EstimateModelCreateForm(
# entity_slug=entity.slug, user_model=entity.admin
# )
form = forms.EstimateModelCreateForm()
form.fields["customer"].queryset = dealer.customers.all()
if pk:
opportunity = models.Opportunity.objects.get(pk=pk)
@ -3988,13 +4038,9 @@ class EstimatePreviewView(LoginRequiredMixin, PermissionRequiredMixin, DetailVie
dealer = get_user_type(self.request)
estimate = kwargs.get("object")
if estimate.get_itemtxs_data():
data = get_financial_values(estimate)
kwargs["vat_amount"] = data["vat_amount"]
kwargs["total"] = data["grand_total"]
kwargs["discount_amount"] = data["discount_amount"]
kwargs["vat"] = data["vat"]
kwargs["additional_services"] = data["additional_services"]
# data = get_financial_values(estimate)
calculator = CarFinanceCalculator(estimate)
kwargs["data"] = calculator.get_finance_data()
kwargs["dealer"] = dealer
return super().get_context_data(**kwargs)
@ -6221,11 +6267,12 @@ def send_email_view(request, pk):
{dealer.phone_number}
هيكل | Haikal
"""
subject = _("Quotation")
# subject = _("Quotation")
send_email(
str(settings.DEFAULT_FROM_EMAIL),
estimate.customer.email,
subject,
"عرض سعر - Quotation",
msg,
)

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.1 MiB

View File

@ -22,7 +22,7 @@
</div>
<div class="row mb-3">
<div class="col-sm-6 col-md-8">
<form method="post" class="form row g-3 needs-validation" novalidate>
<form method="post" class="form row g-3 needs-validation" enctype="multipart/form-data" novalidate>
{% csrf_token %}
{{ form|crispy }}
<div class="col-12">

View File

@ -70,17 +70,17 @@
</td>
<td class="name align-middle white-space-nowrap ps-0">
<div class="d-flex align-items-center">
<div><a class="fs-8 fw-bold" href="{% url 'customer_detail' customer.pk %}">{{ customer.customer_name }}</a>
<div><a class="fs-8 fw-bold" href="{% url 'customer_detail' customer.pk %}">{{ customer.full_name }}</a>
<div class="d-flex align-items-center">
</div>
</div>
</div>
</td>
<td class="email align-middle white-space-nowrap fw-semibold ps-4 border-end border-translucent"><a class="text-body-highlight" href="">{{ customer.email }}</a></td>
<td class="phone align-middle white-space-nowrap fw-semibold ps-4 border-end border-translucent"><a class="text-body-highlight" href="tel:{{ customer.phone }}">{{ customer.phone }}</a></td>
<td class="contact align-middle white-space-nowrap ps-4 border-end border-translucent fw-semibold text-body-highlight">{{ customer.additional_info.customer_info.national_id }}</td>
<td class="phone align-middle white-space-nowrap fw-semibold ps-4 border-end border-translucent"><a class="text-body-highlight" href="tel:{{ customer.phone }}">{{ customer.phone_number }}</a></td>
<td class="contact align-middle white-space-nowrap ps-4 border-end border-translucent fw-semibold text-body-highlight">{{ customer.national_id }}</td>
<td class="company align-middle white-space-nowrap text-body-tertiary text-opacity-85 ps-4 border-end border-translucent fw-semibold text-body-highlight">
{{ customer.address_1 }}</td>
{{ customer.address }}</td>
<td class="date align-middle white-space-nowrap text-body-tertiary text-opacity-85 ps-4 text-body-tertiary">
{% if customer.active %}
<span class="badge badge-phoenix badge-phoenix-success"><i class="fas fa-check"></i> {{customer.active}}</span>

View File

@ -41,10 +41,10 @@
<div class="card-body d-flex flex-column justify-content-between pb-3">
<div class="row align-items-center g-5 mb-3 text-center text-sm-start">
<div class="col-12 col-sm-auto mb-sm-2">
<div class="avatar avatar-5xl"><img class="rounded-circle" src="{% static "images/team/15.webp" %}" alt="" /></div>
<div class="avatar avatar-5xl"><img class="rounded-circle" src="{{ customer.image.url }}" alt="" /></div>
</div>
<div class="col-12 col-sm-auto flex-1">
<h3>{{ customer.customer_name }}</h3>
<h3>{{ customer.full_name }}</h3>
<p class="text-body-secondary">{{ customer.created|timesince}}</p>
</div>
</div>
@ -69,11 +69,11 @@
<button class="btn btn-link p-0"><span class="fas fa-pen fs-8 ms-3 text-body-quaternary"></span></button>
</div>
<h5 class="text-body-secondary">{{ _("Address") }}</h5>
<p class="text-body-secondary">{{ customer.address_1}}</p>
<p class="text-body-secondary">{{ customer.address}}</p>
<div class="mb-3">
<h5 class="text-body-secondary">{% trans 'Email' %}</h5><a href="{{ customer.email}}">{{ customer.email }}</a>
</div>
<h5 class="text-body-secondary">{% trans 'Phone Number' %}</h5><a class="text-body-secondary" href="#">{{ customer.phone }}</a>
<h5 class="text-body-secondary">{% trans 'Phone Number' %}</h5><a class="text-body-secondary" href="#">{{ customer.phone_number }}</a>
</div>
</div>
</div>

View File

@ -13,7 +13,7 @@
</style>
{% endblock customCSS %}
{% block content %}
<div class="row mt-4">
<div class="row mt-4">
{% if not items %}
<div class="alert alert-outline-warning d-flex align-items-center" role="alert">
<i class="fa-solid fa-circle-info fs-6"></i>
@ -32,7 +32,7 @@
<h3 class="text-center"><i class="fa-regular fa-file-lines"></i> {% trans "Create Quotation" %}</h3>
{% csrf_token %}
<div class="row g-3 col-10">
{{ form|crispy }}
{{ form|crispy }}
<div class="row mt-5">
<div id="formrow">
<h3 class="text-start"><i class="fa-solid fa-car-side"></i> {{ _("Cars") }}</h3>
@ -70,7 +70,7 @@
{% endblock content %}
{% block customJS %}
<script>
<script>
const Toast = Swal.mixin({
toast: true,
position: "top-end",
@ -98,7 +98,7 @@
</div>
<div class="mb-2 col-sm-2">
<input class="form-control quantity" type="number" placeholder="Quantity" name="quantity[]" required>
</div>
</div>
<div class="mb-2 col-sm-1">
<button class="btn btn-danger removeBtn"><i class="fa-solid fa-trash"></i> {{ _("Remove") }}</button>
</div>
@ -133,7 +133,6 @@
csrfmiddlewaretoken: document.querySelector('[name=csrfmiddlewaretoken]').value,
title: document.querySelector('[name=title]').value,
customer: document.querySelector('[name=customer]').value,
terms: document.querySelector('[name=terms]').value,
item: [],
quantity: [],
opportunity_id: "{{opportunity_id}}"
@ -148,7 +147,7 @@
formData.quantity.push(input.value);
});
console.log(formData);
try {
// Send data to the server using fetch
const response = await fetch("{% url 'estimate_create' %}", {
@ -175,8 +174,8 @@
notify("error","Unexpected response from the server");
}
} catch (error) {
notify("error", error);
notify("error", error);
}
});
</script>

View File

@ -99,23 +99,25 @@
<td></td>
<td>
<div class="qr-code">
<img class="rounded-soft" src="{{ dealer.logo.url|default:'' }}" alt="Dealer Logo"/>
{% if dealer.logo %}
<img class="rounded-soft" src="{{ dealer.logo.url|default:'' }}" alt="Dealer Logo"/>
{% endif %}
</div>
</td>
</tr>
<tr>
<td><strong>{{ dealer.name }}</strong></td>
<td></td>
<td class="ps-1"><strong>Customer Name</strong></td>
<td class="text-center">{{ dealer.arabic_name }}<br>{{ dealer.name }}</td>
<td class="text-end"><strong>{{ dealer.arabic_name }}</strong></td>
</tr>
<tr>
<td><strong>Address</strong></td>
<td>{{ dealer.address }}</td>
<td class="ps-1"><strong>Address</strong></td>
<td class="text-center">{{ dealer.address }}</td>
<td class="text-end"> <strong>العنوان</strong></td>
</tr>
<tr>
<td><strong>Phone</strong></td>
<td>{{ dealer.phone_number }}</td>
<td class="ps-1"><strong>Phone</strong></td>
<td class="text-center">{{ dealer.phone_number }}</td>
<td class="text-end"><strong>جوال</strong></td>
</tr>
<tr>
@ -190,8 +192,8 @@
<tr>
<td class="ps-1 fs-10 align-content-center" colspan="5"></td>
<td class="text-center fs-10 align-content-center">{{ data.quantity|floatformat:-1 }}</td>
<td class="text-center fs-10 align-content-center">{{ data.total_price|floatformat:2 }}</td>
<td class="text-center fs-10 align-content-center">{{ data.total_vat|floatformat:2 }}</td>
<td class="text-center fs-10 align-content-center">{{ data.total_price_before_discount|floatformat:2 }}</td>
<td class="text-center fs-10 align-content-center">{{ data.grand_total|floatformat:2 }}</td>
</tr>
</tbody>
</table>

View File

@ -96,46 +96,40 @@
<h5 class="fs-5">Tax&nbsp;Invoice&nbsp;&nbsp;/&nbsp;&nbsp;فاتورة&nbsp;ضريبية</h5>
</div>
<div class="invoice-details p-1">
<table class="table table-sm table-responsive border-gray-50">
<div class="d-flex justify-content-center align-items-center">
<div class="qr-code">
<img src="{% static 'qr_code/Marwan_qr.png' %}" alt="QR Code">
</div>
</div>
<div class="d-flex justify-content-end align-items-end">
<div class="dealer-logo ">
{% if dealer.logo %}
<img class="rounded-soft" src="{{ dealer.logo.url|default:'' }}" alt="Dealer Logo"/>
{% endif %}
</div>
</div>
<table class="table table-sm table-bordered border-gray-50">
<tr>
<td>
<div class="d-flex justify-content-center align-items-center">
<div class="qr-code">
<img src="{% static 'qr_code/Marwan_qr.png' %}" alt="QR Code">
</div>
</div>
</td>
<td></td>
<td>
<div class="d-flex justify-content-end align-items-end">
<div class="dealer-logo ">
{% if dealer.logo %}
<img class="rounded-soft" src="{{ dealer.logo.url|default:'' }}" alt="Dealer Logo"/>
{% endif %}
</div>
</div>
</td>
</tr>
<tr>
<td><strong>{{ dealer.name }}</strong></td>
<td></td>
<td class="text-end"><strong>{{ dealer.arabic_name }}</strong></td>
</tr>
<tr>
<td><strong>Address</strong></td>
<td>{{ dealer.address }}</td>
<td class="text-end"><strong>العنوان</strong></td>
</tr>
<tr>
<td><strong>Phone</strong></td>
<td>{{ dealer.phone_number }}</td>
<td class="text-end"><strong>جوال</strong></td>
</tr>
<tr>
<td><strong>VAT Number</strong></td>
<td>{{ dealer.vrn }}</td>
<td class="text-end"><strong>الرقم الضريبي</strong></td>
</tr>
<td class="ps-1"><strong>Customer Name</strong></td>
<td class="text-center">{{ dealer.arabic_name }}<br>{{ dealer.name }}</td>
<td class="text-end"><strong>اسم&nbsp;العميل</strong></td>
</tr>
<tr>
<td class="ps-1"><strong>Address</strong></td>
<td class="text-center">{{ dealer.address }}</td>
<td class="text-end"> <strong>العنوان</strong></td>
</tr>
<tr>
<td class="ps-1"><strong>Phone</strong></td>
<td class="text-center">{{ dealer.phone_number }}</td>
<td class="text-end"><strong>جوال</strong></td>
</tr>
<tr>
<td class="ps-1"><strong>VAT Number</strong></td>
<td class="text-center">{{ dealer.vrn }}</td>
<td class="text-end p-1"><strong>الرقم الضريبي</strong></td>
</tr>
</table>
<table class="table table-sm table-bordered border-gray-50">
@ -203,8 +197,8 @@
<tr>
<td class="ps-1 fs-10 align-content-center" colspan="5"></td>
<td class="text-center fs-10 align-content-center">{{ data.quantity|floatformat }}</td>
<td class="text-center fs-10 align-content-center">{{ data.total_price|floatformat }}</td>
<td class="text-center fs-10 align-content-center">{{ data.total_vat|floatformat }}</td>
<td class="text-center fs-10 align-content-center">{{ data.total_price_before_discount|floatformat }}</td>
<td class="text-center fs-10 align-content-center">{{ data.grand_total|floatformat }}</td>
</tr>
</tbody>
</table>