Compare commits
3 Commits
cb4366f028
...
5dee96ec09
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
5dee96ec09 | ||
|
|
b44fb270fa | ||
|
|
9d6d69f1d0 |
@ -16,7 +16,6 @@ from django.utils.decorators import method_decorator
|
||||
from inventory import models as inventory_models
|
||||
|
||||
|
||||
|
||||
class LoginView(APIView):
|
||||
permission_classes = [permissions.AllowAny,]
|
||||
|
||||
|
||||
@ -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,8 @@ from .models import (
|
||||
CarModel,
|
||||
SaleOrder,
|
||||
CarMake,
|
||||
Customer,
|
||||
Organization,
|
||||
DealerSettings
|
||||
)
|
||||
from django_ledger import models as ledger_models
|
||||
@ -59,6 +63,13 @@ 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
|
||||
@ -105,20 +116,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
|
||||
@ -148,7 +153,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 = [
|
||||
@ -162,64 +169,81 @@ 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):
|
||||
class OrganizationForm(forms.ModelForm):
|
||||
"""
|
||||
Represents a form for collecting and handling organization-specific details.
|
||||
|
||||
@ -234,8 +258,10 @@ class OrganizationForm(CustomerForm):
|
||||
:ivar logo: Optional field to upload the logo of the organization.
|
||||
:type logo: forms.ImageField
|
||||
"""
|
||||
contact_person = forms.CharField(required=False)
|
||||
logo = forms.ImageField(required=False)
|
||||
phone_number = SaudiPhoneNumberField(label=_('Phone Number'),required=True)
|
||||
class Meta:
|
||||
model = Organization
|
||||
fields = ["name","arabic_name","email","phone_number","crn","vrn","address","logo"]
|
||||
|
||||
|
||||
class CarForm(
|
||||
@ -292,10 +318,8 @@ class CarForm(
|
||||
self.fields["id_car_model"].choices = [
|
||||
(obj.id_car_model, obj.get_local_name()) for obj in queryset
|
||||
]
|
||||
if "vendor" in self.fields:
|
||||
self.fields["vendor"].queryset = ledger_models.VendorModel.objects.filter(
|
||||
active=True
|
||||
)
|
||||
# if "vendor" in self.fields:
|
||||
# self.fields["vendor"].queryset = dealer.vendors.all()
|
||||
|
||||
|
||||
class CarUpdateForm(forms.ModelForm, AddClassMixin):
|
||||
@ -482,7 +506,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=10,validators=[RegexValidator(regex='^05[0-9]{8}$')], required=True)
|
||||
phone_number = SaudiPhoneNumberField(label=_('Phone Number'))
|
||||
contact_person = forms.CharField(label=_("Contact Person"))
|
||||
|
||||
class Meta:
|
||||
@ -569,6 +593,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 = [
|
||||
@ -787,21 +813,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):
|
||||
@ -998,6 +1010,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),
|
||||
@ -1205,71 +1219,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):
|
||||
@ -1581,21 +1599,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(
|
||||
|
||||
@ -1,4 +1,4 @@
|
||||
# Generated by Django 5.1.7 on 2025-05-04 16:07
|
||||
# Generated by Django 5.1.7 on 2025-05-05 16:32
|
||||
|
||||
import datetime
|
||||
import django.core.validators
|
||||
@ -17,17 +17,11 @@ class Migration(migrations.Migration):
|
||||
initial = True
|
||||
|
||||
dependencies = [
|
||||
# ('appointment', '0002_alter_workinghours_options'),
|
||||
('appointment', '0001_initial'),
|
||||
('auth', '0012_alter_user_first_name_max_length'),
|
||||
('contenttypes', '0002_remove_content_type_name'),
|
||||
('django_ledger', '0021_alter_bankaccountmodel_account_model_and_more'),
|
||||
migrations.swappable_dependency(settings.AUTH_USER_MODEL),
|
||||
migrations.swappable_dependency(settings.DJANGO_LEDGER_ACCOUNT_MODEL),
|
||||
migrations.swappable_dependency(settings.DJANGO_LEDGER_CUSTOMER_MODEL),
|
||||
migrations.swappable_dependency(settings.DJANGO_LEDGER_ENTITY_MODEL),
|
||||
migrations.swappable_dependency(settings.DJANGO_LEDGER_ESTIMATE_MODEL),
|
||||
migrations.swappable_dependency(settings.DJANGO_LEDGER_INVOICE_MODEL),
|
||||
migrations.swappable_dependency(settings.DJANGO_LEDGER_ITEM_MODEL),
|
||||
migrations.swappable_dependency(settings.DJANGO_LEDGER_VENDOR_MODEL),
|
||||
]
|
||||
|
||||
operations = [
|
||||
@ -92,7 +86,7 @@ class Migration(migrations.Migration):
|
||||
fields=[
|
||||
('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
|
||||
('amount', models.DecimalField(decimal_places=2, max_digits=10, verbose_name='amount')),
|
||||
('payment_method', models.CharField(choices=[('cash', 'cash'), ('credit', 'credit'), ('transfer', 'transfer'), ('debit', 'debit'), ('SADAD', 'SADAD')], max_length=50, verbose_name='method')),
|
||||
('payment_method', models.CharField(choices=[('cash', 'cash'), ('credit', 'credit'), ('transfer', 'transfer'), ('debit', 'debit'), ('sadad', 'SADAD')], max_length=50, verbose_name='method')),
|
||||
('reference_number', models.CharField(blank=True, max_length=100, null=True, verbose_name='reference number')),
|
||||
('payment_date', models.DateField(auto_now_add=True, verbose_name='date')),
|
||||
],
|
||||
@ -120,7 +114,7 @@ class Migration(migrations.Migration):
|
||||
('price', models.DecimalField(decimal_places=2, max_digits=14, verbose_name='Price')),
|
||||
('taxable', models.BooleanField(default=False, verbose_name='taxable')),
|
||||
('uom', models.CharField(choices=[('EA', 'Each'), ('PR', 'Pair'), ('SET', 'Set'), ('GAL', 'Gallon'), ('L', 'Liter'), ('M', 'Meter'), ('KG', 'Kilogram'), ('HR', 'Hour'), ('BX', 'Box'), ('RL', 'Roll'), ('PKG', 'Package'), ('DZ', 'Dozen'), ('SQ_M', 'Square Meter'), ('PC', 'Piece'), ('BDL', 'Bundle')], max_length=10, verbose_name='Unit of Measurement')),
|
||||
('item', models.OneToOneField(blank=True, null=True, on_delete=django.db.models.deletion.CASCADE, to=settings.DJANGO_LEDGER_ITEM_MODEL, verbose_name='Item')),
|
||||
('item', models.OneToOneField(blank=True, null=True, on_delete=django.db.models.deletion.CASCADE, to='django_ledger.itemmodel', verbose_name='Item')),
|
||||
],
|
||||
options={
|
||||
'verbose_name': 'Additional Services',
|
||||
@ -140,7 +134,7 @@ class Migration(migrations.Migration):
|
||||
('mileage', models.IntegerField(blank=True, null=True, verbose_name='Mileage')),
|
||||
('receiving_date', models.DateTimeField(verbose_name='Receiving Date')),
|
||||
('hash', models.CharField(blank=True, max_length=64, null=True, verbose_name='Hash')),
|
||||
('vendor', models.ForeignKey(on_delete=django.db.models.deletion.DO_NOTHING, related_name='cars', to=settings.DJANGO_LEDGER_VENDOR_MODEL, verbose_name='Vendor')),
|
||||
('vendor', models.ForeignKey(on_delete=django.db.models.deletion.DO_NOTHING, related_name='cars', to='django_ledger.vendormodel', verbose_name='Vendor')),
|
||||
('id_car_make', models.ForeignKey(blank=True, db_column='id_car_make', null=True, on_delete=django.db.models.deletion.DO_NOTHING, to='inventory.carmake', verbose_name='Make')),
|
||||
],
|
||||
options={
|
||||
@ -322,7 +316,7 @@ class Migration(migrations.Migration):
|
||||
('logo', models.ImageField(blank=True, null=True, upload_to='logos/users', verbose_name='Logo')),
|
||||
('joined_at', models.DateTimeField(auto_now_add=True, verbose_name='Joined At')),
|
||||
('updated_at', models.DateTimeField(auto_now=True, verbose_name='Updated At')),
|
||||
('entity', models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.SET_NULL, to=settings.DJANGO_LEDGER_ENTITY_MODEL)),
|
||||
('entity', models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.SET_NULL, to='django_ledger.entitymodel')),
|
||||
('user', models.OneToOneField(on_delete=django.db.models.deletion.CASCADE, related_name='dealer', to=settings.AUTH_USER_MODEL)),
|
||||
],
|
||||
options={
|
||||
@ -339,7 +333,7 @@ class Migration(migrations.Migration):
|
||||
fields=[
|
||||
('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
|
||||
('name', models.CharField(max_length=100)),
|
||||
('group', models.OneToOneField(on_delete=django.db.models.deletion.CASCADE, to='auth.group', verbose_name='')),
|
||||
('group', models.OneToOneField(on_delete=django.db.models.deletion.CASCADE, to='auth.group', verbose_name='Group')),
|
||||
('dealer', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, related_name='groups', to='inventory.dealer')),
|
||||
],
|
||||
),
|
||||
@ -437,13 +431,13 @@ class Migration(migrations.Migration):
|
||||
fields=[
|
||||
('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
|
||||
('additional_info', models.JSONField(blank=True, default=dict, null=True)),
|
||||
('bill_cash_account', models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.SET_NULL, related_name='bill_cash', to=settings.DJANGO_LEDGER_ACCOUNT_MODEL)),
|
||||
('bill_prepaid_account', models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.SET_NULL, related_name='bill_prepaid', to=settings.DJANGO_LEDGER_ACCOUNT_MODEL)),
|
||||
('bill_unearned_account', models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.SET_NULL, related_name='bill_unearned', to=settings.DJANGO_LEDGER_ACCOUNT_MODEL)),
|
||||
('bill_cash_account', models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.SET_NULL, related_name='bill_cash', to='django_ledger.accountmodel')),
|
||||
('bill_prepaid_account', models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.SET_NULL, related_name='bill_prepaid', to='django_ledger.accountmodel')),
|
||||
('bill_unearned_account', models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.SET_NULL, related_name='bill_unearned', to='django_ledger.accountmodel')),
|
||||
('dealer', models.OneToOneField(blank=True, null=True, on_delete=django.db.models.deletion.CASCADE, related_name='settings', to='inventory.dealer')),
|
||||
('invoice_cash_account', models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.SET_NULL, related_name='invoice_cash', to=settings.DJANGO_LEDGER_ACCOUNT_MODEL)),
|
||||
('invoice_prepaid_account', models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.SET_NULL, related_name='invoice_prepaid', to=settings.DJANGO_LEDGER_ACCOUNT_MODEL)),
|
||||
('invoice_unearned_account', models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.SET_NULL, related_name='invoice_unearned', to=settings.DJANGO_LEDGER_ACCOUNT_MODEL)),
|
||||
('invoice_cash_account', models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.SET_NULL, related_name='invoice_cash', to='django_ledger.accountmodel')),
|
||||
('invoice_prepaid_account', models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.SET_NULL, related_name='invoice_prepaid', to='django_ledger.accountmodel')),
|
||||
('invoice_unearned_account', models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.SET_NULL, related_name='invoice_unearned', to='django_ledger.accountmodel')),
|
||||
],
|
||||
),
|
||||
migrations.CreateModel(
|
||||
@ -485,7 +479,7 @@ class Migration(migrations.Migration):
|
||||
('status', models.CharField(choices=[('new', 'New'), ('pending', 'Pending'), ('in_progress', 'In Progress'), ('qualified', 'Qualified'), ('contacted', 'Contacted'), ('converted', 'Converted'), ('canceled', 'Canceled')], db_index=True, default='new', max_length=50, verbose_name='Status')),
|
||||
('created', models.DateTimeField(auto_now_add=True, db_index=True, verbose_name='Created')),
|
||||
('updated', models.DateTimeField(auto_now=True, verbose_name='Updated')),
|
||||
('customer', models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.CASCADE, related_name='leads', to=settings.DJANGO_LEDGER_CUSTOMER_MODEL)),
|
||||
('customer', models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.CASCADE, related_name='leads', to='django_ledger.customermodel')),
|
||||
('dealer', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, related_name='leads', to='inventory.dealer')),
|
||||
('id_car_make', models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.DO_NOTHING, to='inventory.carmake', verbose_name='Make')),
|
||||
('id_car_model', models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.DO_NOTHING, to='inventory.carmodel', verbose_name='Model')),
|
||||
@ -584,12 +578,12 @@ class Migration(migrations.Migration):
|
||||
name='SaleOrder',
|
||||
fields=[
|
||||
('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
|
||||
('payment_method', models.CharField(choices=[('cash', 'Cash'), ('finance', 'Finance'), ('lease', 'Lease'), ('credit_card', 'Credit Card'), ('bank_transfer', 'Bank Transfer'), ('SADAD', 'SADAD')], max_length=20)),
|
||||
('payment_method', models.CharField(choices=[('cash', 'Cash'), ('finance', 'Finance'), ('lease', 'Lease'), ('credit_card', 'Credit Card'), ('bank_transfer', 'Bank Transfer'), ('sadad', 'SADAD')], max_length=20)),
|
||||
('comments', models.TextField(blank=True, null=True)),
|
||||
('formatted_order_id', models.CharField(editable=False, max_length=10, unique=True)),
|
||||
('created', models.DateTimeField(auto_now_add=True)),
|
||||
('estimate', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, related_name='sale_orders', to=settings.DJANGO_LEDGER_ESTIMATE_MODEL, verbose_name='Estimate')),
|
||||
('invoice', models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.CASCADE, related_name='sale_orders', to=settings.DJANGO_LEDGER_INVOICE_MODEL, verbose_name='Invoice')),
|
||||
('estimate', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, related_name='sale_orders', to='django_ledger.estimatemodel', verbose_name='Estimate')),
|
||||
('invoice', models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.CASCADE, related_name='sale_orders', to='django_ledger.invoicemodel', verbose_name='Invoice')),
|
||||
],
|
||||
options={
|
||||
'ordering': ['-created'],
|
||||
@ -599,15 +593,15 @@ class Migration(migrations.Migration):
|
||||
name='Schedule',
|
||||
fields=[
|
||||
('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
|
||||
('purpose', models.CharField(choices=[('Product Demo', 'Product Demo'), ('Follow-Up Call', 'Follow-Up Call'), ('Contract Discussion', 'Contract Discussion'), ('Sales Meeting', 'Sales Meeting'), ('Support Call', 'Support Call'), ('Other', 'Other')], max_length=200)),
|
||||
('purpose', models.CharField(choices=[('product_demo', 'Product Demo'), ('follow_up_call', 'Follow-Up Call'), ('contract_discussion', 'Contract Discussion'), ('sales_meeting', 'Sales Meeting'), ('support_call', 'Support Call'), ('other', 'Other')], max_length=200)),
|
||||
('scheduled_at', models.DateTimeField()),
|
||||
('scheduled_type', models.CharField(choices=[('Call', 'Call'), ('Meeting', 'Meeting'), ('Email', 'Email')], default='Call', max_length=200)),
|
||||
('scheduled_type', models.CharField(choices=[('call', 'Call'), ('meeting', 'Meeting'), ('email', 'Email')], default='Call', max_length=200)),
|
||||
('duration', models.DurationField(default=datetime.timedelta(seconds=300))),
|
||||
('notes', models.TextField(blank=True, null=True)),
|
||||
('status', models.CharField(choices=[('Scheduled', 'Scheduled'), ('Completed', 'Completed'), ('Canceled', 'Canceled')], default='Scheduled', max_length=200)),
|
||||
('status', models.CharField(choices=[('scheduled', 'Scheduled'), ('completed', 'Completed'), ('canceled', 'Canceled')], default='Scheduled', max_length=200)),
|
||||
('created_at', models.DateTimeField(auto_now_add=True)),
|
||||
('updated_at', models.DateTimeField(auto_now=True)),
|
||||
('customer', models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.CASCADE, related_name='schedules', to=settings.DJANGO_LEDGER_CUSTOMER_MODEL)),
|
||||
('customer', models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.CASCADE, related_name='schedules', to='django_ledger.customermodel')),
|
||||
('lead', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, related_name='schedules', to='inventory.lead')),
|
||||
('scheduled_by', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to=settings.AUTH_USER_MODEL)),
|
||||
],
|
||||
@ -650,9 +644,9 @@ class Migration(migrations.Migration):
|
||||
('updated', models.DateTimeField(auto_now=True, verbose_name='Updated')),
|
||||
('closed', models.BooleanField(default=False, verbose_name='Closed')),
|
||||
('car', models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.SET_NULL, to='inventory.car', verbose_name='Car')),
|
||||
('customer', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, related_name='opportunities', to=settings.DJANGO_LEDGER_CUSTOMER_MODEL)),
|
||||
('customer', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, related_name='opportunities', to='django_ledger.customermodel')),
|
||||
('dealer', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, related_name='opportunities', to='inventory.dealer')),
|
||||
('estimate', models.OneToOneField(blank=True, null=True, on_delete=django.db.models.deletion.SET_NULL, related_name='opportunity', to=settings.DJANGO_LEDGER_ESTIMATE_MODEL)),
|
||||
('estimate', models.OneToOneField(blank=True, null=True, on_delete=django.db.models.deletion.SET_NULL, related_name='opportunity', to='django_ledger.estimatemodel')),
|
||||
('lead', models.OneToOneField(blank=True, null=True, on_delete=django.db.models.deletion.CASCADE, related_name='opportunity', to='inventory.lead')),
|
||||
('staff', models.ForeignKey(null=True, on_delete=django.db.models.deletion.SET_NULL, related_name='owner', to='inventory.staff', verbose_name='Owner')),
|
||||
],
|
||||
@ -710,6 +704,7 @@ class Migration(migrations.Migration):
|
||||
('logo', models.ImageField(blank=True, null=True, upload_to='logos/vendors', verbose_name='Logo')),
|
||||
('created_at', models.DateTimeField(auto_now_add=True, verbose_name='Created At')),
|
||||
('dealer', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, related_name='vendors', to='inventory.dealer')),
|
||||
('vendor_model', models.ForeignKey(on_delete=django.db.models.deletion.DO_NOTHING, to='django_ledger.vendormodel', verbose_name='Vendor Model')),
|
||||
],
|
||||
options={
|
||||
'verbose_name': 'Vendor',
|
||||
@ -783,6 +778,7 @@ class Migration(migrations.Migration):
|
||||
('user', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, related_name='payments', to=settings.AUTH_USER_MODEL)),
|
||||
],
|
||||
options={
|
||||
'verbose_name': 'Payment History',
|
||||
'verbose_name_plural': 'Payment Histories',
|
||||
'ordering': ['-payment_date'],
|
||||
'indexes': [models.Index(fields=['transaction_id'], name='inventory_p_transac_9469f3_idx'), models.Index(fields=['user'], name='inventory_p_user_id_c31626_idx'), models.Index(fields=['status'], name='inventory_p_status_abcb77_idx'), models.Index(fields=['payment_date'], name='inventory_p_payment_b3068c_idx')],
|
||||
|
||||
20
inventory/migrations/0002_alter_vendor_vendor_model.py
Normal file
20
inventory/migrations/0002_alter_vendor_vendor_model.py
Normal file
@ -0,0 +1,20 @@
|
||||
# Generated by Django 5.1.7 on 2025-05-06 12:57
|
||||
|
||||
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', '0001_initial'),
|
||||
]
|
||||
|
||||
operations = [
|
||||
migrations.AlterField(
|
||||
model_name='vendor',
|
||||
name='vendor_model',
|
||||
field=models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.DO_NOTHING, to='django_ledger.vendormodel', verbose_name='Vendor Model'),
|
||||
),
|
||||
]
|
||||
19
inventory/migrations/0003_alter_car_vendor.py
Normal file
19
inventory/migrations/0003_alter_car_vendor.py
Normal file
@ -0,0 +1,19 @@
|
||||
# Generated by Django 5.1.7 on 2025-05-06 14:31
|
||||
|
||||
import django.db.models.deletion
|
||||
from django.db import migrations, models
|
||||
|
||||
|
||||
class Migration(migrations.Migration):
|
||||
|
||||
dependencies = [
|
||||
('inventory', '0002_alter_vendor_vendor_model'),
|
||||
]
|
||||
|
||||
operations = [
|
||||
migrations.AlterField(
|
||||
model_name='car',
|
||||
name='vendor',
|
||||
field=models.ForeignKey(on_delete=django.db.models.deletion.DO_NOTHING, related_name='cars', to='inventory.vendor', verbose_name='Vendor'),
|
||||
),
|
||||
]
|
||||
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'),
|
||||
),
|
||||
]
|
||||
@ -0,0 +1,32 @@
|
||||
# Generated by Django 5.1.7 on 2025-05-07 09:33
|
||||
|
||||
import django.db.models.deletion
|
||||
from django.conf import settings
|
||||
from django.db import migrations, models
|
||||
|
||||
|
||||
class Migration(migrations.Migration):
|
||||
|
||||
dependencies = [
|
||||
('django_ledger', '0021_alter_bankaccountmodel_account_model_and_more'),
|
||||
('inventory', '0007_customer_customer_type'),
|
||||
migrations.swappable_dependency(settings.AUTH_USER_MODEL),
|
||||
]
|
||||
|
||||
operations = [
|
||||
migrations.RemoveField(
|
||||
model_name='customer',
|
||||
name='customer_type',
|
||||
),
|
||||
migrations.AddField(
|
||||
model_name='organization',
|
||||
name='customer_model',
|
||||
field=models.ForeignKey(null=True, on_delete=django.db.models.deletion.SET_NULL, to='django_ledger.customermodel'),
|
||||
),
|
||||
migrations.AddField(
|
||||
model_name='organization',
|
||||
name='user',
|
||||
field=models.OneToOneField(default=1, on_delete=django.db.models.deletion.CASCADE, related_name='organization_profile', to=settings.AUTH_USER_MODEL),
|
||||
preserve_default=False,
|
||||
),
|
||||
]
|
||||
19
inventory/migrations/0009_organization_email.py
Normal file
19
inventory/migrations/0009_organization_email.py
Normal file
@ -0,0 +1,19 @@
|
||||
# Generated by Django 5.1.7 on 2025-05-07 09:44
|
||||
|
||||
from django.db import migrations, models
|
||||
|
||||
|
||||
class Migration(migrations.Migration):
|
||||
|
||||
dependencies = [
|
||||
('inventory', '0008_remove_customer_customer_type_and_more'),
|
||||
]
|
||||
|
||||
operations = [
|
||||
migrations.AddField(
|
||||
model_name='organization',
|
||||
name='email',
|
||||
field=models.EmailField(default='t@tenhal.sa', max_length=254, verbose_name='Email'),
|
||||
preserve_default=False,
|
||||
),
|
||||
]
|
||||
18
inventory/migrations/0010_organization_active.py
Normal file
18
inventory/migrations/0010_organization_active.py
Normal file
@ -0,0 +1,18 @@
|
||||
# Generated by Django 5.1.7 on 2025-05-07 09:46
|
||||
|
||||
from django.db import migrations, models
|
||||
|
||||
|
||||
class Migration(migrations.Migration):
|
||||
|
||||
dependencies = [
|
||||
('inventory', '0009_organization_email'),
|
||||
]
|
||||
|
||||
operations = [
|
||||
migrations.AddField(
|
||||
model_name='organization',
|
||||
name='active',
|
||||
field=models.BooleanField(default=True, verbose_name='Active'),
|
||||
),
|
||||
]
|
||||
@ -19,7 +19,7 @@ from phonenumber_field.modelfields import PhoneNumberField
|
||||
from django.utils.timezone import now
|
||||
from django.contrib.auth.models import Group
|
||||
|
||||
from inventory.utils import get_user_type
|
||||
from inventory.utils import get_user_type, to_dict
|
||||
from .mixins import LocalizedNameMixin
|
||||
from django_ledger.models import EntityModel, ItemModel,EstimateModel,InvoiceModel,AccountModel,EntityManagementModel
|
||||
from django.contrib.contenttypes.fields import GenericForeignKey
|
||||
@ -376,7 +376,7 @@ class Car(models.Model):
|
||||
)
|
||||
|
||||
vendor = models.ForeignKey(
|
||||
VendorModel,
|
||||
"Vendor",
|
||||
models.DO_NOTHING,
|
||||
related_name="cars",
|
||||
verbose_name=_("Vendor"),
|
||||
@ -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,10 @@ 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")
|
||||
)
|
||||
created = models.DateTimeField(auto_now_add=True, verbose_name=_("Created"))
|
||||
updated = models.DateTimeField(auto_now=True, verbose_name=_("Updated"))
|
||||
|
||||
@ -1069,24 +1077,85 @@ 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})
|
||||
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})
|
||||
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
|
||||
def deactivate_account(self):
|
||||
self.active = False
|
||||
self.customer_model.active = False
|
||||
self.user.is_active = False
|
||||
self.customer_model.save()
|
||||
self.user.save()
|
||||
self.save()
|
||||
|
||||
class Organization(models.Model, LocalizedNameMixin):
|
||||
dealer = models.ForeignKey(
|
||||
Dealer, on_delete=models.CASCADE, related_name="organizations"
|
||||
)
|
||||
customer_model = models.ForeignKey(
|
||||
CustomerModel, on_delete=models.SET_NULL, null=True
|
||||
)
|
||||
user = models.OneToOneField(User, on_delete=models.CASCADE, related_name='organization_profile')
|
||||
name = models.CharField(max_length=255, verbose_name=_("Name"))
|
||||
arabic_name = models.CharField(max_length=255, verbose_name=_("Arabic Name"))
|
||||
crn = models.CharField(
|
||||
max_length=15, verbose_name=_("Commercial Registration Number")
|
||||
)
|
||||
vrn = models.CharField(max_length=15, verbose_name=_("VAT Registration Number"))
|
||||
email = models.EmailField(verbose_name=_("Email"))
|
||||
phone_number = PhoneNumberField(region="SA", verbose_name=_("Phone Number"))
|
||||
address = models.CharField(
|
||||
max_length=200, blank=True, null=True, verbose_name=_("Address")
|
||||
@ -1094,6 +1163,7 @@ class Organization(models.Model, LocalizedNameMixin):
|
||||
logo = models.ImageField(
|
||||
upload_to="logos", blank=True, null=True, verbose_name=_("Logo")
|
||||
)
|
||||
active = models.BooleanField(default=True, verbose_name=_("Active"))
|
||||
created = models.DateTimeField(auto_now_add=True, verbose_name=_("Created"))
|
||||
updated = models.DateTimeField(auto_now=True, verbose_name=_("Updated"))
|
||||
|
||||
@ -1103,7 +1173,61 @@ class Organization(models.Model, LocalizedNameMixin):
|
||||
|
||||
def __str__(self):
|
||||
return self.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.name,
|
||||
"address_1": self.address,
|
||||
"phone": self.phone_number,
|
||||
"email": self.email,
|
||||
},
|
||||
)
|
||||
try:
|
||||
customer.additional_info.update({"customer_info": customer_dict})
|
||||
except Exception:
|
||||
pass
|
||||
customer.save()
|
||||
return customer
|
||||
|
||||
def update_user_model(self):
|
||||
user = self.user
|
||||
user.first_name = self.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.name
|
||||
customer.address_1 = self.address
|
||||
customer.phone = self.phone_number
|
||||
customer.email = self.email
|
||||
try:
|
||||
customer.additional_info.update({"customer_info": customer_dict})
|
||||
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.name,
|
||||
)
|
||||
user.set_password("Tenhal@123")
|
||||
user.save()
|
||||
return user
|
||||
|
||||
def deactivate_account(self):
|
||||
self.active = False
|
||||
self.user.is_active = False
|
||||
self.customer_model.active = False
|
||||
self.user.save()
|
||||
self.customer_model.save()
|
||||
self.save()
|
||||
|
||||
class Representative(models.Model, LocalizedNameMixin):
|
||||
dealer = models.ForeignKey(
|
||||
@ -1471,6 +1595,9 @@ class Vendor(models.Model, LocalizedNameMixin):
|
||||
vrn = models.CharField(
|
||||
max_length=15, unique=True, verbose_name=_("VAT Registration Number")
|
||||
)
|
||||
vendor_model = models.ForeignKey(
|
||||
VendorModel, on_delete=models.DO_NOTHING, verbose_name=_("Vendor Model"),null=True,blank=True
|
||||
)
|
||||
arabic_name = models.CharField(max_length=255, verbose_name=_("Arabic Name"))
|
||||
name = models.CharField(max_length=255, verbose_name=_("English Name"))
|
||||
contact_person = models.CharField(max_length=100, verbose_name=_("Contact Person"))
|
||||
@ -1491,6 +1618,56 @@ class Vendor(models.Model, LocalizedNameMixin):
|
||||
def __str__(self):
|
||||
return self.name
|
||||
|
||||
def create_vendor_model(self):
|
||||
entity = self.dealer.entity
|
||||
additionals = to_dict(self)
|
||||
if not self.vendor_model:
|
||||
vendor = entity.create_vendor(
|
||||
vendor_model_kwargs={
|
||||
"vendor_name": self.name,
|
||||
"vendor_number": self.crn,
|
||||
"address_1": self.address,
|
||||
"phone": self.phone_number,
|
||||
"email": self.email,
|
||||
"tax_id_number": self.vrn,
|
||||
"active": True,
|
||||
"hidden": False,
|
||||
"additional_info": additionals,
|
||||
}
|
||||
)
|
||||
self.vendor_model = vendor
|
||||
self.save()
|
||||
|
||||
def update_vendor_model(self):
|
||||
additionals = to_dict(self)
|
||||
self.vendor_model.vendor_name = self.name
|
||||
self.vendor_model.vendor_number = self.crn
|
||||
self.vendor_model.address_1 = self.address
|
||||
self.vendor_model.phone = self.phone_number
|
||||
self.vendor_model.email = self.email
|
||||
self.vendor_model.tax_id_number = self.vrn
|
||||
self.vendor_model.additional_info = additionals
|
||||
self.vendor_model.save()
|
||||
|
||||
def create_vendor_account(self,role):
|
||||
entity = self.dealer.entity
|
||||
coa = entity.get_default_coa()
|
||||
last_account = entity.get_all_accounts().filter(role=role).order_by('-created').first()
|
||||
|
||||
if len(last_account.code) == 4:
|
||||
code = f"{int(last_account.code)}{1:03d}"
|
||||
elif len(last_account.code) > 4:
|
||||
code = f"{int(last_account.code)+1}"
|
||||
|
||||
if not entity.get_all_accounts().filter(name=self.name, role=role,coa_model=coa,balance_type="credit",active=True).exists():
|
||||
entity.create_account(
|
||||
name=self.name,
|
||||
code=code,
|
||||
role=role,
|
||||
coa_model=coa,
|
||||
balance_type="credit",
|
||||
active=True
|
||||
)
|
||||
|
||||
class Payment(models.Model):
|
||||
METHOD_CHOICES = [
|
||||
|
||||
@ -190,74 +190,10 @@ def create_ledger_vendor(sender, instance, created, **kwargs):
|
||||
:return: None
|
||||
"""
|
||||
if created:
|
||||
entity = EntityModel.objects.filter(admin=instance.dealer.user).first()
|
||||
additionals = to_dict(instance)
|
||||
vendor = entity.create_vendor(
|
||||
vendor_model_kwargs={
|
||||
"vendor_name": instance.name,
|
||||
"vendor_number": instance.crn,
|
||||
"address_1": instance.address,
|
||||
"phone": instance.phone_number,
|
||||
"email": instance.email,
|
||||
"tax_id_number": instance.vrn,
|
||||
"active": True,
|
||||
"hidden": False,
|
||||
"additional_info": additionals,
|
||||
}
|
||||
)
|
||||
|
||||
|
||||
coa = entity.get_default_coa()
|
||||
last_account = entity.get_all_accounts().filter(role=roles.LIABILITY_CL_ACC_PAYABLE).order_by('-created').first()
|
||||
|
||||
# code = f"{int(last_account.code)}{1:03d}"
|
||||
if len(last_account.code) == 4:
|
||||
code = f"{int(last_account.code)}{1:03d}"
|
||||
elif len(last_account.code) > 4:
|
||||
code = f"{int(last_account.code)+1}"
|
||||
|
||||
account = entity.create_account(
|
||||
name=instance.name,
|
||||
code=code,
|
||||
role=roles.LIABILITY_CL_ACC_PAYABLE,
|
||||
coa_model=coa,
|
||||
balance_type="credit",
|
||||
active=True
|
||||
)
|
||||
print(f"VendorModel created for Vendor: {instance.name}")
|
||||
|
||||
@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)
|
||||
instance.create_vendor_model()
|
||||
instance.create_vendor_account(roles.LIABILITY_CL_ACC_PAYABLE)
|
||||
else:
|
||||
instance.update_vendor_model()
|
||||
|
||||
# Create Item
|
||||
@receiver(post_save, sender=models.Car)
|
||||
@ -800,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",
|
||||
),
|
||||
@ -202,15 +202,15 @@ urlpatterns = [
|
||||
path('crm/calender/', views.EmployeeCalendarView.as_view(), name='calendar_list'),
|
||||
# Vendor URLs
|
||||
path("vendors", views.VendorListView.as_view(), name="vendor_list"),
|
||||
path("vendors/<uuid:pk>/", views.vendorDetailView, name="vendor_detail"),
|
||||
path("vendors/<int:pk>/", views.vendorDetailView, name="vendor_detail"),
|
||||
path("vendors/create/", views.VendorCreateView.as_view(), name="vendor_create"),
|
||||
path(
|
||||
"vendors/<uuid:pk>/update/",
|
||||
"vendors/<int:pk>/update/",
|
||||
views.VendorUpdateView.as_view(),
|
||||
name="vendor_update",
|
||||
),
|
||||
path(
|
||||
"vendors/<uuid:pk>/delete/",
|
||||
"vendors/<int:pk>/delete/",
|
||||
views.delete_vendor,
|
||||
name="vendor_delete",
|
||||
),
|
||||
@ -374,22 +374,22 @@ path(
|
||||
"organizations/", views.OrganizationListView.as_view(), name="organization_list"
|
||||
),
|
||||
path(
|
||||
"organizations/<uuid:pk>/",
|
||||
"organizations/<int:pk>/",
|
||||
views.OrganizationDetailView.as_view(),
|
||||
name="organization_detail",
|
||||
),
|
||||
path(
|
||||
"organizations/create/",
|
||||
views.OrganizationCreateView,
|
||||
views.OrganizationCreateView.as_view(),
|
||||
name="organization_create",
|
||||
),
|
||||
path(
|
||||
"organizations/<uuid:pk>/update/",
|
||||
views.OrganizationUpdateView,
|
||||
"organizations/<int:pk>/update/",
|
||||
views.OrganizationUpdateView.as_view(),
|
||||
name="organization_update",
|
||||
),
|
||||
path(
|
||||
"organizations/<uuid:pk>/delete/",
|
||||
"organizations/<int:pk>/delete/",
|
||||
views.OrganizationDeleteView,
|
||||
name="organization_delete",
|
||||
),
|
||||
|
||||
@ -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)")
|
||||
)
|
||||
@ -579,7 +579,7 @@ class CarCreateView(LoginRequiredMixin, PermissionRequiredMixin, CreateView):
|
||||
def get_form(self, form_class=None):
|
||||
form = super().get_form(form_class)
|
||||
dealer = get_user_type(self.request)
|
||||
form.fields["vendor"].queryset = dealer.entity.get_vendors().filter(active=True)
|
||||
form.fields["vendor"].queryset = dealer.vendors.all()
|
||||
return form
|
||||
|
||||
def get_success_url(self):
|
||||
@ -595,6 +595,12 @@ class CarCreateView(LoginRequiredMixin, PermissionRequiredMixin, CreateView):
|
||||
messages.success(self.request, _("Car saved successfully"))
|
||||
return super().form_valid(form)
|
||||
|
||||
def get_context_data(self, **kwargs):
|
||||
context = super().get_context_data(**kwargs)
|
||||
dealer = get_user_type(self.request)
|
||||
context["vendor_exists"] = dealer.vendors.exists()
|
||||
|
||||
return context
|
||||
|
||||
def car_history(request, pk):
|
||||
"""
|
||||
@ -1323,7 +1329,7 @@ class CarUpdateView(
|
||||
form = super().get_form(form_class)
|
||||
dealer = get_user_type(self.request)
|
||||
print(dealer.get_vendors())
|
||||
form.fields["vendor"].queryset = dealer.get_vendors()
|
||||
form.fields["vendor"].queryset = dealer.vendors.all()
|
||||
return form
|
||||
|
||||
class CarDeleteView(
|
||||
@ -1878,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
|
||||
@ -1889,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.filter(active=True)
|
||||
return apply_search_filters(customers, query)
|
||||
|
||||
def get_context_data(self, **kwargs):
|
||||
@ -1917,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"]
|
||||
@ -1929,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
|
||||
@ -1941,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
|
||||
@ -1958,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():
|
||||
@ -2012,131 +2016,71 @@ def add_activity_to_customer(request, pk):
|
||||
)
|
||||
|
||||
|
||||
@login_required
|
||||
@permission_required("django_ledger.add_customermodel", raise_exception=True)
|
||||
def CustomerCreateView(request):
|
||||
class CustomerCreateView(LoginRequiredMixin, PermissionRequiredMixin, CreateView):
|
||||
"""
|
||||
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.
|
||||
# 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.
|
||||
|
||||
: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.
|
||||
# :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.
|
||||
# """
|
||||
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"
|
||||
|
||||
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.user = user
|
||||
form.instance.customer_model = customer
|
||||
|
||||
return super().form_valid(form)
|
||||
|
||||
class CustomerUpdateView(LoginRequiredMixin, PermissionRequiredMixin, UpdateView):
|
||||
"""
|
||||
form = forms.CustomerForm()
|
||||
if request.method == "POST":
|
||||
form = forms.CustomerForm(request.POST)
|
||||
dealer = get_user_type(request)
|
||||
# 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.
|
||||
|
||||
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()
|
||||
# :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
|
||||
|
||||
messages.success(request, _("Customer created successfully"))
|
||||
return redirect("customer_list")
|
||||
# :param pk: The primary key of the CustomerModel object that is to be updated.
|
||||
# :type pk: int
|
||||
|
||||
except Exception as e:
|
||||
messages.error(request, _(f"An error occurred: {str(e)}"))
|
||||
else:
|
||||
messages.error(request, _("Please correct the errors below"))
|
||||
# :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
|
||||
# """
|
||||
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"
|
||||
|
||||
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.
|
||||
|
||||
: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
|
||||
|
||||
:param pk: The primary key of the CustomerModel object that is to be updated.
|
||||
:type pk: int
|
||||
|
||||
: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 = 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})
|
||||
def form_valid(self, form):
|
||||
form.instance.update_user_model()
|
||||
form.instance.update_customer_model()
|
||||
return super().form_valid(form)
|
||||
|
||||
|
||||
@login_required
|
||||
@ -2156,14 +2100,9 @@ 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.active = False
|
||||
user.is_active = False
|
||||
customer.save()
|
||||
user.save()
|
||||
|
||||
messages.success(request, _("Customer deleted successfully"))
|
||||
customer = get_object_or_404(models.Customer, pk=pk)
|
||||
customer.deactivate_account()
|
||||
messages.success(request, _("Customer deactivated successfully"))
|
||||
return redirect("customer_list")
|
||||
|
||||
|
||||
@ -2193,17 +2132,17 @@ class VendorListView(LoginRequiredMixin, ListView):
|
||||
ordered by their creation date in descending order.
|
||||
:type ordering: list
|
||||
"""
|
||||
model = VendorModel
|
||||
model = models.Vendor
|
||||
context_object_name = "vendors"
|
||||
paginate_by = 10
|
||||
template_name = "vendors/vendors_list.html"
|
||||
ordering = ["-created"]
|
||||
# ordering = ["-created"]
|
||||
|
||||
def get_queryset(self):
|
||||
query = self.request.GET.get("q")
|
||||
dealer = get_user_type(self.request)
|
||||
vendors = dealer.entity.get_vendors().filter(active=True)
|
||||
return apply_search_filters(vendors, query)
|
||||
# def get_queryset(self):
|
||||
# query = self.request.GET.get("q")
|
||||
# dealer = get_user_type(self.request)
|
||||
# vendors = dealer.entity.get_vendors().filter(active=True)
|
||||
# return apply_search_filters(vendors, query)
|
||||
|
||||
|
||||
@login_required
|
||||
@ -2295,24 +2234,24 @@ class VendorUpdateView(
|
||||
success_url = reverse_lazy("vendor_list")
|
||||
success_message = _("Vendor updated successfully")
|
||||
|
||||
def get_initial(self):
|
||||
initial = super().get_initial()
|
||||
initial = self.object.additional_info
|
||||
return initial
|
||||
# def get_initial(self):
|
||||
# initial = super().get_initial()
|
||||
# initial = self.object.additional_info
|
||||
# return initial
|
||||
|
||||
def form_valid(self, form):
|
||||
instance = form.save(commit=False)
|
||||
|
||||
instance.vendor_name = self.request.POST["name"]
|
||||
instance.vendor_number = self.request.POST["crn"]
|
||||
instance.address_1 = self.request.POST["address"]
|
||||
instance.phone = self.request.POST["phone_number"]
|
||||
instance.email = self.request.POST["email"]
|
||||
instance.tax_id_number = self.request.POST["vrn"]
|
||||
additionals = form.cleaned_data
|
||||
additionals["phone_number"] = str(additionals["phone_number"])
|
||||
instance.additional_info = additionals
|
||||
instance.save()
|
||||
# instance = form.save(commit=False)
|
||||
print(self.request.POST)
|
||||
# instance.vendor_name = self.request.POST["name"]
|
||||
# instance.vendor_number = self.request.POST["crn"]
|
||||
# instance.address_1 = self.request.POST["address"]
|
||||
# instance.phone = self.request.POST["phone_number"]
|
||||
# instance.email = self.request.POST["email"]
|
||||
# instance.tax_id_number = self.request.POST["vrn"]
|
||||
# additionals = form.cleaned_data
|
||||
# additionals["phone_number"] = str(additionals["phone_number"])
|
||||
# instance.additional_info = additionals
|
||||
# instance.save()
|
||||
return super().form_valid(form)
|
||||
|
||||
|
||||
@ -2333,8 +2272,9 @@ def delete_vendor(request, pk):
|
||||
:rtype: HttpResponseRedirect
|
||||
"""
|
||||
vendor = get_object_or_404(models.Vendor, pk=pk)
|
||||
# vendor.active = False
|
||||
vendor.delete()
|
||||
vendor.active = False
|
||||
vendor.vendor_model.active = False
|
||||
vendor.save()
|
||||
messages.success(request, _("Vendor deleted successfully"))
|
||||
return redirect("vendor_list")
|
||||
|
||||
@ -2794,7 +2734,7 @@ class OrganizationListView(LoginRequiredMixin, ListView):
|
||||
:ivar paginate_by: The number of organizations displayed per page.
|
||||
:type paginate_by: int
|
||||
"""
|
||||
model = CustomerModel
|
||||
model = models.Organization
|
||||
template_name = "organizations/organization_list.html"
|
||||
context_object_name = "organizations"
|
||||
paginate_by = 10
|
||||
@ -2802,9 +2742,7 @@ class OrganizationListView(LoginRequiredMixin, ListView):
|
||||
def get_queryset(self):
|
||||
query = self.request.GET.get("q")
|
||||
dealer = get_user_type(self.request)
|
||||
organization = dealer.entity.get_customers().filter(
|
||||
additional_info__type="organization", active=True
|
||||
)
|
||||
organization = dealer.organizations.filter(active=True)
|
||||
|
||||
return apply_search_filters(organization, query)
|
||||
|
||||
@ -2827,131 +2765,72 @@ class OrganizationDetailView(LoginRequiredMixin, DetailView):
|
||||
template for accessing the organization's data.
|
||||
:type context_object_name: str
|
||||
"""
|
||||
model = CustomerModel
|
||||
model = models.Organization
|
||||
template_name = "organizations/organization_detail.html"
|
||||
context_object_name = "organization"
|
||||
|
||||
|
||||
@login_required
|
||||
def OrganizationCreateView(request):
|
||||
class OrganizationCreateView(LoginRequiredMixin, PermissionRequiredMixin, CreateView):
|
||||
"""
|
||||
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.
|
||||
# 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.
|
||||
|
||||
: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
|
||||
# :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
|
||||
# """
|
||||
model = models.Organization
|
||||
form_class = forms.OrganizationForm
|
||||
permission_required = ["django_ledger.add_customermodel"]
|
||||
template_name = "organizations/organization_form.html"
|
||||
success_url = reverse_lazy("organization_list")
|
||||
success_message = "Organization created successfully"
|
||||
|
||||
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.user = user
|
||||
form.instance.customer_model = customer
|
||||
|
||||
return super().form_valid(form)
|
||||
|
||||
class OrganizationUpdateView(LoginRequiredMixin, PermissionRequiredMixin, UpdateView):
|
||||
"""
|
||||
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")
|
||||
# Handles the update of an organization instance. This view fetches the organization
|
||||
# based on the provided primary key (pk) and renders a form for editing the
|
||||
# organization attributes. When a POST request is made, this view validates and
|
||||
# processes the form data, updates the organization instance, and saves the changes.
|
||||
# If the request method is not POST, it initializes the form with existing organization
|
||||
# data for rendering.
|
||||
|
||||
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)
|
||||
# :param request: The HTTP request object. Must be authenticated via login.
|
||||
# :type request: HttpRequest
|
||||
# :param pk: The primary key of the organization to be updated.
|
||||
# :type pk: int
|
||||
# :return: An HTTP response object. Either renders the organization form or redirects
|
||||
# to the organization list upon successful update.
|
||||
# :rtype: HttpResponse
|
||||
# """
|
||||
model = models.Organization
|
||||
form_class = forms.OrganizationForm
|
||||
permission_required = ["django_ledger.change_customermodel"]
|
||||
template_name = "organizations/organization_form.html"
|
||||
success_url = reverse_lazy("organization_list")
|
||||
success_message = "Organization updated successfully"
|
||||
|
||||
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})
|
||||
def form_valid(self, form):
|
||||
form.instance.update_user_model()
|
||||
form.instance.update_customer_model()
|
||||
return super().form_valid(form)
|
||||
|
||||
|
||||
@login_required
|
||||
def OrganizationUpdateView(request, pk):
|
||||
"""
|
||||
Handles the update of an organization instance. This view fetches the organization
|
||||
based on the provided primary key (pk) and renders a form for editing the
|
||||
organization attributes. When a POST request is made, this view validates and
|
||||
processes the form data, updates the organization instance, and saves the changes.
|
||||
If the request method is not POST, it initializes the form with existing organization
|
||||
data for rendering.
|
||||
|
||||
:param request: The HTTP request object. Must be authenticated via login.
|
||||
:type request: HttpRequest
|
||||
:param pk: The primary key of the organization to be updated.
|
||||
:type pk: int
|
||||
:return: An HTTP response object. Either renders the organization form or redirects
|
||||
to the organization list upon successful update.
|
||||
:rtype: HttpResponse
|
||||
"""
|
||||
organization = get_object_or_404(CustomerModel, pk=pk)
|
||||
if request.method == "POST":
|
||||
form = forms.OrganizationForm(request.POST)
|
||||
|
||||
organization_dict = {
|
||||
x: request.POST[x] for x in request.POST if x != "csrfmiddlewaretoken"
|
||||
}
|
||||
dealer = get_user_type(request)
|
||||
|
||||
instance = dealer.entity.get_customers().get(
|
||||
pk=organization.additional_info["customer_info"]["pk"]
|
||||
)
|
||||
name = organization_dict["first_name"] + " " + organization_dict["last_name"]
|
||||
instance.customer_name = name
|
||||
instance.address_1 = organization_dict["address"]
|
||||
instance.phone = organization_dict["phone_number"]
|
||||
instance.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
|
||||
else:
|
||||
organization_dict["logo"] = organization.additional_info["customer_info"][
|
||||
"logo"
|
||||
]
|
||||
|
||||
organization_dict["pk"] = str(instance.pk)
|
||||
instance.additional_info["customer_info"] = organization_dict
|
||||
instance.additional_info["type"] = "organization"
|
||||
instance.save()
|
||||
messages.success(request, _("Organization created successfully"))
|
||||
return redirect("organization_list")
|
||||
else:
|
||||
form = forms.OrganizationForm(
|
||||
initial=organization.additional_info["customer_info"] or {}
|
||||
)
|
||||
# form.fields.pop("logo", None)
|
||||
return render(request, "organizations/organization_form.html", {"form": form})
|
||||
|
||||
|
||||
# class OrganizationDeleteView(LoginRequiredMixin, SuccessMessageMixin, DeleteView):
|
||||
# model = models.Organization
|
||||
# template_name = "organizations/organization_confirm_delete.html"
|
||||
# success_url = reverse_lazy("organization_list")
|
||||
# success_message = "Organization deleted successfully."
|
||||
@login_required
|
||||
def OrganizationDeleteView(request, pk):
|
||||
"""
|
||||
@ -2967,14 +2846,9 @@ def OrganizationDeleteView(request, pk):
|
||||
:return: An HTTP response redirecting to the organization list view.
|
||||
:rtype: HttpResponseRedirect
|
||||
"""
|
||||
organization = get_object_or_404(CustomerModel, pk=pk)
|
||||
try:
|
||||
User.objects.get(email=organization.email).delete()
|
||||
organization.delete()
|
||||
messages.success(request, _("Organization deleted successfully"))
|
||||
except Exception as e:
|
||||
print("unable to delete user", e)
|
||||
messages.error(request, _("Unable to delete organization"))
|
||||
organization = get_object_or_404(models.Organization, pk=pk)
|
||||
organization.deactivate_account()
|
||||
messages.success(request, _("Organization Deactivated successfully"))
|
||||
return redirect("organization_list")
|
||||
|
||||
|
||||
@ -3633,8 +3507,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", [])
|
||||
@ -3673,7 +3548,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 = {}
|
||||
@ -3767,12 +3642,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)
|
||||
@ -3982,13 +3856,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)
|
||||
|
||||
@ -6215,11 +6085,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 |
BIN
static/images/logos/vendors/image.png
vendored
Normal file
BIN
static/images/logos/vendors/image.png
vendored
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 1.1 MiB |
BIN
static/images/logos/vendors/logo-for-the-word-daju-48a980.jpg
vendored
Normal file
BIN
static/images/logos/vendors/logo-for-the-word-daju-48a980.jpg
vendored
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 42 KiB |
@ -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>
|
||||
|
||||
@ -20,28 +20,28 @@
|
||||
{% if not car.ready %}
|
||||
<div class="alert alert-outline-warning d-flex align-items-center" role="alert">
|
||||
<i class="fa-solid fa-circle-info fs-6"></i>
|
||||
<p class="mb-0 flex-1">{{ _("This car information is not complete , please add colors and finances before making it ready for sale .") }}</p>
|
||||
<p class="mb-0 flex-1">{{ _("This car information is not complete , please add colors and finances before making it ready for sale .") }}</p>
|
||||
<button class="btn-close" type="button" data-bs-dismiss="alert" aria-label="Close"></button>
|
||||
</div>
|
||||
{% endif %}
|
||||
{% if car.get_transfer.status == "draft" %}
|
||||
<div class="alert alert-outline-info d-flex align-items-center" role="alert">
|
||||
<div class="alert alert-outline-info d-flex align-items-center" role="alert">
|
||||
<i class="fa-solid fa-circle-info fs-6"></i>
|
||||
<p class="mb-0 flex-1">{{ _("Action Required , Please Approved The Tranfer Request Of This Car .") }}</p>
|
||||
<p class="mb-0 flex-1">{{ _("Action Required , Please Approved The Tranfer Request Of This Car .") }}</p>
|
||||
<button class="btn-close" type="button" data-bs-dismiss="alert" aria-label="Close"></button>
|
||||
</div>
|
||||
{% endif %}
|
||||
{% if car.get_transfer and car.get_transfer.status == "approved" %}
|
||||
<div class="alert alert-outline-info d-flex align-items-center" role="alert">
|
||||
<div class="alert alert-outline-info d-flex align-items-center" role="alert">
|
||||
<i class="fa-solid fa-circle-info fs-6"></i>
|
||||
<p class="mb-0 flex-1">{{ _("Car Is In Transfer Process To Another Dealer, Please Wait For The Acceptance .") }}</p>
|
||||
<p class="mb-0 flex-1">{{ _("Car Is In Transfer Process To Another Dealer, Please Wait For The Acceptance .") }}</p>
|
||||
<button class="btn-close" type="button" data-bs-dismiss="alert" aria-label="Close"></button>
|
||||
</div>
|
||||
{% endif %}
|
||||
{% if car.is_reserved %}
|
||||
<div class="alert alert-outline-warning d-flex align-items-center" role="alert">
|
||||
<div class="alert alert-outline-warning d-flex align-items-center" role="alert">
|
||||
<i class="fa-solid fa-circle-info fs-6"></i>
|
||||
<p class="mb-0 flex-1">{{ _("This car is reserved until ") }}{{ car.get_reservation.reserved_until }}</p>
|
||||
<p class="mb-0 flex-1">{{ _("This car is reserved until ") }}{{ car.get_reservation.reserved_until }}</p>
|
||||
<button class="btn-close" type="button" data-bs-dismiss="alert" aria-label="Close"></button>
|
||||
</div>
|
||||
{% endif %}
|
||||
@ -98,7 +98,7 @@
|
||||
{% if car.vendor %}
|
||||
<tr>
|
||||
<th>{% trans "Vendor"|capfirst %}</th>
|
||||
<td>{{ car.vendor.vendor_name }}</td>
|
||||
<td>{{ car.vendor.name }}</td>
|
||||
</tr>
|
||||
{% endif %}
|
||||
<tr>
|
||||
@ -361,7 +361,7 @@
|
||||
<p class="card-header rounded-top fw-bold">{% trans 'Transfer Details' %}</p>
|
||||
<div class="card-body">
|
||||
<div class="table-responsive scrollbar mb-3">
|
||||
<table class="table table-sm fs-9 mb-0 overflow-hidden">
|
||||
<table class="table table-sm fs-9 mb-0 overflow-hidden">
|
||||
<thead>
|
||||
<tr>
|
||||
<th>{% trans "Action" %}</th>
|
||||
@ -373,7 +373,7 @@
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
|
||||
|
||||
<tr>
|
||||
<td><span class="badge badge-phoenix badge-phoenix-info">Transfer</span></td>
|
||||
<td>
|
||||
@ -396,11 +396,11 @@
|
||||
<a class="btn btn-sm btn-phoenix-success" href="{% url 'transfer_detail' car.get_transfer.pk %}">Approve</a>
|
||||
{% endif %}
|
||||
</td>
|
||||
<td>
|
||||
<td>
|
||||
<a class="btn btn-sm btn-phoenix-success" href="{% url 'transfer_detail' car.get_transfer.pk %}?action=cancel">Cancel</a>
|
||||
</td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@ -6,14 +6,24 @@
|
||||
height: auto;
|
||||
margin: 0 auto;
|
||||
}
|
||||
.disabled{
|
||||
opacity: 0.5;
|
||||
pointer-events: none;
|
||||
}
|
||||
</style>
|
||||
|
||||
<!-- JavaScript Section -->
|
||||
|
||||
<script src="{% static 'vendors/zxing/index.min.js' %}"></script>
|
||||
<script src="{% static 'vendors/tesseract/tesseract.min.js' %}"></script>
|
||||
|
||||
<div class=" container-fluid m-0">
|
||||
{% if not vendor_exists %}
|
||||
<div class="alert alert-outline-warning d-flex align-items-center" role="alert">
|
||||
<i class="fa-solid fa-circle-info fs-6"></i>
|
||||
<p class="mb-0 flex-1">{{ _("Please Add A Vendor, Before Adding A Car .") }}</p>
|
||||
<button class="btn-close" type="button" data-bs-dismiss="alert" aria-label="Close"></button>
|
||||
</div>
|
||||
{% endif %}
|
||||
<div class=" container-fluid m-0 {% if not vendor_exists %}disabled{% endif %}">
|
||||
<form method="post" id="carForm" class="form needs-validation" novalidate>
|
||||
{% csrf_token %}
|
||||
{% include 'partials/form_errors.html' %}
|
||||
|
||||
@ -5,10 +5,10 @@
|
||||
<div class="row my-4">
|
||||
<h2>{{ organization.get_local_name }}</h2>
|
||||
<ul class="list-group mb-4">
|
||||
<li class="list-group-item"><strong>{% trans "CRN" %}:</strong> {{ organization.additional_info.customer_info.crn }}</li>
|
||||
<li class="list-group-item"><strong>{% trans "VRN" %}:</strong> {{ organization.additional_info.customer_info.vrn }}</li>
|
||||
<li class="list-group-item"><strong>{% trans "Phone" %}:</strong> {{ organization.additional_info.customer_info.phone_number }}</li>
|
||||
<li class="list-group-item"><strong>{% trans "Address" %}:</strong> {{ organization.additional_info.customer_info.address }}</li>
|
||||
<li class="list-group-item"><strong>{% trans "CRN" %}:</strong> {{ organization.crn }}</li>
|
||||
<li class="list-group-item"><strong>{% trans "VRN" %}:</strong> {{ organization.vrn }}</li>
|
||||
<li class="list-group-item"><strong>{% trans "Phone" %}:</strong> {{ organization.phone_number }}</li>
|
||||
<li class="list-group-item"><strong>{% trans "Address" %}:</strong> {{ organization.address }}</li>
|
||||
</ul>
|
||||
<div class="d-flex">
|
||||
<a href="{% url 'organization_update' organization.pk %}" class="btn btn-sm btn-warning me-2">{% trans "Edit" %}</a>
|
||||
|
||||
@ -102,26 +102,26 @@
|
||||
<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 'organization_detail' org.pk %}">{{ org.customer_name }}</a>
|
||||
<a class="fs-8 fw-bold" href="{% url 'organization_detail' org.pk %}">{{ org.name }}</a>
|
||||
<div class="d-flex align-items-center">
|
||||
<p class="mb-0 text-body-highlight fw-semibold fs-9 me-2"></p><span class="badge badge-phoenix badge-phoenix-primary">{{ org.customer_name }}</span>
|
||||
<p class="mb-0 text-body-highlight fw-semibold fs-9 me-2"></p><span class="badge badge-phoenix badge-phoenix-primary">{{ org.name }}</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</td>
|
||||
<td class="email align-middle white-space-nowrap fw-semibold ps-4 border-end border-translucent">{{ org.additional_info.customer_info.crn }}</td>
|
||||
<td class="phone align-middle white-space-nowrap fw-semibold ps-4 border-end border-translucent">{{ org.additional_info.customer_info.vrn }}</td>
|
||||
<td class="email align-middle white-space-nowrap fw-semibold ps-4 border-end border-translucent">{{ org.crn }}</td>
|
||||
<td class="phone align-middle white-space-nowrap fw-semibold ps-4 border-end border-translucent">{{ org.vrn }}</td>
|
||||
<td class="phone align-middle white-space-nowrap ps-4 border-end border-translucent fw-semibold text-body-highlight">
|
||||
<a class="text-body-highlight" href="tel:{{ org.phone }}">{{ org.phone }}</a>
|
||||
<a class="text-body-highlight" href="tel:{{ org.phone }}">{{ org.phone_number }}</a>
|
||||
</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">{{ org.address_1 }}</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">{{ org.address }}</td>
|
||||
<td class="date align-middle white-space-nowrap text-body-tertiary text-opacity-85 ps-4 text-body-tertiary">{{ org.created|date }}</td>
|
||||
<td class="align-middle white-space-nowrap text-end pe-0 ps-4">
|
||||
{% if perms.django_ledger.change_customermodel %}
|
||||
<div class="btn-reveal-trigger position-static">
|
||||
<button class="btn btn-sm dropdown-toggle dropdown-caret-none transition-none btn-reveal fs-10" type="button" data-bs-toggle="dropdown" data-boundary="window" aria-haspopup="true" aria-expanded="false" data-bs-reference="parent"><span class="fas fa-ellipsis-h fs-10"></span></button>
|
||||
<div class="dropdown-menu dropdown-menu-end py-2">
|
||||
<a href="{% url 'organization_update' org.pk %}" class="dropdown-item text-success-dark">{% trans 'Edit' %}</a>
|
||||
<div class="dropdown-menu dropdown-menu-end py-2">
|
||||
<a href="{% url 'organization_update' org.pk %}" class="dropdown-item text-success-dark">{% trans 'Edit' %}</a>
|
||||
{% if perms.django_ledger.delete_customermodel %}
|
||||
<div class="dropdown-divider"></div><button class="dropdown-item text-danger" data-bs-toggle="modal" data-bs-target="#deleteModal">{% trans 'Delete' %}</button>
|
||||
{% endif %}
|
||||
|
||||
@ -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>
|
||||
|
||||
2
templates/vendors/vendor_form.html
vendored
2
templates/vendors/vendor_form.html
vendored
@ -27,7 +27,7 @@
|
||||
<div class="row">
|
||||
<div class="col-xl-9">
|
||||
|
||||
<form class="row g-3 mb-9" method="post" class="form" novalidate>
|
||||
<form class="row g-3 mb-9" method="post" class="form" enctype="multipart/form-data" novalidate >
|
||||
{% csrf_token %}
|
||||
{{ redirect_field }}
|
||||
{{ form|crispy }}
|
||||
|
||||
8
templates/vendors/vendors_list.html
vendored
8
templates/vendors/vendors_list.html
vendored
@ -114,7 +114,7 @@
|
||||
<div class="avatar avatar-xl me-3"><img class="rounded-circle" src="{% static 'images/icons/picture.svg' %}" alt="" />
|
||||
{% endif %}
|
||||
</div>
|
||||
<div><a class="fs-8 fw-bold" href="">{{ vendor.vendor_name }}</a>
|
||||
<div><a class="fs-8 fw-bold" href="">{{ vendor.pk }}</a>
|
||||
<div class="d-flex align-items-center">
|
||||
<p class="mb-0 text-body-highlight fw-semibold fs-9 me-2">{{ vendor.vendor_name }}</p><span class="badge badge-phoenix badge-phoenix-primary">{{ vendor.id}}</span>
|
||||
</div>
|
||||
@ -122,11 +122,11 @@
|
||||
</div>
|
||||
</td>
|
||||
<td class="email align-middle white-space-nowrap fw-semibold ps-4 border-end border-translucent"><a class="text-body-highlight" href="">{{ vendor.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:{{ vendor.phone }}">{{ vendor.phone }}</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:{{ vendor.phone }}">{{ vendor.phone_number }}</a></td>
|
||||
<td class="contact align-middle white-space-nowrap ps-4 border-end border-translucent fw-semibold text-body-highlight">{{ vendor.contact_person }}</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">
|
||||
{{ vendor.address_1 }}</td>
|
||||
<td class="date align-middle white-space-nowrap text-body-tertiary text-opacity-85 ps-4 text-body-tertiary">{{ vendor.created|date }}</td>
|
||||
{{ vendor.address }}</td>
|
||||
<td class="date align-middle white-space-nowrap text-body-tertiary text-opacity-85 ps-4 text-body-tertiary">{{ vendor.created_at|date }}</td>
|
||||
<td class="align-middle white-space-nowrap text-end pe-0 ps-4">
|
||||
<div class="btn-reveal-trigger position-static">
|
||||
<button class="btn btn-sm dropdown-toggle dropdown-caret-none transition-none btn-reveal fs-10" type="button" data-bs-toggle="dropdown" data-boundary="window" aria-haspopup="true" aria-expanded="false" data-bs-reference="parent"><span class="fas fa-ellipsis-h fs-10"></span></button>
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user