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
|
from inventory import models as inventory_models
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
class LoginView(APIView):
|
class LoginView(APIView):
|
||||||
permission_classes = [permissions.AllowAny,]
|
permission_classes = [permissions.AllowAny,]
|
||||||
|
|
||||||
|
|||||||
@ -10,6 +10,8 @@ from django.core.validators import MinLengthValidator
|
|||||||
from django import forms
|
from django import forms
|
||||||
from plans.models import PlanPricing
|
from plans.models import PlanPricing
|
||||||
from django.contrib.auth import get_user_model
|
from django.contrib.auth import get_user_model
|
||||||
|
|
||||||
|
from inventory.validators import SaudiPhoneNumberValidator
|
||||||
from .models import CustomGroup, Status, Stage
|
from .models import CustomGroup, Status, Stage
|
||||||
from .mixins import AddClassMixin
|
from .mixins import AddClassMixin
|
||||||
from django_ledger.forms.invoice import (
|
from django_ledger.forms.invoice import (
|
||||||
@ -46,6 +48,8 @@ from .models import (
|
|||||||
CarModel,
|
CarModel,
|
||||||
SaleOrder,
|
SaleOrder,
|
||||||
CarMake,
|
CarMake,
|
||||||
|
Customer,
|
||||||
|
Organization,
|
||||||
DealerSettings
|
DealerSettings
|
||||||
)
|
)
|
||||||
from django_ledger import models as ledger_models
|
from django_ledger import models as ledger_models
|
||||||
@ -59,6 +63,13 @@ import django_tables2 as tables
|
|||||||
User = get_user_model()
|
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):
|
class AdditionalServiceForm(forms.ModelForm):
|
||||||
"""
|
"""
|
||||||
A form used for creating and updating instances of the
|
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"}),
|
widget=forms.CheckboxSelectMultiple(attrs={"class": "form-check-input"}),
|
||||||
queryset=Service.objects.all(),
|
queryset=Service.objects.all(),
|
||||||
required=False,)
|
required=False,)
|
||||||
phone_number = forms.CharField(
|
phone_number = SaudiPhoneNumberField(
|
||||||
required=False,
|
required=False,
|
||||||
max_length=10,
|
|
||||||
min_length=10,
|
|
||||||
widget=forms.TextInput(attrs={
|
widget=forms.TextInput(attrs={
|
||||||
'class': 'form-control',
|
'class': 'form-control',
|
||||||
'placeholder': _('Phone Number'),
|
'placeholder': _('Phone Number'),
|
||||||
'id': 'phone'
|
'id': 'phone'
|
||||||
}),
|
}),
|
||||||
label=_('Phone Number'),
|
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:
|
class Meta:
|
||||||
model = Staff
|
model = Staff
|
||||||
@ -148,7 +153,9 @@ class DealerForm(forms.ModelForm):
|
|||||||
:type address: str
|
:type address: str
|
||||||
:ivar logo: Logo of the dealer.
|
:ivar logo: Logo of the dealer.
|
||||||
:type logo: File
|
:type logo: File
|
||||||
|
|
||||||
"""
|
"""
|
||||||
|
phone_number = SaudiPhoneNumberField(label=_('Phone Number'))
|
||||||
class Meta:
|
class Meta:
|
||||||
model = Dealer
|
model = Dealer
|
||||||
fields = [
|
fields = [
|
||||||
@ -162,64 +169,81 @@ class DealerForm(forms.ModelForm):
|
|||||||
]
|
]
|
||||||
|
|
||||||
|
|
||||||
class CustomerForm(forms.Form):
|
class CustomerForm(forms.ModelForm):
|
||||||
"""
|
phone_number = SaudiPhoneNumberField(label=_('Phone Number'))
|
||||||
Represents a form for collecting customer information.
|
|
||||||
|
|
||||||
This form is used to gather and validate customer details such as name,
|
class Meta:
|
||||||
email, phone number, national ID, tax registration details, and address.
|
model = Customer
|
||||||
It includes several fields with validation constraints and specific
|
fields = [
|
||||||
requirements. It is designed to handle both required and optional fields,
|
'title',
|
||||||
ensuring the correctness of user inputs.
|
"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.
|
# This form is used to gather and validate customer details such as name,
|
||||||
:type first_name: forms.CharField
|
# email, phone number, national ID, tax registration details, and address.
|
||||||
:ivar last_name: Customer's last name.
|
# It includes several fields with validation constraints and specific
|
||||||
:type last_name: forms.CharField
|
# requirements. It is designed to handle both required and optional fields,
|
||||||
:ivar arabic_name: Customer's name in Arabic.
|
# ensuring the correctness of user inputs.
|
||||||
:type arabic_name: forms.CharField
|
|
||||||
:ivar email: Customer's email address.
|
# :ivar first_name: Customer's first name.
|
||||||
:type email: forms.EmailField
|
# :type first_name: forms.CharField
|
||||||
:ivar phone_number: Customer's phone number. Validates the format and
|
# :ivar last_name: Customer's last name.
|
||||||
ensures the number is in the Saudi Arabia region.
|
# :type last_name: forms.CharField
|
||||||
:type phone_number: PhoneNumberField
|
# :ivar arabic_name: Customer's name in Arabic.
|
||||||
:ivar national_id: Customer's national ID. Optional field limited to
|
# :type arabic_name: forms.CharField
|
||||||
a maximum length of 10 characters.
|
# :ivar email: Customer's email address.
|
||||||
:type national_id: forms.CharField
|
# :type email: forms.EmailField
|
||||||
:ivar crn: Commercial registration number (CRN) of the customer. Optional field.
|
# :ivar phone_number: Customer's phone number. Validates the format and
|
||||||
:type crn: forms.CharField
|
# ensures the number is in the Saudi Arabia region.
|
||||||
:ivar vrn: Value-added tax registration number (VRN) of the customer.
|
# :type phone_number: PhoneNumberField
|
||||||
Optional field.
|
# :ivar national_id: Customer's national ID. Optional field limited to
|
||||||
:type vrn: forms.CharField
|
# a maximum length of 10 characters.
|
||||||
:ivar address: Customer's address.
|
# :type national_id: forms.CharField
|
||||||
:type address: forms.CharField
|
# :ivar crn: Commercial registration number (CRN) of the customer. Optional field.
|
||||||
"""
|
# :type crn: forms.CharField
|
||||||
first_name = forms.CharField()
|
# :ivar vrn: Value-added tax registration number (VRN) of the customer.
|
||||||
last_name = forms.CharField()
|
# Optional field.
|
||||||
arabic_name = forms.CharField()
|
# :type vrn: forms.CharField
|
||||||
email = forms.EmailField()
|
# :ivar address: Customer's address.
|
||||||
# phone_number = PhoneNumberField(
|
# :type address: forms.CharField
|
||||||
# label=_("Phone Number"),
|
# """
|
||||||
# widget=forms.TextInput(
|
# first_name = forms.CharField()
|
||||||
# attrs={
|
# last_name = forms.CharField()
|
||||||
# "placeholder": _("Phone"),
|
# arabic_name = forms.CharField()
|
||||||
# }
|
# email = forms.EmailField()
|
||||||
# ),
|
# # phone_number = PhoneNumberField(
|
||||||
# region="SA",
|
# # label=_("Phone Number"),
|
||||||
# error_messages={
|
# # widget=forms.TextInput(
|
||||||
# "required": _("This field is required."),
|
# # attrs={
|
||||||
# "invalid": _("Phone number must be in the format 05xxxxxxxx"),
|
# # "placeholder": _("Phone"),
|
||||||
# },
|
# # }
|
||||||
# required=True,
|
# # ),
|
||||||
# )
|
# # region="SA",
|
||||||
phone_number = forms.CharField(label=_("Phone Number"),min_length=10,max_length=10,validators=[RegexValidator(regex='^05[0-9]{8}$')], required=True)
|
# # error_messages={
|
||||||
national_id = forms.CharField(max_length=10,required=False)
|
# # "required": _("This field is required."),
|
||||||
crn = forms.CharField(required=False)
|
# # "invalid": _("Phone number must be in the format 05xxxxxxxx"),
|
||||||
vrn = forms.CharField(required=False)
|
# # },
|
||||||
address = forms.CharField()
|
# # 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.
|
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.
|
:ivar logo: Optional field to upload the logo of the organization.
|
||||||
:type logo: forms.ImageField
|
:type logo: forms.ImageField
|
||||||
"""
|
"""
|
||||||
contact_person = forms.CharField(required=False)
|
phone_number = SaudiPhoneNumberField(label=_('Phone Number'),required=True)
|
||||||
logo = forms.ImageField(required=False)
|
class Meta:
|
||||||
|
model = Organization
|
||||||
|
fields = ["name","arabic_name","email","phone_number","crn","vrn","address","logo"]
|
||||||
|
|
||||||
|
|
||||||
class CarForm(
|
class CarForm(
|
||||||
@ -292,10 +318,8 @@ class CarForm(
|
|||||||
self.fields["id_car_model"].choices = [
|
self.fields["id_car_model"].choices = [
|
||||||
(obj.id_car_model, obj.get_local_name()) for obj in queryset
|
(obj.id_car_model, obj.get_local_name()) for obj in queryset
|
||||||
]
|
]
|
||||||
if "vendor" in self.fields:
|
# if "vendor" in self.fields:
|
||||||
self.fields["vendor"].queryset = ledger_models.VendorModel.objects.filter(
|
# self.fields["vendor"].queryset = dealer.vendors.all()
|
||||||
active=True
|
|
||||||
)
|
|
||||||
|
|
||||||
|
|
||||||
class CarUpdateForm(forms.ModelForm, AddClassMixin):
|
class CarUpdateForm(forms.ModelForm, AddClassMixin):
|
||||||
@ -482,7 +506,7 @@ class VendorForm(forms.ModelForm):
|
|||||||
:ivar Meta: Inner class to define metadata for the Vendor form.
|
:ivar Meta: Inner class to define metadata for the Vendor form.
|
||||||
:type Meta: Type[VendorForm.Meta]
|
: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"))
|
contact_person = forms.CharField(label=_("Contact Person"))
|
||||||
|
|
||||||
class Meta:
|
class Meta:
|
||||||
@ -569,6 +593,8 @@ class RepresentativeForm(forms.ModelForm):
|
|||||||
:ivar Meta.fields: The fields from the model to include in the form.
|
:ivar Meta.fields: The fields from the model to include in the form.
|
||||||
:type Meta.fields: list of str
|
:type Meta.fields: list of str
|
||||||
"""
|
"""
|
||||||
|
phone_number = SaudiPhoneNumberField(label=_('Phone Number'))
|
||||||
|
|
||||||
class Meta:
|
class Meta:
|
||||||
model = Representative
|
model = Representative
|
||||||
fields = [
|
fields = [
|
||||||
@ -787,21 +813,7 @@ class WizardForm2(forms.Form):
|
|||||||
# },
|
# },
|
||||||
# required=True,
|
# required=True,
|
||||||
# )
|
# )
|
||||||
phone_number = forms.CharField(
|
phone_number = SaudiPhoneNumberField(label=_('Phone Number'))
|
||||||
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)')
|
|
||||||
)]
|
|
||||||
)
|
|
||||||
|
|
||||||
|
|
||||||
class WizardForm3(forms.Form):
|
class WizardForm3(forms.Form):
|
||||||
@ -998,6 +1010,8 @@ class LeadForm(forms.ModelForm):
|
|||||||
options are displayed until a car make is selected.
|
options are displayed until a car make is selected.
|
||||||
:type id_car_model: ModelChoiceField
|
:type id_car_model: ModelChoiceField
|
||||||
"""
|
"""
|
||||||
|
phone_number = SaudiPhoneNumberField(label=_('Phone Number'))
|
||||||
|
|
||||||
id_car_make = forms.ModelChoiceField(
|
id_car_make = forms.ModelChoiceField(
|
||||||
label=_("Make"),
|
label=_("Make"),
|
||||||
queryset=CarMake.objects.filter(is_sa_import=True),
|
queryset=CarMake.objects.filter(is_sa_import=True),
|
||||||
@ -1205,71 +1219,75 @@ class SaleOrderForm(forms.ModelForm):
|
|||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
class EstimateModelCreateForm(EstimateModelCreateFormBase):
|
class EstimateModelCreateForm(forms.Form):
|
||||||
"""
|
title = forms.CharField(max_length=255)
|
||||||
Defines the EstimateModelCreateForm class, which is used to create and manage
|
customer = forms.ModelChoiceField(queryset=Customer.objects.none())
|
||||||
forms for EstimateModel. This form handles the rendering and validation
|
|
||||||
of specific fields, their input widgets, and labels.
|
|
||||||
|
|
||||||
The purpose of this class is to provide a structured way to handle
|
# class EstimateModelCreateForm(EstimateModelCreateFormBase):
|
||||||
EstimateModel instances and their related fields, ensuring a user-friendly
|
# """
|
||||||
form interface with proper field configuration. It facilitates fetching
|
# Defines the EstimateModelCreateForm class, which is used to create and manage
|
||||||
related data, such as customer queries, based on user-specific parameters.
|
# 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
|
# The purpose of this class is to provide a structured way to handle
|
||||||
form operates.
|
# EstimateModel instances and their related fields, ensuring a user-friendly
|
||||||
:type ENTITY_SLUG: str
|
# form interface with proper field configuration. It facilitates fetching
|
||||||
:ivar USER_MODEL: The user model that provides methods and objects needed
|
# related data, such as customer queries, based on user-specific parameters.
|
||||||
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 __init__(self, *args, entity_slug, user_model, **kwargs):
|
# :ivar ENTITY_SLUG: A string that represents the entity context in which the
|
||||||
super(EstimateModelCreateForm, self).__init__(
|
# form operates.
|
||||||
*args, entity_slug=entity_slug, user_model=user_model, **kwargs
|
# :type ENTITY_SLUG: str
|
||||||
)
|
# :ivar USER_MODEL: The user model that provides methods and objects needed
|
||||||
self.ENTITY_SLUG = entity_slug
|
# to filter and query customers.
|
||||||
self.USER_MODEL = user_model
|
# :type USER_MODEL: Any
|
||||||
self.fields["customer"].queryset = self.get_customer_queryset()
|
# :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):
|
# def __init__(self, *args, entity_slug, user_model, **kwargs):
|
||||||
return self.USER_MODEL.dealer.entity.get_customers()
|
# 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):
|
class OpportunityStatusForm(forms.Form):
|
||||||
@ -1581,21 +1599,7 @@ class PaymentPlanForm(forms.Form):
|
|||||||
label=_('Email Address')
|
label=_('Email Address')
|
||||||
)
|
)
|
||||||
|
|
||||||
phone = forms.CharField(
|
phone_number = SaudiPhoneNumberField(label=_('Phone Number'))
|
||||||
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)')
|
|
||||||
)]
|
|
||||||
)
|
|
||||||
|
|
||||||
# Credit Card Fields (not saved to database)
|
# Credit Card Fields (not saved to database)
|
||||||
card_number = CreditCardField(
|
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 datetime
|
||||||
import django.core.validators
|
import django.core.validators
|
||||||
@ -17,17 +17,11 @@ class Migration(migrations.Migration):
|
|||||||
initial = True
|
initial = True
|
||||||
|
|
||||||
dependencies = [
|
dependencies = [
|
||||||
# ('appointment', '0002_alter_workinghours_options'),
|
('appointment', '0001_initial'),
|
||||||
('auth', '0012_alter_user_first_name_max_length'),
|
('auth', '0012_alter_user_first_name_max_length'),
|
||||||
('contenttypes', '0002_remove_content_type_name'),
|
('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.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 = [
|
operations = [
|
||||||
@ -92,7 +86,7 @@ class Migration(migrations.Migration):
|
|||||||
fields=[
|
fields=[
|
||||||
('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
|
('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')),
|
('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')),
|
('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')),
|
('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')),
|
('price', models.DecimalField(decimal_places=2, max_digits=14, verbose_name='Price')),
|
||||||
('taxable', models.BooleanField(default=False, verbose_name='taxable')),
|
('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')),
|
('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={
|
options={
|
||||||
'verbose_name': 'Additional Services',
|
'verbose_name': 'Additional Services',
|
||||||
@ -140,7 +134,7 @@ class Migration(migrations.Migration):
|
|||||||
('mileage', models.IntegerField(blank=True, null=True, verbose_name='Mileage')),
|
('mileage', models.IntegerField(blank=True, null=True, verbose_name='Mileage')),
|
||||||
('receiving_date', models.DateTimeField(verbose_name='Receiving Date')),
|
('receiving_date', models.DateTimeField(verbose_name='Receiving Date')),
|
||||||
('hash', models.CharField(blank=True, max_length=64, null=True, verbose_name='Hash')),
|
('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')),
|
('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={
|
options={
|
||||||
@ -322,7 +316,7 @@ class Migration(migrations.Migration):
|
|||||||
('logo', models.ImageField(blank=True, null=True, upload_to='logos/users', verbose_name='Logo')),
|
('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')),
|
('joined_at', models.DateTimeField(auto_now_add=True, verbose_name='Joined At')),
|
||||||
('updated_at', models.DateTimeField(auto_now=True, verbose_name='Updated At')),
|
('updated_at', models.DateTimeField(auto_now=True, verbose_name='Updated At')),
|
||||||
('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)),
|
('user', models.OneToOneField(on_delete=django.db.models.deletion.CASCADE, related_name='dealer', to=settings.AUTH_USER_MODEL)),
|
||||||
],
|
],
|
||||||
options={
|
options={
|
||||||
@ -339,7 +333,7 @@ class Migration(migrations.Migration):
|
|||||||
fields=[
|
fields=[
|
||||||
('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
|
('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
|
||||||
('name', models.CharField(max_length=100)),
|
('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')),
|
('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=[
|
fields=[
|
||||||
('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
|
('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
|
||||||
('additional_info', models.JSONField(blank=True, default=dict, null=True)),
|
('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_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=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='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=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='django_ledger.accountmodel')),
|
||||||
('dealer', models.OneToOneField(blank=True, null=True, on_delete=django.db.models.deletion.CASCADE, related_name='settings', to='inventory.dealer')),
|
('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_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=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='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=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='django_ledger.accountmodel')),
|
||||||
],
|
],
|
||||||
),
|
),
|
||||||
migrations.CreateModel(
|
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')),
|
('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')),
|
('created', models.DateTimeField(auto_now_add=True, db_index=True, verbose_name='Created')),
|
||||||
('updated', models.DateTimeField(auto_now=True, verbose_name='Updated')),
|
('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')),
|
('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_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')),
|
('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',
|
name='SaleOrder',
|
||||||
fields=[
|
fields=[
|
||||||
('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
|
('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)),
|
('comments', models.TextField(blank=True, null=True)),
|
||||||
('formatted_order_id', models.CharField(editable=False, max_length=10, unique=True)),
|
('formatted_order_id', models.CharField(editable=False, max_length=10, unique=True)),
|
||||||
('created', models.DateTimeField(auto_now_add=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')),
|
('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=settings.DJANGO_LEDGER_INVOICE_MODEL, verbose_name='Invoice')),
|
('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={
|
options={
|
||||||
'ordering': ['-created'],
|
'ordering': ['-created'],
|
||||||
@ -599,15 +593,15 @@ class Migration(migrations.Migration):
|
|||||||
name='Schedule',
|
name='Schedule',
|
||||||
fields=[
|
fields=[
|
||||||
('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
|
('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_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))),
|
('duration', models.DurationField(default=datetime.timedelta(seconds=300))),
|
||||||
('notes', models.TextField(blank=True, null=True)),
|
('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)),
|
('created_at', models.DateTimeField(auto_now_add=True)),
|
||||||
('updated_at', models.DateTimeField(auto_now=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')),
|
('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)),
|
('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')),
|
('updated', models.DateTimeField(auto_now=True, verbose_name='Updated')),
|
||||||
('closed', models.BooleanField(default=False, verbose_name='Closed')),
|
('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')),
|
('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')),
|
('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')),
|
('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')),
|
('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')),
|
('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')),
|
('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')),
|
('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={
|
options={
|
||||||
'verbose_name': 'Vendor',
|
'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)),
|
('user', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, related_name='payments', to=settings.AUTH_USER_MODEL)),
|
||||||
],
|
],
|
||||||
options={
|
options={
|
||||||
|
'verbose_name': 'Payment History',
|
||||||
'verbose_name_plural': 'Payment Histories',
|
'verbose_name_plural': 'Payment Histories',
|
||||||
'ordering': ['-payment_date'],
|
'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')],
|
'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.utils.timezone import now
|
||||||
from django.contrib.auth.models import Group
|
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 .mixins import LocalizedNameMixin
|
||||||
from django_ledger.models import EntityModel, ItemModel,EstimateModel,InvoiceModel,AccountModel,EntityManagementModel
|
from django_ledger.models import EntityModel, ItemModel,EstimateModel,InvoiceModel,AccountModel,EntityManagementModel
|
||||||
from django.contrib.contenttypes.fields import GenericForeignKey
|
from django.contrib.contenttypes.fields import GenericForeignKey
|
||||||
@ -376,7 +376,7 @@ class Car(models.Model):
|
|||||||
)
|
)
|
||||||
|
|
||||||
vendor = models.ForeignKey(
|
vendor = models.ForeignKey(
|
||||||
VendorModel,
|
"Vendor",
|
||||||
models.DO_NOTHING,
|
models.DO_NOTHING,
|
||||||
related_name="cars",
|
related_name="cars",
|
||||||
verbose_name=_("Vendor"),
|
verbose_name=_("Vendor"),
|
||||||
@ -1032,18 +1032,22 @@ class Priority(models.TextChoices):
|
|||||||
HIGH = "high", _("High")
|
HIGH = "high", _("High")
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
class Customer(models.Model):
|
class Customer(models.Model):
|
||||||
dealer = models.ForeignKey(
|
dealer = models.ForeignKey(
|
||||||
Dealer, on_delete=models.CASCADE, related_name="customers"
|
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')
|
user = models.OneToOneField(User, on_delete=models.CASCADE, related_name='customer_profile')
|
||||||
title = models.CharField(
|
title = models.CharField(
|
||||||
choices=Title.choices, default=Title.NA, max_length=10, verbose_name=_("Title")
|
choices=Title.choices, default=Title.NA, max_length=10, verbose_name=_("Title")
|
||||||
)
|
)
|
||||||
first_name = models.CharField(max_length=50, verbose_name=_("First Name"))
|
first_name = models.CharField(max_length=50, verbose_name=_("First Name"))
|
||||||
middle_name = models.CharField(
|
# middle_name = models.CharField(
|
||||||
max_length=50, blank=True, null=True, verbose_name=_("Middle Name")
|
# max_length=50, blank=True, null=True, verbose_name=_("Middle Name")
|
||||||
)
|
# )
|
||||||
last_name = models.CharField(max_length=50, verbose_name=_("Last Name"))
|
last_name = models.CharField(max_length=50, verbose_name=_("Last Name"))
|
||||||
gender = models.CharField(
|
gender = models.CharField(
|
||||||
choices=[("m", _("Male")), ("f", _("Female"))],
|
choices=[("m", _("Male")), ("f", _("Female"))],
|
||||||
@ -1061,6 +1065,10 @@ class Customer(models.Model):
|
|||||||
address = models.CharField(
|
address = models.CharField(
|
||||||
max_length=200, blank=True, null=True, verbose_name=_("Address")
|
max_length=200, blank=True, null=True, verbose_name=_("Address")
|
||||||
)
|
)
|
||||||
|
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"))
|
created = models.DateTimeField(auto_now_add=True, verbose_name=_("Created"))
|
||||||
updated = models.DateTimeField(auto_now=True, verbose_name=_("Updated"))
|
updated = models.DateTimeField(auto_now=True, verbose_name=_("Updated"))
|
||||||
|
|
||||||
@ -1069,24 +1077,85 @@ class Customer(models.Model):
|
|||||||
verbose_name_plural = _("Customers")
|
verbose_name_plural = _("Customers")
|
||||||
|
|
||||||
def __str__(self):
|
def __str__(self):
|
||||||
middle = f" {self.middle_name}" if self.middle_name else ""
|
# middle = f" {self.middle_name}" if self.middle_name else ""
|
||||||
return f"{self.first_name}{middle} {self.last_name}"
|
return f"{self.first_name} {self.last_name}"
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def get_full_name(self):
|
def full_name(self):
|
||||||
return f"{self.first_name} {self.middle_name} {self.last_name}"
|
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):
|
class Organization(models.Model, LocalizedNameMixin):
|
||||||
dealer = models.ForeignKey(
|
dealer = models.ForeignKey(
|
||||||
Dealer, on_delete=models.CASCADE, related_name="organizations"
|
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"))
|
name = models.CharField(max_length=255, verbose_name=_("Name"))
|
||||||
arabic_name = models.CharField(max_length=255, verbose_name=_("Arabic Name"))
|
arabic_name = models.CharField(max_length=255, verbose_name=_("Arabic Name"))
|
||||||
crn = models.CharField(
|
crn = models.CharField(
|
||||||
max_length=15, verbose_name=_("Commercial Registration Number")
|
max_length=15, verbose_name=_("Commercial Registration Number")
|
||||||
)
|
)
|
||||||
vrn = models.CharField(max_length=15, verbose_name=_("VAT Registration Number"))
|
vrn = models.CharField(max_length=15, verbose_name=_("VAT Registration Number"))
|
||||||
|
email = models.EmailField(verbose_name=_("Email"))
|
||||||
phone_number = PhoneNumberField(region="SA", verbose_name=_("Phone Number"))
|
phone_number = PhoneNumberField(region="SA", verbose_name=_("Phone Number"))
|
||||||
address = models.CharField(
|
address = models.CharField(
|
||||||
max_length=200, blank=True, null=True, verbose_name=_("Address")
|
max_length=200, blank=True, null=True, verbose_name=_("Address")
|
||||||
@ -1094,6 +1163,7 @@ class Organization(models.Model, LocalizedNameMixin):
|
|||||||
logo = models.ImageField(
|
logo = models.ImageField(
|
||||||
upload_to="logos", blank=True, null=True, verbose_name=_("Logo")
|
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"))
|
created = models.DateTimeField(auto_now_add=True, verbose_name=_("Created"))
|
||||||
updated = models.DateTimeField(auto_now=True, verbose_name=_("Updated"))
|
updated = models.DateTimeField(auto_now=True, verbose_name=_("Updated"))
|
||||||
|
|
||||||
@ -1103,7 +1173,61 @@ class Organization(models.Model, LocalizedNameMixin):
|
|||||||
|
|
||||||
def __str__(self):
|
def __str__(self):
|
||||||
return self.name
|
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):
|
class Representative(models.Model, LocalizedNameMixin):
|
||||||
dealer = models.ForeignKey(
|
dealer = models.ForeignKey(
|
||||||
@ -1471,6 +1595,9 @@ class Vendor(models.Model, LocalizedNameMixin):
|
|||||||
vrn = models.CharField(
|
vrn = models.CharField(
|
||||||
max_length=15, unique=True, verbose_name=_("VAT Registration Number")
|
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"))
|
arabic_name = models.CharField(max_length=255, verbose_name=_("Arabic Name"))
|
||||||
name = models.CharField(max_length=255, verbose_name=_("English Name"))
|
name = models.CharField(max_length=255, verbose_name=_("English Name"))
|
||||||
contact_person = models.CharField(max_length=100, verbose_name=_("Contact Person"))
|
contact_person = models.CharField(max_length=100, verbose_name=_("Contact Person"))
|
||||||
@ -1491,6 +1618,56 @@ class Vendor(models.Model, LocalizedNameMixin):
|
|||||||
def __str__(self):
|
def __str__(self):
|
||||||
return self.name
|
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):
|
class Payment(models.Model):
|
||||||
METHOD_CHOICES = [
|
METHOD_CHOICES = [
|
||||||
|
|||||||
@ -190,74 +190,10 @@ def create_ledger_vendor(sender, instance, created, **kwargs):
|
|||||||
:return: None
|
:return: None
|
||||||
"""
|
"""
|
||||||
if created:
|
if created:
|
||||||
entity = EntityModel.objects.filter(admin=instance.dealer.user).first()
|
instance.create_vendor_model()
|
||||||
additionals = to_dict(instance)
|
instance.create_vendor_account(roles.LIABILITY_CL_ACC_PAYABLE)
|
||||||
vendor = entity.create_vendor(
|
else:
|
||||||
vendor_model_kwargs={
|
instance.update_vendor_model()
|
||||||
"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)
|
|
||||||
|
|
||||||
# Create Item
|
# Create Item
|
||||||
@receiver(post_save, sender=models.Car)
|
@receiver(post_save, sender=models.Car)
|
||||||
@ -800,7 +736,13 @@ def update_finance_cost(sender, instance, created, **kwargs):
|
|||||||
if created:
|
if created:
|
||||||
entity = instance.car.dealer.entity
|
entity = instance.car.dealer.entity
|
||||||
vendor = instance.car.vendor
|
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)
|
ledger,_ = LedgerModel.objects.get_or_create(name=name, entity=entity)
|
||||||
save_journal(instance,ledger,vendor)
|
save_journal(instance,ledger,vendor)
|
||||||
|
|
||||||
|
|||||||
@ -76,26 +76,26 @@ urlpatterns = [
|
|||||||
# CRM URLs
|
# CRM URLs
|
||||||
path("customers/", views.CustomerListView.as_view(), name="customer_list"),
|
path("customers/", views.CustomerListView.as_view(), name="customer_list"),
|
||||||
path(
|
path(
|
||||||
"customers/<uuid:pk>/",
|
"customers/<int:pk>/",
|
||||||
views.CustomerDetailView.as_view(),
|
views.CustomerDetailView.as_view(),
|
||||||
name="customer_detail",
|
name="customer_detail",
|
||||||
),
|
),
|
||||||
path(
|
path(
|
||||||
"customers/create/", views.CustomerCreateView, name="customer_create"
|
"customers/create/", views.CustomerCreateView.as_view(), name="customer_create"
|
||||||
),
|
),
|
||||||
path(
|
path(
|
||||||
"customers/<uuid:pk>/update/",
|
"customers/<int:pk>/update/",
|
||||||
views.CustomerUpdateView,
|
views.CustomerUpdateView.as_view(),
|
||||||
name="customer_update",
|
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(
|
path(
|
||||||
"customers/<str:customer_id>/opportunities/create/",
|
"customers/<str:customer_id>/opportunities/create/",
|
||||||
views.OpportunityCreateView.as_view(),
|
views.OpportunityCreateView.as_view(),
|
||||||
name="create_opportunity",
|
name="create_opportunity",
|
||||||
),
|
),
|
||||||
path(
|
path(
|
||||||
"customers/<uuid:pk>/add-note/",
|
"customers/<int:pk>/add-note/",
|
||||||
views.add_note_to_customer,
|
views.add_note_to_customer,
|
||||||
name="add_note_to_customer",
|
name="add_note_to_customer",
|
||||||
),
|
),
|
||||||
@ -202,15 +202,15 @@ urlpatterns = [
|
|||||||
path('crm/calender/', views.EmployeeCalendarView.as_view(), name='calendar_list'),
|
path('crm/calender/', views.EmployeeCalendarView.as_view(), name='calendar_list'),
|
||||||
# Vendor URLs
|
# Vendor URLs
|
||||||
path("vendors", views.VendorListView.as_view(), name="vendor_list"),
|
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/create/", views.VendorCreateView.as_view(), name="vendor_create"),
|
||||||
path(
|
path(
|
||||||
"vendors/<uuid:pk>/update/",
|
"vendors/<int:pk>/update/",
|
||||||
views.VendorUpdateView.as_view(),
|
views.VendorUpdateView.as_view(),
|
||||||
name="vendor_update",
|
name="vendor_update",
|
||||||
),
|
),
|
||||||
path(
|
path(
|
||||||
"vendors/<uuid:pk>/delete/",
|
"vendors/<int:pk>/delete/",
|
||||||
views.delete_vendor,
|
views.delete_vendor,
|
||||||
name="vendor_delete",
|
name="vendor_delete",
|
||||||
),
|
),
|
||||||
@ -374,22 +374,22 @@ path(
|
|||||||
"organizations/", views.OrganizationListView.as_view(), name="organization_list"
|
"organizations/", views.OrganizationListView.as_view(), name="organization_list"
|
||||||
),
|
),
|
||||||
path(
|
path(
|
||||||
"organizations/<uuid:pk>/",
|
"organizations/<int:pk>/",
|
||||||
views.OrganizationDetailView.as_view(),
|
views.OrganizationDetailView.as_view(),
|
||||||
name="organization_detail",
|
name="organization_detail",
|
||||||
),
|
),
|
||||||
path(
|
path(
|
||||||
"organizations/create/",
|
"organizations/create/",
|
||||||
views.OrganizationCreateView,
|
views.OrganizationCreateView.as_view(),
|
||||||
name="organization_create",
|
name="organization_create",
|
||||||
),
|
),
|
||||||
path(
|
path(
|
||||||
"organizations/<uuid:pk>/update/",
|
"organizations/<int:pk>/update/",
|
||||||
views.OrganizationUpdateView,
|
views.OrganizationUpdateView.as_view(),
|
||||||
name="organization_update",
|
name="organization_update",
|
||||||
),
|
),
|
||||||
path(
|
path(
|
||||||
"organizations/<uuid:pk>/delete/",
|
"organizations/<int:pk>/delete/",
|
||||||
views.OrganizationDeleteView,
|
views.OrganizationDeleteView,
|
||||||
name="organization_delete",
|
name="organization_delete",
|
||||||
),
|
),
|
||||||
|
|||||||
@ -388,7 +388,7 @@ def get_financial_values(model):
|
|||||||
if i.item_model.additional_info["additional_services"]:
|
if i.item_model.additional_info["additional_services"]:
|
||||||
additional_services.extend(
|
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"]
|
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
|
total_vat_amount = total_price_discounted * self.vat_rate
|
||||||
|
|
||||||
return {
|
return {
|
||||||
|
"total_price_before_discount": round(total_price, 2), # total_price_before_discount,
|
||||||
"total_price": round(total_price_discounted, 2), # total_price_discounted,
|
"total_price": round(total_price_discounted, 2), # total_price_discounted,
|
||||||
"total_vat_amount": round(total_vat_amount, 2), # total_vat_amount,
|
"total_vat_amount": round(total_vat_amount, 2), # total_vat_amount,
|
||||||
"total_discount": round(total_discount,2),
|
"total_discount": round(total_discount,2),
|
||||||
@ -1055,6 +1056,7 @@ class CarFinanceCalculator:
|
|||||||
"cars": [self._get_car_data(item) for item in self.item_transactions],
|
"cars": [self._get_car_data(item) for item in self.item_transactions],
|
||||||
"quantity": sum(self._get_quantity(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": totals['total_price'],
|
||||||
|
"total_price_before_discount": totals['total_price_before_discount'],
|
||||||
"total_vat": totals['total_vat_amount'] + totals['total_price'],
|
"total_vat": totals['total_vat_amount'] + totals['total_price'],
|
||||||
"total_vat_amount": totals['total_vat_amount'],
|
"total_vat_amount": totals['total_vat_amount'],
|
||||||
"total_discount": totals['total_discount'],
|
"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):
|
def get_form(self, form_class=None):
|
||||||
form = super().get_form(form_class)
|
form = super().get_form(form_class)
|
||||||
dealer = get_user_type(self.request)
|
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
|
return form
|
||||||
|
|
||||||
def get_success_url(self):
|
def get_success_url(self):
|
||||||
@ -595,6 +595,12 @@ class CarCreateView(LoginRequiredMixin, PermissionRequiredMixin, CreateView):
|
|||||||
messages.success(self.request, _("Car saved successfully"))
|
messages.success(self.request, _("Car saved successfully"))
|
||||||
return super().form_valid(form)
|
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):
|
def car_history(request, pk):
|
||||||
"""
|
"""
|
||||||
@ -1323,7 +1329,7 @@ class CarUpdateView(
|
|||||||
form = super().get_form(form_class)
|
form = super().get_form(form_class)
|
||||||
dealer = get_user_type(self.request)
|
dealer = get_user_type(self.request)
|
||||||
print(dealer.get_vendors())
|
print(dealer.get_vendors())
|
||||||
form.fields["vendor"].queryset = dealer.get_vendors()
|
form.fields["vendor"].queryset = dealer.vendors.all()
|
||||||
return form
|
return form
|
||||||
|
|
||||||
class CarDeleteView(
|
class CarDeleteView(
|
||||||
@ -1878,7 +1884,7 @@ class CustomerListView(LoginRequiredMixin, PermissionRequiredMixin, ListView):
|
|||||||
:ivar permission_required: A list of permissions required to access the view.
|
:ivar permission_required: A list of permissions required to access the view.
|
||||||
:type permission_required: list
|
:type permission_required: list
|
||||||
"""
|
"""
|
||||||
model = CustomerModel
|
model = models.Customer
|
||||||
home_label = _("customers")
|
home_label = _("customers")
|
||||||
context_object_name = "customers"
|
context_object_name = "customers"
|
||||||
paginate_by = 10
|
paginate_by = 10
|
||||||
@ -1889,9 +1895,7 @@ class CustomerListView(LoginRequiredMixin, PermissionRequiredMixin, ListView):
|
|||||||
def get_queryset(self):
|
def get_queryset(self):
|
||||||
query = self.request.GET.get("q")
|
query = self.request.GET.get("q")
|
||||||
dealer = get_user_type(self.request)
|
dealer = get_user_type(self.request)
|
||||||
customers = dealer.entity.get_customers().filter(
|
customers = dealer.customers.filter(active=True)
|
||||||
additional_info__type="customer"
|
|
||||||
)
|
|
||||||
return apply_search_filters(customers, query)
|
return apply_search_filters(customers, query)
|
||||||
|
|
||||||
def get_context_data(self, **kwargs):
|
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.
|
:ivar permission_required: The list of permissions required to access this view.
|
||||||
:type permission_required: list[str]
|
:type permission_required: list[str]
|
||||||
"""
|
"""
|
||||||
model = CustomerModel
|
model = models.Customer
|
||||||
template_name = "customers/view_customer.html"
|
template_name = "customers/view_customer.html"
|
||||||
context_object_name = "customer"
|
context_object_name = "customer"
|
||||||
permission_required = ["django_ledger.view_customermodel"]
|
permission_required = ["django_ledger.view_customermodel"]
|
||||||
@ -1929,9 +1933,9 @@ class CustomerDetailView(LoginRequiredMixin, PermissionRequiredMixin, DetailView
|
|||||||
context["customer_notes"] = models.Notes.objects.filter(
|
context["customer_notes"] = models.Notes.objects.filter(
|
||||||
object_id=self.object.pk
|
object_id=self.object.pk
|
||||||
)
|
)
|
||||||
estimates = entity.get_estimates().filter(customer=self.object)
|
estimates = entity.get_estimates().filter(customer=self.object.customer_model)
|
||||||
invoices = entity.get_invoices().filter(customer=self.object)
|
invoices = entity.get_invoices().filter(customer=self.object.customer_model)
|
||||||
# txs = entity. transactions(customer=self.object)
|
|
||||||
total = estimates.count() + invoices.count()
|
total = estimates.count() + invoices.count()
|
||||||
|
|
||||||
context["estimates"] = estimates
|
context["estimates"] = estimates
|
||||||
@ -1941,7 +1945,7 @@ class CustomerDetailView(LoginRequiredMixin, PermissionRequiredMixin, DetailView
|
|||||||
|
|
||||||
|
|
||||||
@login_required
|
@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
|
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
|
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
|
POST request, it renders the note form template with context including
|
||||||
the form and customer.
|
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":
|
if request.method == "POST":
|
||||||
form = forms.NoteForm(request.POST)
|
form = forms.NoteForm(request.POST)
|
||||||
if form.is_valid():
|
if form.is_valid():
|
||||||
@ -2012,131 +2016,71 @@ def add_activity_to_customer(request, pk):
|
|||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
@login_required
|
class CustomerCreateView(LoginRequiredMixin, PermissionRequiredMixin, CreateView):
|
||||||
@permission_required("django_ledger.add_customermodel", raise_exception=True)
|
|
||||||
def CustomerCreateView(request):
|
|
||||||
"""
|
"""
|
||||||
Handles the creation of a new customer within the system. This view ensures that proper permissions
|
# 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
|
# 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
|
# 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.
|
# 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.
|
# :param request: The HTTP request object containing metadata about the request initiated by the user.
|
||||||
:type request: HttpRequest
|
# :type request: HttpRequest
|
||||||
:return: The rendered form page or a redirect to the customer list page upon successful creation.
|
# :return: The rendered form page or a redirect to the customer list page upon successful creation.
|
||||||
:rtype: HttpResponse
|
# :rtype: HttpResponse
|
||||||
:raises PermissionDenied: If the user does not have the required permissions to access the view.
|
# :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()
|
# Updates the details of an existing customer in the database. This view is
|
||||||
if request.method == "POST":
|
# accessible only to logged-in users with the appropriate permissions. It
|
||||||
form = forms.CustomerForm(request.POST)
|
# handles both GET (form rendering with pre-filled customer data) and POST
|
||||||
dealer = get_user_type(request)
|
# (submitting updates) requests. Data validation and customer updates are
|
||||||
|
# conducted based on the received form data.
|
||||||
|
|
||||||
if form.is_valid():
|
# :param request: The HTTP request object used to determine the request method,
|
||||||
if (
|
# access user session details, and provide request data such as POST content.
|
||||||
dealer.entity.get_customers()
|
# Expected to contain the updated customer data if request method is POST.
|
||||||
.filter(email=form.cleaned_data["email"])
|
# :type request: HttpRequest
|
||||||
.exists()
|
|
||||||
):
|
|
||||||
messages.error(request, _("Customer with this email already exists"))
|
|
||||||
else:
|
|
||||||
customer_name = (
|
|
||||||
f"{form.cleaned_data['first_name']} "
|
|
||||||
f"{form.cleaned_data['last_name']}"
|
|
||||||
)
|
|
||||||
customer_dict = {
|
|
||||||
x: request.POST[x]
|
|
||||||
for x in request.POST
|
|
||||||
if x != "csrfmiddlewaretoken"
|
|
||||||
}
|
|
||||||
try:
|
|
||||||
customer = dealer.entity.create_customer(
|
|
||||||
commit=False,
|
|
||||||
customer_model_kwargs={
|
|
||||||
"customer_name": customer_name,
|
|
||||||
"address_1": form.cleaned_data["address"],
|
|
||||||
"phone": form.cleaned_data["phone_number"],
|
|
||||||
"email": form.cleaned_data["email"],
|
|
||||||
},
|
|
||||||
)
|
|
||||||
customer.additional_info.update({"customer_info": customer_dict})
|
|
||||||
customer.additional_info.update({"type": "customer"})
|
|
||||||
customer.save()
|
|
||||||
|
|
||||||
messages.success(request, _("Customer created successfully"))
|
# :param pk: The primary key of the CustomerModel object that is to be updated.
|
||||||
return redirect("customer_list")
|
# :type pk: int
|
||||||
|
|
||||||
except Exception as e:
|
# :return: A rendered HTML template displaying the customer form pre-filled
|
||||||
messages.error(request, _(f"An error occurred: {str(e)}"))
|
# with existing data if a GET request is received. On successful form
|
||||||
else:
|
# submission (POST request), redirects to the customer list page
|
||||||
messages.error(request, _("Please correct the errors below"))
|
# 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})
|
def form_valid(self, form):
|
||||||
|
form.instance.update_user_model()
|
||||||
|
form.instance.update_customer_model()
|
||||||
@login_required
|
return super().form_valid(form)
|
||||||
@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})
|
|
||||||
|
|
||||||
|
|
||||||
@login_required
|
@login_required
|
||||||
@ -2156,14 +2100,9 @@ def delete_customer(request, pk):
|
|||||||
:return: A redirect response to the customer list page.
|
:return: A redirect response to the customer list page.
|
||||||
:rtype: HttpResponseRedirect
|
:rtype: HttpResponseRedirect
|
||||||
"""
|
"""
|
||||||
customer = get_object_or_404(models.CustomerModel, pk=pk)
|
customer = get_object_or_404(models.Customer, pk=pk)
|
||||||
user = User.objects.get(email=customer.email)
|
customer.deactivate_account()
|
||||||
customer.active = False
|
messages.success(request, _("Customer deactivated successfully"))
|
||||||
user.is_active = False
|
|
||||||
customer.save()
|
|
||||||
user.save()
|
|
||||||
|
|
||||||
messages.success(request, _("Customer deleted successfully"))
|
|
||||||
return redirect("customer_list")
|
return redirect("customer_list")
|
||||||
|
|
||||||
|
|
||||||
@ -2193,17 +2132,17 @@ class VendorListView(LoginRequiredMixin, ListView):
|
|||||||
ordered by their creation date in descending order.
|
ordered by their creation date in descending order.
|
||||||
:type ordering: list
|
:type ordering: list
|
||||||
"""
|
"""
|
||||||
model = VendorModel
|
model = models.Vendor
|
||||||
context_object_name = "vendors"
|
context_object_name = "vendors"
|
||||||
paginate_by = 10
|
paginate_by = 10
|
||||||
template_name = "vendors/vendors_list.html"
|
template_name = "vendors/vendors_list.html"
|
||||||
ordering = ["-created"]
|
# ordering = ["-created"]
|
||||||
|
|
||||||
def get_queryset(self):
|
# def get_queryset(self):
|
||||||
query = self.request.GET.get("q")
|
# query = self.request.GET.get("q")
|
||||||
dealer = get_user_type(self.request)
|
# dealer = get_user_type(self.request)
|
||||||
vendors = dealer.entity.get_vendors().filter(active=True)
|
# vendors = dealer.entity.get_vendors().filter(active=True)
|
||||||
return apply_search_filters(vendors, query)
|
# return apply_search_filters(vendors, query)
|
||||||
|
|
||||||
|
|
||||||
@login_required
|
@login_required
|
||||||
@ -2295,24 +2234,24 @@ class VendorUpdateView(
|
|||||||
success_url = reverse_lazy("vendor_list")
|
success_url = reverse_lazy("vendor_list")
|
||||||
success_message = _("Vendor updated successfully")
|
success_message = _("Vendor updated successfully")
|
||||||
|
|
||||||
def get_initial(self):
|
# def get_initial(self):
|
||||||
initial = super().get_initial()
|
# initial = super().get_initial()
|
||||||
initial = self.object.additional_info
|
# initial = self.object.additional_info
|
||||||
return initial
|
# return initial
|
||||||
|
|
||||||
def form_valid(self, form):
|
def form_valid(self, form):
|
||||||
instance = form.save(commit=False)
|
# instance = form.save(commit=False)
|
||||||
|
print(self.request.POST)
|
||||||
instance.vendor_name = self.request.POST["name"]
|
# instance.vendor_name = self.request.POST["name"]
|
||||||
instance.vendor_number = self.request.POST["crn"]
|
# instance.vendor_number = self.request.POST["crn"]
|
||||||
instance.address_1 = self.request.POST["address"]
|
# instance.address_1 = self.request.POST["address"]
|
||||||
instance.phone = self.request.POST["phone_number"]
|
# instance.phone = self.request.POST["phone_number"]
|
||||||
instance.email = self.request.POST["email"]
|
# instance.email = self.request.POST["email"]
|
||||||
instance.tax_id_number = self.request.POST["vrn"]
|
# instance.tax_id_number = self.request.POST["vrn"]
|
||||||
additionals = form.cleaned_data
|
# additionals = form.cleaned_data
|
||||||
additionals["phone_number"] = str(additionals["phone_number"])
|
# additionals["phone_number"] = str(additionals["phone_number"])
|
||||||
instance.additional_info = additionals
|
# instance.additional_info = additionals
|
||||||
instance.save()
|
# instance.save()
|
||||||
return super().form_valid(form)
|
return super().form_valid(form)
|
||||||
|
|
||||||
|
|
||||||
@ -2333,8 +2272,9 @@ def delete_vendor(request, pk):
|
|||||||
:rtype: HttpResponseRedirect
|
:rtype: HttpResponseRedirect
|
||||||
"""
|
"""
|
||||||
vendor = get_object_or_404(models.Vendor, pk=pk)
|
vendor = get_object_or_404(models.Vendor, pk=pk)
|
||||||
# vendor.active = False
|
vendor.active = False
|
||||||
vendor.delete()
|
vendor.vendor_model.active = False
|
||||||
|
vendor.save()
|
||||||
messages.success(request, _("Vendor deleted successfully"))
|
messages.success(request, _("Vendor deleted successfully"))
|
||||||
return redirect("vendor_list")
|
return redirect("vendor_list")
|
||||||
|
|
||||||
@ -2794,7 +2734,7 @@ class OrganizationListView(LoginRequiredMixin, ListView):
|
|||||||
:ivar paginate_by: The number of organizations displayed per page.
|
:ivar paginate_by: The number of organizations displayed per page.
|
||||||
:type paginate_by: int
|
:type paginate_by: int
|
||||||
"""
|
"""
|
||||||
model = CustomerModel
|
model = models.Organization
|
||||||
template_name = "organizations/organization_list.html"
|
template_name = "organizations/organization_list.html"
|
||||||
context_object_name = "organizations"
|
context_object_name = "organizations"
|
||||||
paginate_by = 10
|
paginate_by = 10
|
||||||
@ -2802,9 +2742,7 @@ class OrganizationListView(LoginRequiredMixin, ListView):
|
|||||||
def get_queryset(self):
|
def get_queryset(self):
|
||||||
query = self.request.GET.get("q")
|
query = self.request.GET.get("q")
|
||||||
dealer = get_user_type(self.request)
|
dealer = get_user_type(self.request)
|
||||||
organization = dealer.entity.get_customers().filter(
|
organization = dealer.organizations.filter(active=True)
|
||||||
additional_info__type="organization", active=True
|
|
||||||
)
|
|
||||||
|
|
||||||
return apply_search_filters(organization, query)
|
return apply_search_filters(organization, query)
|
||||||
|
|
||||||
@ -2827,131 +2765,72 @@ class OrganizationDetailView(LoginRequiredMixin, DetailView):
|
|||||||
template for accessing the organization's data.
|
template for accessing the organization's data.
|
||||||
:type context_object_name: str
|
:type context_object_name: str
|
||||||
"""
|
"""
|
||||||
model = CustomerModel
|
model = models.Organization
|
||||||
template_name = "organizations/organization_detail.html"
|
template_name = "organizations/organization_detail.html"
|
||||||
context_object_name = "organization"
|
context_object_name = "organization"
|
||||||
|
|
||||||
|
|
||||||
@login_required
|
class OrganizationCreateView(LoginRequiredMixin, PermissionRequiredMixin, CreateView):
|
||||||
def OrganizationCreateView(request):
|
|
||||||
"""
|
"""
|
||||||
Handles the creation of a new organization via a web form. This view allows the
|
# 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
|
# authenticated user to submit data for creating an organization. If a POST request
|
||||||
is received, it validates the data, checks for duplicate organizations, and
|
# is received, it validates the data, checks for duplicate organizations, and
|
||||||
creates a customer linked to the organization, including its associated
|
# creates a customer linked to the organization, including its associated
|
||||||
information such as address, phone number, and logo. Upon success, the user
|
# information such as address, phone number, and logo. Upon success, the user
|
||||||
is redirected to the organization list, and a success message is displayed.
|
# is redirected to the organization list, and a success message is displayed.
|
||||||
|
|
||||||
:param request: The HTTP request object containing data for creating an organization.
|
# :param request: The HTTP request object containing data for creating an organization.
|
||||||
:type request: HttpRequest
|
# :type request: HttpRequest
|
||||||
:return: An HTTP response object rendering the organization create form page or
|
# :return: An HTTP response object rendering the organization create form page or
|
||||||
redirecting the user after a successful creation.
|
# redirecting the user after a successful creation.
|
||||||
:rtype: HttpResponse
|
# :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":
|
# Handles the update of an organization instance. This view fetches the organization
|
||||||
form = forms.OrganizationForm(request.POST)
|
# based on the provided primary key (pk) and renders a form for editing the
|
||||||
if CustomerModel.objects.filter(email=request.POST["email"]).exists():
|
# organization attributes. When a POST request is made, this view validates and
|
||||||
messages.error(
|
# processes the form data, updates the organization instance, and saves the changes.
|
||||||
request, _("An organization with this email already exists.")
|
# If the request method is not POST, it initializes the form with existing organization
|
||||||
)
|
# data for rendering.
|
||||||
return redirect("organization_create")
|
|
||||||
|
|
||||||
organization_dict = {
|
# :param request: The HTTP request object. Must be authenticated via login.
|
||||||
x: request.POST[x] for x in request.POST if x != "csrfmiddlewaretoken"
|
# :type request: HttpRequest
|
||||||
}
|
# :param pk: The primary key of the organization to be updated.
|
||||||
dealer = get_user_type(request)
|
# :type pk: int
|
||||||
name = organization_dict["first_name"] + " " + organization_dict["last_name"]
|
# :return: An HTTP response object. Either renders the organization form or redirects
|
||||||
customer = dealer.entity.create_customer(
|
# to the organization list upon successful update.
|
||||||
commit=False,
|
# :rtype: HttpResponse
|
||||||
customer_model_kwargs={
|
# """
|
||||||
"customer_name": name,
|
model = models.Organization
|
||||||
"address_1": organization_dict["address"],
|
form_class = forms.OrganizationForm
|
||||||
"phone": organization_dict["phone_number"],
|
permission_required = ["django_ledger.change_customermodel"]
|
||||||
"email": organization_dict["email"],
|
template_name = "organizations/organization_form.html"
|
||||||
},
|
success_url = reverse_lazy("organization_list")
|
||||||
)
|
success_message = "Organization updated successfully"
|
||||||
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
|
def form_valid(self, form):
|
||||||
organization_dict["pk"] = str(customer.pk)
|
form.instance.update_user_model()
|
||||||
customer.additional_info.update({"customer_info": organization_dict})
|
form.instance.update_customer_model()
|
||||||
customer.additional_info.update({"type": "organization"})
|
return super().form_valid(form)
|
||||||
customer.save()
|
|
||||||
messages.success(request, _("Organization created successfully"))
|
|
||||||
return redirect("organization_list")
|
|
||||||
else:
|
|
||||||
form = forms.OrganizationForm()
|
|
||||||
return render(request, "organizations/organization_form.html", {"form": form})
|
|
||||||
|
|
||||||
|
|
||||||
@login_required
|
|
||||||
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
|
@login_required
|
||||||
def OrganizationDeleteView(request, pk):
|
def OrganizationDeleteView(request, pk):
|
||||||
"""
|
"""
|
||||||
@ -2967,14 +2846,9 @@ def OrganizationDeleteView(request, pk):
|
|||||||
:return: An HTTP response redirecting to the organization list view.
|
:return: An HTTP response redirecting to the organization list view.
|
||||||
:rtype: HttpResponseRedirect
|
:rtype: HttpResponseRedirect
|
||||||
"""
|
"""
|
||||||
organization = get_object_or_404(CustomerModel, pk=pk)
|
organization = get_object_or_404(models.Organization, pk=pk)
|
||||||
try:
|
organization.deactivate_account()
|
||||||
User.objects.get(email=organization.email).delete()
|
messages.success(request, _("Organization Deactivated successfully"))
|
||||||
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"))
|
|
||||||
return redirect("organization_list")
|
return redirect("organization_list")
|
||||||
|
|
||||||
|
|
||||||
@ -3633,8 +3507,9 @@ def create_estimate(request, pk=None):
|
|||||||
data = json.loads(request.body)
|
data = json.loads(request.body)
|
||||||
title = data.get("title")
|
title = data.get("title")
|
||||||
customer_id = data.get("customer")
|
customer_id = data.get("customer")
|
||||||
terms = data.get("terms")
|
# terms = data.get("terms")
|
||||||
customer = entity.get_customers().filter(pk=customer_id).first()
|
# customer = entity.get_customers().filter(pk=customer_id).first()
|
||||||
|
customer = models.Customer.objects.filter(pk=customer_id).first()
|
||||||
|
|
||||||
items = data.get("item", [])
|
items = data.get("item", [])
|
||||||
quantities = data.get("quantity", [])
|
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")},
|
{"status": "error", "message": _("Quantity must be less than or equal to the number of cars in stock")},
|
||||||
)
|
)
|
||||||
estimate = entity.create_estimate(
|
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):
|
if isinstance(items, list):
|
||||||
item_quantity_map = {}
|
item_quantity_map = {}
|
||||||
@ -3767,12 +3642,11 @@ def create_estimate(request, pk=None):
|
|||||||
}
|
}
|
||||||
)
|
)
|
||||||
|
|
||||||
form = forms.EstimateModelCreateForm(
|
# form = forms.EstimateModelCreateForm(
|
||||||
entity_slug=entity.slug, user_model=entity.admin
|
# entity_slug=entity.slug, user_model=entity.admin
|
||||||
)
|
# )
|
||||||
form.fields["customer"].queryset = entity.get_customers().filter(
|
form = forms.EstimateModelCreateForm()
|
||||||
active=True, additional_info__type="customer"
|
form.fields["customer"].queryset = dealer.customers.all()
|
||||||
)
|
|
||||||
|
|
||||||
if pk:
|
if pk:
|
||||||
opportunity = models.Opportunity.objects.get(pk=pk)
|
opportunity = models.Opportunity.objects.get(pk=pk)
|
||||||
@ -3982,13 +3856,9 @@ class EstimatePreviewView(LoginRequiredMixin, PermissionRequiredMixin, DetailVie
|
|||||||
dealer = get_user_type(self.request)
|
dealer = get_user_type(self.request)
|
||||||
estimate = kwargs.get("object")
|
estimate = kwargs.get("object")
|
||||||
if estimate.get_itemtxs_data():
|
if estimate.get_itemtxs_data():
|
||||||
data = get_financial_values(estimate)
|
# data = get_financial_values(estimate)
|
||||||
|
calculator = CarFinanceCalculator(estimate)
|
||||||
kwargs["vat_amount"] = data["vat_amount"]
|
kwargs["data"] = calculator.get_finance_data()
|
||||||
kwargs["total"] = data["grand_total"]
|
|
||||||
kwargs["discount_amount"] = data["discount_amount"]
|
|
||||||
kwargs["vat"] = data["vat"]
|
|
||||||
kwargs["additional_services"] = data["additional_services"]
|
|
||||||
kwargs["dealer"] = dealer
|
kwargs["dealer"] = dealer
|
||||||
return super().get_context_data(**kwargs)
|
return super().get_context_data(**kwargs)
|
||||||
|
|
||||||
@ -6215,11 +6085,12 @@ def send_email_view(request, pk):
|
|||||||
{dealer.phone_number}
|
{dealer.phone_number}
|
||||||
هيكل | Haikal
|
هيكل | Haikal
|
||||||
"""
|
"""
|
||||||
subject = _("Quotation")
|
# subject = _("Quotation")
|
||||||
|
|
||||||
send_email(
|
send_email(
|
||||||
str(settings.DEFAULT_FROM_EMAIL),
|
str(settings.DEFAULT_FROM_EMAIL),
|
||||||
estimate.customer.email,
|
estimate.customer.email,
|
||||||
subject,
|
"عرض سعر - Quotation",
|
||||||
msg,
|
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>
|
||||||
<div class="row mb-3">
|
<div class="row mb-3">
|
||||||
<div class="col-sm-6 col-md-8">
|
<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 %}
|
{% csrf_token %}
|
||||||
{{ form|crispy }}
|
{{ form|crispy }}
|
||||||
<div class="col-12">
|
<div class="col-12">
|
||||||
|
|||||||
@ -70,17 +70,17 @@
|
|||||||
</td>
|
</td>
|
||||||
<td class="name align-middle white-space-nowrap ps-0">
|
<td class="name align-middle white-space-nowrap ps-0">
|
||||||
<div class="d-flex align-items-center">
|
<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 class="d-flex align-items-center">
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</td>
|
</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="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="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.additional_info.customer_info.national_id }}</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">
|
<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">
|
<td class="date align-middle white-space-nowrap text-body-tertiary text-opacity-85 ps-4 text-body-tertiary">
|
||||||
{% if customer.active %}
|
{% if customer.active %}
|
||||||
<span class="badge badge-phoenix badge-phoenix-success"><i class="fas fa-check"></i> {{customer.active}}</span>
|
<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="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="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="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>
|
||||||
<div class="col-12 col-sm-auto flex-1">
|
<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>
|
<p class="text-body-secondary">{{ customer.created|timesince}}</p>
|
||||||
</div>
|
</div>
|
||||||
</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>
|
<button class="btn btn-link p-0"><span class="fas fa-pen fs-8 ms-3 text-body-quaternary"></span></button>
|
||||||
</div>
|
</div>
|
||||||
<h5 class="text-body-secondary">{{ _("Address") }}</h5>
|
<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">
|
<div class="mb-3">
|
||||||
<h5 class="text-body-secondary">{% trans 'Email' %}</h5><a href="{{ customer.email}}">{{ customer.email }}</a>
|
<h5 class="text-body-secondary">{% trans 'Email' %}</h5><a href="{{ customer.email}}">{{ customer.email }}</a>
|
||||||
</div>
|
</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>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@ -20,28 +20,28 @@
|
|||||||
{% if not car.ready %}
|
{% if not car.ready %}
|
||||||
<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>
|
<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>
|
<button class="btn-close" type="button" data-bs-dismiss="alert" aria-label="Close"></button>
|
||||||
</div>
|
</div>
|
||||||
{% endif %}
|
{% endif %}
|
||||||
{% if car.get_transfer.status == "draft" %}
|
{% 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>
|
<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>
|
<button class="btn-close" type="button" data-bs-dismiss="alert" aria-label="Close"></button>
|
||||||
</div>
|
</div>
|
||||||
{% endif %}
|
{% endif %}
|
||||||
{% if car.get_transfer and car.get_transfer.status == "approved" %}
|
{% 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>
|
<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>
|
<button class="btn-close" type="button" data-bs-dismiss="alert" aria-label="Close"></button>
|
||||||
</div>
|
</div>
|
||||||
{% endif %}
|
{% endif %}
|
||||||
{% if car.is_reserved %}
|
{% 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>
|
<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>
|
<button class="btn-close" type="button" data-bs-dismiss="alert" aria-label="Close"></button>
|
||||||
</div>
|
</div>
|
||||||
{% endif %}
|
{% endif %}
|
||||||
@ -98,7 +98,7 @@
|
|||||||
{% if car.vendor %}
|
{% if car.vendor %}
|
||||||
<tr>
|
<tr>
|
||||||
<th>{% trans "Vendor"|capfirst %}</th>
|
<th>{% trans "Vendor"|capfirst %}</th>
|
||||||
<td>{{ car.vendor.vendor_name }}</td>
|
<td>{{ car.vendor.name }}</td>
|
||||||
</tr>
|
</tr>
|
||||||
{% endif %}
|
{% endif %}
|
||||||
<tr>
|
<tr>
|
||||||
@ -361,7 +361,7 @@
|
|||||||
<p class="card-header rounded-top fw-bold">{% trans 'Transfer Details' %}</p>
|
<p class="card-header rounded-top fw-bold">{% trans 'Transfer Details' %}</p>
|
||||||
<div class="card-body">
|
<div class="card-body">
|
||||||
<div class="table-responsive scrollbar mb-3">
|
<div class="table-responsive scrollbar mb-3">
|
||||||
<table class="table table-sm fs-9 mb-0 overflow-hidden">
|
<table class="table table-sm fs-9 mb-0 overflow-hidden">
|
||||||
<thead>
|
<thead>
|
||||||
<tr>
|
<tr>
|
||||||
<th>{% trans "Action" %}</th>
|
<th>{% trans "Action" %}</th>
|
||||||
@ -373,7 +373,7 @@
|
|||||||
</tr>
|
</tr>
|
||||||
</thead>
|
</thead>
|
||||||
<tbody>
|
<tbody>
|
||||||
|
|
||||||
<tr>
|
<tr>
|
||||||
<td><span class="badge badge-phoenix badge-phoenix-info">Transfer</span></td>
|
<td><span class="badge badge-phoenix badge-phoenix-info">Transfer</span></td>
|
||||||
<td>
|
<td>
|
||||||
@ -396,11 +396,11 @@
|
|||||||
<a class="btn btn-sm btn-phoenix-success" href="{% url 'transfer_detail' car.get_transfer.pk %}">Approve</a>
|
<a class="btn btn-sm btn-phoenix-success" href="{% url 'transfer_detail' car.get_transfer.pk %}">Approve</a>
|
||||||
{% endif %}
|
{% endif %}
|
||||||
</td>
|
</td>
|
||||||
<td>
|
<td>
|
||||||
<a class="btn btn-sm btn-phoenix-success" href="{% url 'transfer_detail' car.get_transfer.pk %}?action=cancel">Cancel</a>
|
<a class="btn btn-sm btn-phoenix-success" href="{% url 'transfer_detail' car.get_transfer.pk %}?action=cancel">Cancel</a>
|
||||||
</td>
|
</td>
|
||||||
</tr>
|
</tr>
|
||||||
</tbody>
|
</tbody>
|
||||||
</table>
|
</table>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@ -6,14 +6,24 @@
|
|||||||
height: auto;
|
height: auto;
|
||||||
margin: 0 auto;
|
margin: 0 auto;
|
||||||
}
|
}
|
||||||
|
.disabled{
|
||||||
|
opacity: 0.5;
|
||||||
|
pointer-events: none;
|
||||||
|
}
|
||||||
</style>
|
</style>
|
||||||
|
|
||||||
<!-- JavaScript Section -->
|
<!-- JavaScript Section -->
|
||||||
|
|
||||||
<script src="{% static 'vendors/zxing/index.min.js' %}"></script>
|
<script src="{% static 'vendors/zxing/index.min.js' %}"></script>
|
||||||
<script src="{% static 'vendors/tesseract/tesseract.min.js' %}"></script>
|
<script src="{% static 'vendors/tesseract/tesseract.min.js' %}"></script>
|
||||||
|
{% if not vendor_exists %}
|
||||||
<div class=" container-fluid m-0">
|
<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>
|
<form method="post" id="carForm" class="form needs-validation" novalidate>
|
||||||
{% csrf_token %}
|
{% csrf_token %}
|
||||||
{% include 'partials/form_errors.html' %}
|
{% include 'partials/form_errors.html' %}
|
||||||
|
|||||||
@ -5,10 +5,10 @@
|
|||||||
<div class="row my-4">
|
<div class="row my-4">
|
||||||
<h2>{{ organization.get_local_name }}</h2>
|
<h2>{{ organization.get_local_name }}</h2>
|
||||||
<ul class="list-group mb-4">
|
<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 "CRN" %}:</strong> {{ organization.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 "VRN" %}:</strong> {{ organization.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 "Phone" %}:</strong> {{ organization.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 "Address" %}:</strong> {{ organization.address }}</li>
|
||||||
</ul>
|
</ul>
|
||||||
<div class="d-flex">
|
<div class="d-flex">
|
||||||
<a href="{% url 'organization_update' organization.pk %}" class="btn btn-sm btn-warning me-2">{% trans "Edit" %}</a>
|
<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">
|
<td class="name align-middle white-space-nowrap ps-0">
|
||||||
<div class="d-flex align-items-center">
|
<div class="d-flex align-items-center">
|
||||||
<div>
|
<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">
|
<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>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</td>
|
</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="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.additional_info.customer_info.vrn }}</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">
|
<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>
|
||||||
<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="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">
|
<td class="align-middle white-space-nowrap text-end pe-0 ps-4">
|
||||||
{% if perms.django_ledger.change_customermodel %}
|
{% if perms.django_ledger.change_customermodel %}
|
||||||
<div class="btn-reveal-trigger position-static">
|
<div class="btn-reveal-trigger position-static">
|
||||||
<button class="btn btn-sm dropdown-toggle dropdown-caret-none transition-none btn-reveal fs-10" 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>
|
<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">
|
<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>
|
<a href="{% url 'organization_update' org.pk %}" class="dropdown-item text-success-dark">{% trans 'Edit' %}</a>
|
||||||
{% if perms.django_ledger.delete_customermodel %}
|
{% 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>
|
<div class="dropdown-divider"></div><button class="dropdown-item text-danger" data-bs-toggle="modal" data-bs-target="#deleteModal">{% trans 'Delete' %}</button>
|
||||||
{% endif %}
|
{% endif %}
|
||||||
|
|||||||
@ -13,7 +13,7 @@
|
|||||||
</style>
|
</style>
|
||||||
{% endblock customCSS %}
|
{% endblock customCSS %}
|
||||||
{% block content %}
|
{% block content %}
|
||||||
<div class="row mt-4">
|
<div class="row mt-4">
|
||||||
{% if not items %}
|
{% if not items %}
|
||||||
<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>
|
<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>
|
<h3 class="text-center"><i class="fa-regular fa-file-lines"></i> {% trans "Create Quotation" %}</h3>
|
||||||
{% csrf_token %}
|
{% csrf_token %}
|
||||||
<div class="row g-3 col-10">
|
<div class="row g-3 col-10">
|
||||||
{{ form|crispy }}
|
{{ form|crispy }}
|
||||||
<div class="row mt-5">
|
<div class="row mt-5">
|
||||||
<div id="formrow">
|
<div id="formrow">
|
||||||
<h3 class="text-start"><i class="fa-solid fa-car-side"></i> {{ _("Cars") }}</h3>
|
<h3 class="text-start"><i class="fa-solid fa-car-side"></i> {{ _("Cars") }}</h3>
|
||||||
@ -70,7 +70,7 @@
|
|||||||
{% endblock content %}
|
{% endblock content %}
|
||||||
|
|
||||||
{% block customJS %}
|
{% block customJS %}
|
||||||
<script>
|
<script>
|
||||||
const Toast = Swal.mixin({
|
const Toast = Swal.mixin({
|
||||||
toast: true,
|
toast: true,
|
||||||
position: "top-end",
|
position: "top-end",
|
||||||
@ -98,7 +98,7 @@
|
|||||||
</div>
|
</div>
|
||||||
<div class="mb-2 col-sm-2">
|
<div class="mb-2 col-sm-2">
|
||||||
<input class="form-control quantity" type="number" placeholder="Quantity" name="quantity[]" required>
|
<input class="form-control quantity" type="number" placeholder="Quantity" name="quantity[]" required>
|
||||||
</div>
|
</div>
|
||||||
<div class="mb-2 col-sm-1">
|
<div class="mb-2 col-sm-1">
|
||||||
<button class="btn btn-danger removeBtn"><i class="fa-solid fa-trash"></i> {{ _("Remove") }}</button>
|
<button class="btn btn-danger removeBtn"><i class="fa-solid fa-trash"></i> {{ _("Remove") }}</button>
|
||||||
</div>
|
</div>
|
||||||
@ -133,7 +133,6 @@
|
|||||||
csrfmiddlewaretoken: document.querySelector('[name=csrfmiddlewaretoken]').value,
|
csrfmiddlewaretoken: document.querySelector('[name=csrfmiddlewaretoken]').value,
|
||||||
title: document.querySelector('[name=title]').value,
|
title: document.querySelector('[name=title]').value,
|
||||||
customer: document.querySelector('[name=customer]').value,
|
customer: document.querySelector('[name=customer]').value,
|
||||||
terms: document.querySelector('[name=terms]').value,
|
|
||||||
item: [],
|
item: [],
|
||||||
quantity: [],
|
quantity: [],
|
||||||
opportunity_id: "{{opportunity_id}}"
|
opportunity_id: "{{opportunity_id}}"
|
||||||
@ -148,7 +147,7 @@
|
|||||||
formData.quantity.push(input.value);
|
formData.quantity.push(input.value);
|
||||||
});
|
});
|
||||||
console.log(formData);
|
console.log(formData);
|
||||||
|
|
||||||
try {
|
try {
|
||||||
// Send data to the server using fetch
|
// Send data to the server using fetch
|
||||||
const response = await fetch("{% url 'estimate_create' %}", {
|
const response = await fetch("{% url 'estimate_create' %}", {
|
||||||
@ -175,8 +174,8 @@
|
|||||||
notify("error","Unexpected response from the server");
|
notify("error","Unexpected response from the server");
|
||||||
}
|
}
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
|
|
||||||
notify("error", error);
|
notify("error", error);
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
</script>
|
</script>
|
||||||
|
|||||||
@ -99,23 +99,25 @@
|
|||||||
<td></td>
|
<td></td>
|
||||||
<td>
|
<td>
|
||||||
<div class="qr-code">
|
<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>
|
</div>
|
||||||
</td>
|
</td>
|
||||||
</tr>
|
</tr>
|
||||||
<tr>
|
<tr>
|
||||||
<td><strong>{{ dealer.name }}</strong></td>
|
<td class="ps-1"><strong>Customer Name</strong></td>
|
||||||
<td></td>
|
<td class="text-center">{{ dealer.arabic_name }}<br>{{ dealer.name }}</td>
|
||||||
<td class="text-end"><strong>{{ dealer.arabic_name }}</strong></td>
|
<td class="text-end"><strong>{{ dealer.arabic_name }}</strong></td>
|
||||||
</tr>
|
</tr>
|
||||||
<tr>
|
<tr>
|
||||||
<td><strong>Address</strong></td>
|
<td class="ps-1"><strong>Address</strong></td>
|
||||||
<td>{{ dealer.address }}</td>
|
<td class="text-center">{{ dealer.address }}</td>
|
||||||
<td class="text-end"> <strong>العنوان</strong></td>
|
<td class="text-end"> <strong>العنوان</strong></td>
|
||||||
</tr>
|
</tr>
|
||||||
<tr>
|
<tr>
|
||||||
<td><strong>Phone</strong></td>
|
<td class="ps-1"><strong>Phone</strong></td>
|
||||||
<td>{{ dealer.phone_number }}</td>
|
<td class="text-center">{{ dealer.phone_number }}</td>
|
||||||
<td class="text-end"><strong>جوال</strong></td>
|
<td class="text-end"><strong>جوال</strong></td>
|
||||||
</tr>
|
</tr>
|
||||||
<tr>
|
<tr>
|
||||||
@ -190,8 +192,8 @@
|
|||||||
<tr>
|
<tr>
|
||||||
<td class="ps-1 fs-10 align-content-center" colspan="5"></td>
|
<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.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_price_before_discount|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.grand_total|floatformat:2 }}</td>
|
||||||
</tr>
|
</tr>
|
||||||
</tbody>
|
</tbody>
|
||||||
</table>
|
</table>
|
||||||
|
|||||||
@ -96,46 +96,40 @@
|
|||||||
<h5 class="fs-5">Tax Invoice / فاتورة ضريبية</h5>
|
<h5 class="fs-5">Tax Invoice / فاتورة ضريبية</h5>
|
||||||
</div>
|
</div>
|
||||||
<div class="invoice-details p-1">
|
<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>
|
<tr>
|
||||||
<td>
|
<td class="ps-1"><strong>Customer Name</strong></td>
|
||||||
<div class="d-flex justify-content-center align-items-center">
|
<td class="text-center">{{ dealer.arabic_name }}<br>{{ dealer.name }}</td>
|
||||||
<div class="qr-code">
|
<td class="text-end"><strong>اسم العميل</strong></td>
|
||||||
<img src="{% static 'qr_code/Marwan_qr.png' %}" alt="QR Code">
|
</tr>
|
||||||
</div>
|
<tr>
|
||||||
</div>
|
<td class="ps-1"><strong>Address</strong></td>
|
||||||
</td>
|
<td class="text-center">{{ dealer.address }}</td>
|
||||||
<td></td>
|
<td class="text-end"> <strong>العنوان</strong></td>
|
||||||
<td>
|
</tr>
|
||||||
<div class="d-flex justify-content-end align-items-end">
|
<tr>
|
||||||
<div class="dealer-logo ">
|
<td class="ps-1"><strong>Phone</strong></td>
|
||||||
{% if dealer.logo %}
|
<td class="text-center">{{ dealer.phone_number }}</td>
|
||||||
<img class="rounded-soft" src="{{ dealer.logo.url|default:'' }}" alt="Dealer Logo"/>
|
<td class="text-end"><strong>جوال</strong></td>
|
||||||
{% endif %}
|
</tr>
|
||||||
</div>
|
<tr>
|
||||||
</div>
|
<td class="ps-1"><strong>VAT Number</strong></td>
|
||||||
</td>
|
<td class="text-center">{{ dealer.vrn }}</td>
|
||||||
</tr>
|
<td class="text-end p-1"><strong>الرقم الضريبي</strong></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>
|
|
||||||
</table>
|
</table>
|
||||||
|
|
||||||
<table class="table table-sm table-bordered border-gray-50">
|
<table class="table table-sm table-bordered border-gray-50">
|
||||||
@ -203,8 +197,8 @@
|
|||||||
<tr>
|
<tr>
|
||||||
<td class="ps-1 fs-10 align-content-center" colspan="5"></td>
|
<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.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_price_before_discount|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.grand_total|floatformat }}</td>
|
||||||
</tr>
|
</tr>
|
||||||
</tbody>
|
</tbody>
|
||||||
</table>
|
</table>
|
||||||
|
|||||||
2
templates/vendors/vendor_form.html
vendored
2
templates/vendors/vendor_form.html
vendored
@ -27,7 +27,7 @@
|
|||||||
<div class="row">
|
<div class="row">
|
||||||
<div class="col-xl-9">
|
<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 %}
|
{% csrf_token %}
|
||||||
{{ redirect_field }}
|
{{ redirect_field }}
|
||||||
{{ form|crispy }}
|
{{ 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="" />
|
<div class="avatar avatar-xl me-3"><img class="rounded-circle" src="{% static 'images/icons/picture.svg' %}" alt="" />
|
||||||
{% endif %}
|
{% endif %}
|
||||||
</div>
|
</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">
|
<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>
|
<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>
|
</div>
|
||||||
@ -122,11 +122,11 @@
|
|||||||
</div>
|
</div>
|
||||||
</td>
|
</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="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="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">
|
<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>
|
{{ vendor.address }}</td>
|
||||||
<td class="date align-middle white-space-nowrap text-body-tertiary text-opacity-85 ps-4 text-body-tertiary">{{ vendor.created|date }}</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">
|
<td class="align-middle white-space-nowrap text-end pe-0 ps-4">
|
||||||
<div class="btn-reveal-trigger position-static">
|
<div class="btn-reveal-trigger position-static">
|
||||||
<button class="btn btn-sm dropdown-toggle dropdown-caret-none transition-none btn-reveal fs-10" 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>
|
<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