update the preview
This commit is contained in:
parent
9d6d69f1d0
commit
b44fb270fa
@ -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(
|
||||
|
||||
18
inventory/migrations/0004_customer_image.py
Normal file
18
inventory/migrations/0004_customer_image.py
Normal 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'),
|
||||
),
|
||||
]
|
||||
20
inventory/migrations/0005_customer_customer_model.py
Normal file
20
inventory/migrations/0005_customer_customer_model.py
Normal 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'),
|
||||
),
|
||||
]
|
||||
@ -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'),
|
||||
),
|
||||
]
|
||||
18
inventory/migrations/0007_customer_customer_type.py
Normal file
18
inventory/migrations/0007_customer_customer_type.py
Normal 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'),
|
||||
),
|
||||
]
|
||||
@ -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(
|
||||
|
||||
@ -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)
|
||||
|
||||
|
||||
@ -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(
|
||||
|
||||
@ -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'],
|
||||
|
||||
@ -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)")
|
||||
)
|
||||
@ -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,
|
||||
)
|
||||
|
||||
|
||||
BIN
static/images/customers/image.png
Normal file
BIN
static/images/customers/image.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 1.1 MiB |
@ -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">
|
||||
|
||||
@ -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>
|
||||
|
||||
@ -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>
|
||||
|
||||
@ -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>
|
||||
|
||||
@ -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>
|
||||
|
||||
@ -96,46 +96,40 @@
|
||||
<h5 class="fs-5">Tax Invoice / فاتورة ضريبية</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>اسم العميل</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>
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user