This commit is contained in:
Marwan Alwali 2025-05-05 18:00:04 +03:00
parent 5a76fb4fa2
commit c3a31d71e1
4 changed files with 541 additions and 716 deletions

View File

@ -96,12 +96,12 @@ class StaffForm(forms.ModelForm):
"""
email = forms.EmailField(
required=True,
label="Email",
label=_("Email"),
widget=forms.EmailInput(attrs={"class": "form-control form-control-sm"}),
)
service_offered = forms.ModelMultipleChoiceField(
label="Services Offered",
label=_("Services Offered"),
widget=forms.CheckboxSelectMultiple(attrs={"class": "form-check-input"}),
queryset=Service.objects.all(),
required=False,)
@ -837,7 +837,7 @@ class WizardForm3(forms.Form):
max_length=10,
error_messages={
"required": _("This field is required."),
"max_length": "Commercial Registration Number must be 10 characters.",
"max_length": _("Commercial Registration Number must be 10 characters"),
},
)
@ -890,11 +890,11 @@ class ItemForm(forms.Form):
"""
item = forms.ModelChoiceField(
queryset=ledger_models.ItemModel.objects.all(),
label="Item",
label=_("Item"),
required=True,
validators=[MinLengthValidator(5)],
)
quantity = forms.DecimalField(label="Quantity", required=True)
quantity = forms.DecimalField(label=_("Quantity"), required=True)
class PaymentForm(forms.Form):
@ -919,13 +919,13 @@ class PaymentForm(forms.Form):
"""
invoice = forms.ModelChoiceField(
queryset=ledger_models.InvoiceModel.objects.all(),
label="Invoice",
label=_("Invoice"),
required=False,
)
bill = forms.ModelChoiceField(
queryset=ledger_models.BillModel.objects.all(), label="Bill", required=False
queryset=ledger_models.BillModel.objects.all(), label=_("Bill"), required=False
)
amount = forms.DecimalField(label="Amount", required=True)
amount = forms.DecimalField(label=_("Amount"), required=True)
payment_method = forms.ChoiceField(
choices=[
("cash", _("cash")),
@ -934,11 +934,11 @@ class PaymentForm(forms.Form):
("debit", _("debit")),
("SADAD", _("SADAD")),
],
label="Payment Method",
label=_("Payment Method"),
required=True,
)
payment_date = forms.DateField(
label="Payment Date", widget=DateInput(attrs={"type": "date"}), required=True
label=_("Payment Date"), widget=DateInput(attrs={"type": "date"}), required=True
)
def clean_amount(self):
@ -947,13 +947,13 @@ class PaymentForm(forms.Form):
model = invoice if invoice else bill
amount = self.cleaned_data["amount"]
if amount + model.amount_paid > model.amount_due:
raise forms.ValidationError("Payment amount is greater than amount due")
raise forms.ValidationError(_("Payment amount is greater than amount due"))
if amount <= 0:
raise forms.ValidationError("Payment amount must be greater than 0")
raise forms.ValidationError(_("Payment amount must be greater than 0"))
if model.is_paid():
raise forms.ValidationError("Invoice is already paid")
raise forms.ValidationError(_("Invoice is already paid"))
if amount > model.amount_due:
raise forms.ValidationError("Payment amount is greater than amount due")
raise forms.ValidationError(_("Payment amount is greater than amount due"))
return amount
@ -979,7 +979,7 @@ class EmailForm(forms.Form):
subject = forms.CharField(max_length=255)
message = forms.CharField(widget=forms.Textarea)
from_email = forms.EmailField()
to_email = forms.EmailField(label="To")
to_email = forms.EmailField(label=_("To"))
class LeadForm(forms.ModelForm):
@ -999,7 +999,7 @@ class LeadForm(forms.ModelForm):
:type id_car_model: ModelChoiceField
"""
id_car_make = forms.ModelChoiceField(
label="Make",
label=_("Make"),
queryset=CarMake.objects.filter(is_sa_import=True),
widget=forms.Select(
attrs={
@ -1016,7 +1016,7 @@ class LeadForm(forms.ModelForm):
required=True,
)
id_car_model = forms.ModelChoiceField(
label="Model",
label=_("Model"),
queryset=CarModel.objects.none(),
widget=forms.Select(attrs={"class": "form-control form-control-sm"}),
required=True,
@ -1291,7 +1291,7 @@ class OpportunityStatusForm(forms.Form):
:type stage: ChoiceField
"""
status = forms.ChoiceField(
label="Status",
label=_("Status"),
choices=Status.choices,
widget=forms.Select(
attrs={
@ -1307,7 +1307,7 @@ class OpportunityStatusForm(forms.Form):
required=True,
)
stage = forms.ChoiceField(
label="Stage",
label=_("Stage"),
choices=Stage.choices,
widget=forms.Select(
attrs={
@ -1496,7 +1496,7 @@ class CreditCardField(forms.CharField):
# Validate using Luhn algorithm
if not Luhn.check_luhn(cleaned_value):
raise forms.ValidationError("Please enter a valid credit card number")
raise forms.ValidationError(_("Please enter a valid credit card number"))
# Add basic card type detection (optional)
if cleaned_value.startswith('4'):
@ -1526,17 +1526,17 @@ class ExpiryDateField(forms.CharField):
# Validate month
if month < 1 or month > 12:
raise forms.ValidationError("Please enter a valid month (01-12)")
raise forms.ValidationError(_("Please enter a valid month (01-12)"))
# Validate not expired
current_year = datetime.now().year
current_month = datetime.now().month
if year < current_year or (year == current_year and month < current_month):
raise forms.ValidationError("This card appears to be expired")
raise forms.ValidationError(_("This card appears to be expired"))
except (ValueError, AttributeError):
raise forms.ValidationError("Please enter a valid expiry date in MM/YY format")
raise forms.ValidationError(_("Please enter a valid expiry date in MM/YY format"))
return value
@ -1545,9 +1545,9 @@ class CVVField(forms.CharField):
value = super().clean(value)
if value:
if not value.isdigit():
raise forms.ValidationError("CVV must contain only digits")
raise forms.ValidationError(_("CVV must contain only digits"))
if len(value) not in (3, 4):
raise forms.ValidationError("CVV must be 3 or 4 digits")
raise forms.ValidationError(_("CVV must be 3 or 4 digits"))
return value
class PaymentPlanForm(forms.Form):
@ -1606,7 +1606,7 @@ class PaymentPlanForm(forms.Form):
'id': 'card-number',
}),
label="Card Number"
label=_("Card Number")
)
expiry_date = ExpiryDateField(
@ -1617,7 +1617,7 @@ class PaymentPlanForm(forms.Form):
'id': 'expiry',
}),
label="Expiration Date"
label=_("Expiration Date")
)
cvv = CVVField(
@ -1628,7 +1628,7 @@ class PaymentPlanForm(forms.Form):
'id': 'cvv',
}),
label="Security Code (CVV)"
label=_("Security Code (CVV)")
)
card_name = forms.CharField(
@ -1640,7 +1640,7 @@ class PaymentPlanForm(forms.Form):
'id': 'card-name',
}),
label="Name on Card"
label=_("Name on Card")
)
# Terms and conditions

View File

@ -171,7 +171,7 @@ class CarMake(models.Model, LocalizedNameMixin):
return self.name
class Meta:
verbose_name = "Make"
verbose_name = _("Make")
class CarModel(models.Model, LocalizedNameMixin):
@ -184,7 +184,7 @@ class CarModel(models.Model, LocalizedNameMixin):
return self.name
class Meta:
verbose_name = "Model"
verbose_name = _("Model")
class CarSerie(models.Model, LocalizedNameMixin):
@ -202,7 +202,7 @@ class CarSerie(models.Model, LocalizedNameMixin):
return self.name
class Meta:
verbose_name = "Series"
verbose_name = _("Series")
class CarTrim(models.Model, LocalizedNameMixin):
@ -219,7 +219,7 @@ class CarTrim(models.Model, LocalizedNameMixin):
return self.name
class Meta:
verbose_name = "Trim"
verbose_name = _("Trim")
class CarEquipment(models.Model, LocalizedNameMixin):
@ -233,7 +233,7 @@ class CarEquipment(models.Model, LocalizedNameMixin):
return self.name
class Meta:
verbose_name = "Equipment"
verbose_name = _("Equipment")
class CarSpecification(models.Model, LocalizedNameMixin):
@ -248,7 +248,7 @@ class CarSpecification(models.Model, LocalizedNameMixin):
return self.name
class Meta:
verbose_name = "Specification"
verbose_name = _("Specification")
class CarSpecificationValue(models.Model):
@ -264,7 +264,7 @@ class CarSpecificationValue(models.Model):
return f"{self.id_car_specification.name}: {self.value} {self.unit}"
class Meta:
verbose_name = "Specification Value"
verbose_name = _("Specification Value")
class CarOption(models.Model, LocalizedNameMixin):
@ -279,7 +279,7 @@ class CarOption(models.Model, LocalizedNameMixin):
return self.name
class Meta:
verbose_name = "Option"
verbose_name = _("Option")
class CarOptionValue(models.Model):
@ -298,7 +298,7 @@ class CarOptionValue(models.Model):
return f"{self.id_car_option.name}: {self.value} {self.unit}"
class Meta:
verbose_name = "Option Value"
verbose_name = _("Option Value")
class CarTransferStatusChoices(models.TextChoices):
@ -480,7 +480,7 @@ class Car(models.Model):
self.cancel_reservation()
self.status = CarStatusChoices.SOLD
self.save()
Activity.objects.create(dealer=dealer,content_object=self, notes="Car Sold",created_by=request.user,activity_type=ActionChoices.SALE_CAR)
Activity.objects.create(dealer=dealer,content_object=self, notes=_("Car Sold"),created_by=request.user,activity_type=ActionChoices.SALE_CAR)
def cancel_reservation(self):
if self.reservations.exists():
@ -801,89 +801,6 @@ class TimestampedModel(models.Model):
abstract = True
# class Subscription(models.Model):
# plan = models.ForeignKey(
# "SubscriptionPlan", on_delete=models.CASCADE, related_name="subscriptions"
# )
# start_date = models.DateField(help_text="Date when the subscription starts")
# end_date = models.DateField(help_text="Date when the subscription ends")
# users = models.ManyToManyField(
# User, through="SubscriptionUser"
# ) # many-to-many relationship with User model
# is_active = models.BooleanField(default=True)
# billing_cycle = models.CharField(
# max_length=10,
# choices=[("monthly", "Monthly"), ("annual", "Annual")],
# default="monthly",
# help_text="Billing cycle for the subscription",
# )
# last_payment_date = models.DateField(
# null=True, blank=True, help_text="Date of the last payment made"
# )
# next_payment_date = models.DateField(
# null=True, blank=True, help_text="Date of the next payment due"
# )
#
# class Meta:
# verbose_name = _("Subscription")
# verbose_name_plural = _("Subscriptions")
#
# def __str__(self):
# return self.plan.name
#
# @property
# def total_subscribers(self):
# return self.users.count()
#
#
# class SubscriptionUser(models.Model):
# subscription = models.ForeignKey(Subscription, on_delete=models.CASCADE)
# user = models.ForeignKey(User, on_delete=models.CASCADE)
#
# class Meta:
# verbose_name = _("Subscription User")
# verbose_name_plural = _("Subscription Users")
#
# def __str__(self):
# return f"{self.subscription} - {self.user}"
#
#
# class SubscriptionPlan(models.Model):
# name = models.CharField(
# max_length=100, unique=True, help_text=_("Name of the subscription plan")
# )
# description = models.TextField()
# price = models.DecimalField(max_digits=10, decimal_places=2)
# max_users = models.PositiveIntegerField(
# help_text=_("Maximum number of users allowed"), default=1
# )
# max_inventory_size = models.PositiveIntegerField(
# help_text=_("Maximum number of cars in inventory"), default=50
# )
# support_level = models.CharField(
# max_length=50,
# choices=[
# ("basic", "Basic Support"),
# ("priority", "Priority Support"),
# ("dedicated", "Dedicated Support"),
# ],
# default="basic",
# help_text="Level of support provided",
# )
# custom_features = models.JSONField(
# blank=True, null=True, help_text=_("Additional features specific to this plan")
# )
# created_at = models.DateTimeField(auto_now_add=True)
# updated_at = models.DateTimeField(auto_now=True)
#
# class Meta:
# verbose_name = _("Subscription Plan")
# verbose_name_plural = _("Subscription Plans")
#
# def __str__(self):
# return f"{self.name} - {self.price}"
class Dealer(models.Model, LocalizedNameMixin):
user = models.OneToOneField(User, on_delete=models.CASCADE, related_name="dealer")
crn = models.CharField(
@ -966,31 +883,6 @@ class Dealer(models.Model, LocalizedNameMixin):
def __str__(self):
return self.name
# @property
# def get_sub_dealers(self):
# if self.dealer_type == "OWNER":
# return self.sub_dealers.all()
# return None
#
# @property
# def is_parent(self):
# return self.dealer_type == "OWNER"
# @property
# def get_root_dealer(self):
# return self.parent_dealer if self.parent_dealer else self
##############################
# Additional staff types for later
# COORDINATOR = "coordinator", _("Coordinator")
# RECEPTIONIST = "receptionist", _("Receptionist")
# AGENT = "agent", _("Agent")
# TECHNICIAN = "technician", _("Technician")
# DRIVER = "driver", _("Driver")
##############################
class StaffTypes(models.TextChoices):
# MANAGER = "manager", _("Manager")
@ -1372,22 +1264,22 @@ class Lead(models.Model):
class Schedule(models.Model):
PURPOSE_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'),
('product_demo', _('Product Demo')),
('follow_up_call', _('Follow-Up Call')),
('contract_discussion', _('Contract Discussion')),
('sales_meeting', _('Sales Meeting')),
('support_call', _('Support Call')),
('other', _('Other')),
]
ScheduledType = [
('Call', 'Call'),
('Meeting', 'Meeting'),
('Email', 'Email'),
('call', _('Call')),
('meeting', _('Meeting')),
('email', _('Email')),
]
ScheduleStatusChoices = [
('Scheduled', 'Scheduled'),
('Completed', 'Completed'),
('Canceled', 'Canceled'),
('scheduled', _('Scheduled')),
('completed', _('Completed')),
('canceled', _('Canceled')),
]
lead = models.ForeignKey(Lead, on_delete=models.CASCADE, related_name='schedules')
customer = models.ForeignKey(CustomerModel, on_delete=models.CASCADE, related_name='schedules',null=True,blank=True)
@ -1593,198 +1485,15 @@ class Vendor(models.Model, LocalizedNameMixin):
return self.name
# class SaleQuotation(models.Model):
# quotation_number = models.CharField(max_length=10, unique=True)
#
# STATUS_CHOICES = [
# ("Draft", _("Draft")),
# ("Approved", _("Approved")),
# ("In Review", _("In Review")),
# ("Paid", _("Paid")),
# ]
# dealer = models.ForeignKey(
# Dealer, on_delete=models.CASCADE, related_name="sales", null=True
# )
# customer = models.ForeignKey(
# Customer,
# on_delete=models.CASCADE,
# related_name="quotations",
# verbose_name=_("Customer"),
# )
# amount = models.DecimalField(
# decimal_places=2,
# default=Decimal("0.00"),
# max_digits=10,
# verbose_name=_("Amount"),
# )
# remarks = models.TextField(blank=True, null=True, verbose_name=_("Remarks"))
# is_approved = models.BooleanField(default=False)
# status = models.CharField(
# max_length=10, choices=STATUS_CHOICES, default="Draft", verbose_name=_("Status")
# )
# created_at = models.DateTimeField(auto_now_add=True, verbose_name=_("Created At"))
# updated_at = models.DateTimeField(auto_now=True, verbose_name=_("Updated At"))
#
# posted = models.BooleanField(default=False)
# payment_id = models.CharField(
# max_length=255, null=True, blank=True, verbose_name=_("Payment ID")
# )
# is_paid = models.BooleanField(default=False)
# date_draft = models.DateTimeField(
# null=True, blank=True, verbose_name=_("Draft Date")
# )
# date_in_review = models.DateTimeField(
# null=True, blank=True, verbose_name=_("In Review Date")
# )
# date_approved = models.DateTimeField(
# null=True, blank=True, verbose_name=_("Approved Date")
# )
# date_paid = models.DateTimeField(null=True, blank=True, verbose_name=_("Paid Date"))
# date_void = models.DateTimeField(null=True, blank=True, verbose_name=_("Void Date"))
# date_canceled = models.DateTimeField(
# null=True, blank=True, verbose_name=_("Canceled Date")
# )
#
# @property
# def total_quantity(self):
# total_quantity = self.quotation_cars.aggregate(total=Sum("quantity"))["total"]
# return total_quantity or 0
#
# @property
# def total(self):
# total = self.quotation_cars.aggregate(
# total_price=Sum(F("car__finances__selling_price") * F("quantity"))
# )
# if not total:
# return 0
# return total["total_price"]
#
# @property
# def total_vat(self):
# if self.total:
# return float(self.total) * 0.15 + float(self.total)
# return 0
# def confirm(self):
# """Confirm the quotation and lock financial details."""
# if self.status != "DRAFT":
# raise ValueError(_("Only draft quotations can be confirmed."))
# self.status = "CONFIRMED"
# self.save()
# def cancel(self):
# """Cancel the quotation."""
# if self.status == "CONFIRMED":
# raise ValueError(_("Cannot cancel a confirmed quotation."))
# self.status = "CANCELED"
# self.save()
# def __str__(self):
# return f"Quotation #{self.quotation_number} for {self.customer}"
#
# @property
# def display_quotation_number(self):
# return f"QN-{self.quotation_number}"
#
# def save(self, *args, **kwargs):
# if not self.quotation_number:
# self.quotation_number = str(next(self._get_quotation_number())).zfill(6)
# super().save(*args, **kwargs)
#
# @classmethod
# def _get_quotation_number(cls):
# last_quotation = cls.objects.all().order_by("id").last()
# if last_quotation:
# last_quotation_number = int(last_quotation.quotation_number)
# else:
# last_quotation_number = 0
# return itertools.count(last_quotation_number + 1)
#
# class SaleQuotationCar(models.Model):
# quotation = models.ForeignKey(
# SaleQuotation,
# on_delete=models.CASCADE,
# related_name="quotation_cars",
# verbose_name=_("Quotation"),
# )
# car = models.ForeignKey(Car, on_delete=models.CASCADE, verbose_name=_("Car"))
# quantity = models.PositiveIntegerField(default=1, verbose_name=_("Quantity"))
#
# @property
# def finance(self):
# return self.car.finances
#
# @property
# def financial_details(self):
# """
# Retrieve financial details dynamically from CarFinance.
# Returns a dictionary with all financial fields for better access.
# """
# car_finance = self.car.finances
# if not car_finance:
# return None
#
# return {
# "selling_price": car_finance.selling_price,
# "administration_fee": car_finance.administration_fee,
# "transportation_fee": car_finance.transportation_fee,
# "custom_card_fee": car_finance.custom_card_fee,
# "registration_fee": car_finance.registration_fee,
# "vat_amount": car_finance.vat_amount,
# # "total_amount": car_finance.total,
# }
#
# @property
# def total(self):
# """
# Calculate total price dynamically based on quantity and selling price.
# """
# if not self.car.finances:
# return Decimal("0.00")
# return self.car.finances.selling_price * self.quantity
#
# @property
# def total_vat(self):
# """
# Calculate total price dynamically based on quantity and selling price.
# """
# if not self.car.finances:
# return Decimal("0.00")
# price = float(self.car.finances.selling_price * self.quantity)
# return (price * 0.15) + price
#
# def __str__(self):
# return f"{self.car} - Quotation #{self.quotation.id}"
#
#
# class SalesOrder(models.Model):
# quotation = models.OneToOneField(
# SaleQuotation,
# on_delete=models.CASCADE,
# related_name="sales_order",
# verbose_name=_("Quotation"),
# )
# created_at = models.DateTimeField(auto_now_add=True, verbose_name=_("Created At"))
# total_amount = models.DecimalField(
# max_digits=14, decimal_places=2, verbose_name=_("Total Amount")
# )
#
# def __str__(self):
# return f"Sales Order #{self.id} from Quotation #{self.quotation.id}"
class Payment(models.Model):
METHOD_CHOICES = [
("cash", _("cash")),
("credit", _("credit")),
("transfer", _("transfer")),
("debit", _("debit")),
("SADAD", _("SADAD")),
("sadad", _("SADAD")),
]
# quotation = models.ForeignKey(
# SaleQuotation, on_delete=models.CASCADE, related_name="payments"
# )
amount = models.DecimalField(
max_digits=10, decimal_places=2, verbose_name=_("amount")
)
@ -1796,19 +1505,13 @@ class Payment(models.Model):
)
payment_date = models.DateField(auto_now_add=True, verbose_name=_("date"))
# def save(self, *args, **kwargs):
# super().save(*args, **kwargs)
# self.quotation.remaining_balance -= self.amount
# if self.quotation.remaining_balance <= 0:
# self.quotation.is_paid = True
# self.quotation.save()
class Meta:
verbose_name = _("payment")
verbose_name_plural = _("payments")
def __str__(self):
return f"Payment of {self.amount} on {self.payment_date} for {self.quotation}"
return f"Payment of {self.amount} on {self.payment_date}"
class Refund(models.Model):
@ -1835,8 +1538,8 @@ class UserActivityLog(models.Model):
timestamp = models.DateTimeField(auto_now_add=True)
class Meta:
verbose_name = "User Activity Log"
verbose_name_plural = "User Activity Logs"
verbose_name = _("User Activity Log")
verbose_name_plural = _("User Activity Logs")
ordering = ["-timestamp"]
def __str__(self):
@ -1863,7 +1566,7 @@ class SaleOrder(models.Model):
('lease', _('Lease')),
("credit_card", _("Credit Card")),
("bank_transfer", _("Bank Transfer")),
("SADAD", _("SADAD")),
("sadad", _("SADAD")),
])
comments = models.TextField(blank=True, null=True)
formatted_order_id = models.CharField(max_length=10, unique=True, editable=False)
@ -2073,7 +1776,8 @@ class PaymentHistory(models.Model):
updated_at = models.DateTimeField(auto_now=True)
class Meta:
verbose_name_plural = "Payment Histories"
verbose_name = _("Payment History")
verbose_name_plural = _("Payment Histories")
ordering = ["-payment_date"]
indexes = [
models.Index(fields=["transaction_id"]),

Binary file not shown.

File diff suppressed because it is too large Load Diff