This commit is contained in:
Marwan Alwali 2025-01-22 16:20:54 +03:00
parent e0bb0b4eda
commit 6354f0c326
8 changed files with 505 additions and 230 deletions

View File

@ -16,6 +16,7 @@ from .models import (
Vendor, Vendor,
Customer, Customer,
Car, Car,
CarTransfer,
CarFinance, CarFinance,
CustomCard, CustomCard,
CarRegistration, CarRegistration,
@ -32,13 +33,12 @@ from .models import (
Staff, Staff,
Opportunity, Priority, Sources, Lead, Activity, Notes, CarModel Opportunity, Priority, Sources, Lead, Activity, Notes, CarModel
) )
from django_ledger.models import ItemModel, InvoiceModel,BillModel from django_ledger.models import ItemModel, InvoiceModel, BillModel
from django.forms import ModelMultipleChoiceField, ValidationError, DateInput from django.forms import ModelMultipleChoiceField, ValidationError, DateInput
from django.utils.translation import gettext_lazy as _ from django.utils.translation import gettext_lazy as _
import django_tables2 as tables import django_tables2 as tables
from django.forms import formset_factory from django.forms import formset_factory
User = get_user_model() User = get_user_model()
@ -121,7 +121,6 @@ class CustomerForm(forms.ModelForm, AddClassMixin):
} }
class CarForm( class CarForm(
forms.ModelForm, forms.ModelForm,
AddClassMixin, AddClassMixin,
@ -242,6 +241,15 @@ class CarLocationForm(forms.ModelForm):
} }
class CarTransferForm(forms.ModelForm):
class Meta:
model = CarTransfer
fields = ["car", "to_dealer", "remarks"]
widgets = {
"remarks": forms.Textarea(attrs={"rows": 2, "class": "form-control"}),
}
# Custom Card Form # Custom Card Form
class CustomCardForm(forms.ModelForm): class CustomCardForm(forms.ModelForm):
custom_date = forms.DateTimeField( custom_date = forms.DateTimeField(
@ -379,7 +387,7 @@ class CarSelectionTable(tables.Table):
template_name = "django_tables2/bootstrap4.html" template_name = "django_tables2/bootstrap4.html"
class WizardForm1(forms.Form): class WizardForm1(forms.Form):
email = forms.EmailField( email = forms.EmailField(
widget=forms.EmailInput( widget=forms.EmailInput(
attrs={ attrs={
@ -561,8 +569,8 @@ class PaymentForm(forms.Form):
label="Payment Method", label="Payment Method",
required=True, required=True,
) )
payment_date = forms.DateField(label="Payment Date",widget=DateInput(attrs={'type': 'date'}), required=True) payment_date = forms.DateField(label="Payment Date", widget=DateInput(attrs={'type': 'date'}), required=True)
def clean_amount(self): def clean_amount(self):
invoice = self.cleaned_data['invoice'] invoice = self.cleaned_data['invoice']
bill = self.cleaned_data['bill'] bill = self.cleaned_data['bill']
@ -573,12 +581,11 @@ class PaymentForm(forms.Form):
if amount <= 0: 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(): if model.is_paid():
raise forms.ValidationError("Invoice is already paid") raise forms.ValidationError("Invoice is already paid")
if amount > model.amount_due: 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 return amount
class EmailForm(forms.Form): class EmailForm(forms.Form):
subject = forms.CharField(max_length=255) subject = forms.CharField(max_length=255)
@ -586,6 +593,7 @@ class EmailForm(forms.Form):
from_email = forms.EmailField() from_email = forms.EmailField()
to_email = forms.EmailField(label="To") to_email = forms.EmailField(label="To")
class LeadForm(forms.ModelForm): class LeadForm(forms.ModelForm):
class Meta: class Meta:
model = Lead model = Lead
@ -615,6 +623,7 @@ class NoteForm(forms.ModelForm):
model = Notes model = Notes
fields = ['note'] fields = ['note']
class ActivityForm(forms.ModelForm): class ActivityForm(forms.ModelForm):
class Meta: class Meta:
model = Activity model = Activity
@ -630,16 +639,17 @@ class OpportunityForm(forms.ModelForm):
class InvoiceModelCreateForm(InvoiceModelCreateFormBase): class InvoiceModelCreateForm(InvoiceModelCreateFormBase):
def __init__(self, *args, **kwargs): def __init__(self, *args, **kwargs):
super().__init__(*args, **kwargs) super().__init__(*args, **kwargs)
self.fields['cash_account'].widget = forms.HiddenInput() self.fields['cash_account'].widget = forms.HiddenInput()
self.fields['prepaid_account'].widget = forms.HiddenInput() self.fields['prepaid_account'].widget = forms.HiddenInput()
self.fields['unearned_account'].widget = forms.HiddenInput() self.fields['unearned_account'].widget = forms.HiddenInput()
self.fields['date_draft'] = forms.DateField(widget=DateInput(attrs={'type': 'date'})) self.fields['date_draft'] = forms.DateField(widget=DateInput(attrs={'type': 'date'}))
class BillModelCreateForm(BillModelCreateFormBase): class BillModelCreateForm(BillModelCreateFormBase):
def __init__(self, *args, **kwargs): def __init__(self, *args, **kwargs):
super().__init__(*args, **kwargs) super().__init__(*args, **kwargs)
self.fields['cash_account'].widget = forms.HiddenInput() self.fields['cash_account'].widget = forms.HiddenInput()
self.fields['prepaid_account'].widget = forms.HiddenInput() self.fields['prepaid_account'].widget = forms.HiddenInput()
self.fields['unearned_account'].widget = forms.HiddenInput() self.fields['unearned_account'].widget = forms.HiddenInput()

View File

@ -1,16 +0,0 @@
# Generated by Django 5.1.5 on 2025-01-21 12:57
from django.db import migrations
class Migration(migrations.Migration):
dependencies = [
('inventory', '0006_cartransferlog_delete_invoicemodelbase'),
]
operations = [
migrations.DeleteModel(
name='CarTransferLog',
),
]

View File

@ -17,7 +17,6 @@ from django_ledger.models import (
UnitOfMeasureModel, UnitOfMeasureModel,
CustomerModel, CustomerModel,
ItemModelQuerySet, ItemModelQuerySet,
) )
from django.db.models import Sum from django.db.models import Sum
from decimal import Decimal, InvalidOperation from decimal import Decimal, InvalidOperation
@ -36,41 +35,77 @@ from django.contrib.contenttypes.models import ContentType
class DealerUserManager(UserManager): class DealerUserManager(UserManager):
def create_user_with_dealer(self, email, password, dealer_name, arabic_name, crn, vrn, address, **extra_fields): def create_user_with_dealer(
user = self.create_user(username=email, email=email, password=password, **extra_fields) self,
Dealer.objects.create(user=user, name=dealer_name, arabic_name=arabic_name, crn=crn, vrn=vrn, address=address, email,
**extra_fields) password,
dealer_name,
arabic_name,
crn,
vrn,
address,
**extra_fields,
):
user = self.create_user(
username=email, email=email, password=password, **extra_fields
)
Dealer.objects.create(
user=user,
name=dealer_name,
arabic_name=arabic_name,
crn=crn,
vrn=vrn,
address=address,
**extra_fields,
)
return user return user
class StaffUserManager(UserManager): class StaffUserManager(UserManager):
def create_user_with_staff(self, email, password, name, arabic_name, phone_number, staff_type, **extra_fields): def create_user_with_staff(
user = self.create_user(username=email, email=email, password=password, **extra_fields) self,
Staff.objects.create(user=user, name=name, arabic_name=arabic_name, phone_number=phone_number, email,
staff_type=staff_type, **extra_fields) password,
name,
arabic_name,
phone_number,
staff_type,
**extra_fields,
):
user = self.create_user(
username=email, email=email, password=password, **extra_fields
)
Staff.objects.create(
user=user,
name=name,
arabic_name=arabic_name,
phone_number=phone_number,
staff_type=staff_type,
**extra_fields,
)
return user return user
class UnitOfMeasure(models.TextChoices): class UnitOfMeasure(models.TextChoices):
EACH = 'EA', 'Each' EACH = "EA", "Each"
PAIR = 'PR', 'Pair' PAIR = "PR", "Pair"
SET = 'SET', 'Set' SET = "SET", "Set"
GALLON = 'GAL', 'Gallon' GALLON = "GAL", "Gallon"
LITER = 'L', 'Liter' LITER = "L", "Liter"
METER = 'M', 'Meter' METER = "M", "Meter"
KILOGRAM = 'KG', 'Kilogram' KILOGRAM = "KG", "Kilogram"
HOUR = 'HR', 'Hour' HOUR = "HR", "Hour"
BOX = 'BX', 'Box' BOX = "BX", "Box"
ROLL = 'RL', 'Roll' ROLL = "RL", "Roll"
PACKAGE = 'PKG', 'Package' PACKAGE = "PKG", "Package"
DOZEN = 'DZ', 'Dozen' DOZEN = "DZ", "Dozen"
SQUARE_METER = 'SQ_M', 'Square Meter' SQUARE_METER = "SQ_M", "Square Meter"
PIECE = 'PC', 'Piece' PIECE = "PC", "Piece"
BUNDLE = 'BDL', 'Bundle' BUNDLE = "BDL", "Bundle"
class VatRate(models.Model): class VatRate(models.Model):
rate = models.DecimalField(max_digits=5, decimal_places=2, default=Decimal('0.15')) rate = models.DecimalField(max_digits=5, decimal_places=2, default=Decimal("0.15"))
is_active = models.BooleanField(default=True) is_active = models.BooleanField(default=True)
created_at = models.DateTimeField(auto_now_add=True) created_at = models.DateTimeField(auto_now_add=True)
@ -79,19 +114,19 @@ class VatRate(models.Model):
class CarType(models.IntegerChoices): class CarType(models.IntegerChoices):
CAR = 1, _('Car') CAR = 1, _("Car")
LIGHT_COMMERCIAL = 2, _('Light Commercial') LIGHT_COMMERCIAL = 2, _("Light Commercial")
HEAVY_DUTY_TRACTORS = 3, _('Heavy-Duty Tractors') HEAVY_DUTY_TRACTORS = 3, _("Heavy-Duty Tractors")
TRAILERS = 4, _('Trailers') TRAILERS = 4, _("Trailers")
MEDIUM_TRUCKS = 5, _('Medium Trucks') MEDIUM_TRUCKS = 5, _("Medium Trucks")
BUSES = 6, _('Buses') BUSES = 6, _("Buses")
MOTORCYCLES = 20, _('Motorcycles') MOTORCYCLES = 20, _("Motorcycles")
BUGGY = 21, _('Buggy') BUGGY = 21, _("Buggy")
MOTO_ATV = 22, _('Moto ATV') MOTO_ATV = 22, _("Moto ATV")
SCOOTERS = 23, _('Scooters') SCOOTERS = 23, _("Scooters")
KARTING = 24, _('Karting') KARTING = 24, _("Karting")
ATV = 25, _('ATV') ATV = 25, _("ATV")
SNOWMOBILES = 26, _('Snowmobiles') SNOWMOBILES = 26, _("Snowmobiles")
class CarMake(models.Model, LocalizedNameMixin): class CarMake(models.Model, LocalizedNameMixin):
@ -124,7 +159,9 @@ class CarModel(models.Model, LocalizedNameMixin):
class CarSerie(models.Model, LocalizedNameMixin): class CarSerie(models.Model, LocalizedNameMixin):
id_car_serie = models.AutoField(primary_key=True) id_car_serie = models.AutoField(primary_key=True)
id_car_model = models.ForeignKey(CarModel, models.DO_NOTHING, db_column="id_car_model") id_car_model = models.ForeignKey(
CarModel, models.DO_NOTHING, db_column="id_car_model"
)
name = models.CharField(max_length=255, blank=True, null=True) name = models.CharField(max_length=255, blank=True, null=True)
arabic_name = models.CharField(max_length=255, blank=True, null=True) arabic_name = models.CharField(max_length=255, blank=True, null=True)
year_begin = models.IntegerField(blank=True, null=True) year_begin = models.IntegerField(blank=True, null=True)
@ -140,7 +177,9 @@ class CarSerie(models.Model, LocalizedNameMixin):
class CarTrim(models.Model, LocalizedNameMixin): class CarTrim(models.Model, LocalizedNameMixin):
id_car_trim = models.AutoField(primary_key=True) id_car_trim = models.AutoField(primary_key=True)
id_car_serie = models.ForeignKey(CarSerie, models.DO_NOTHING, db_column="id_car_serie") id_car_serie = models.ForeignKey(
CarSerie, models.DO_NOTHING, db_column="id_car_serie"
)
name = models.CharField(max_length=255, blank=True, null=True) name = models.CharField(max_length=255, blank=True, null=True)
arabic_name = models.CharField(max_length=255, blank=True, null=True) arabic_name = models.CharField(max_length=255, blank=True, null=True)
start_production_year = models.IntegerField(blank=True, null=True) start_production_year = models.IntegerField(blank=True, null=True)
@ -171,7 +210,9 @@ class CarSpecification(models.Model, LocalizedNameMixin):
id_car_specification = models.AutoField(primary_key=True) id_car_specification = models.AutoField(primary_key=True)
name = models.CharField(max_length=255) name = models.CharField(max_length=255)
arabic_name = models.CharField(max_length=255) arabic_name = models.CharField(max_length=255)
id_parent = models.ForeignKey("self", models.DO_NOTHING, db_column="id_parent", blank=True, null=True) id_parent = models.ForeignKey(
"self", models.DO_NOTHING, db_column="id_parent", blank=True, null=True
)
def __str__(self): def __str__(self):
return self.name return self.name
@ -183,7 +224,9 @@ class CarSpecification(models.Model, LocalizedNameMixin):
class CarSpecificationValue(models.Model): class CarSpecificationValue(models.Model):
id_car_specification_value = models.AutoField(primary_key=True) id_car_specification_value = models.AutoField(primary_key=True)
id_car_trim = models.ForeignKey(CarTrim, models.DO_NOTHING, db_column="id_car_trim") id_car_trim = models.ForeignKey(CarTrim, models.DO_NOTHING, db_column="id_car_trim")
id_car_specification = models.ForeignKey(CarSpecification, models.DO_NOTHING, db_column="id_car_specification") id_car_specification = models.ForeignKey(
CarSpecification, models.DO_NOTHING, db_column="id_car_specification"
)
value = models.CharField(max_length=500) value = models.CharField(max_length=500)
unit = models.CharField(max_length=255, blank=True, null=True) unit = models.CharField(max_length=255, blank=True, null=True)
@ -198,7 +241,9 @@ class CarOption(models.Model, LocalizedNameMixin):
id_car_option = models.AutoField(primary_key=True) id_car_option = models.AutoField(primary_key=True)
name = models.CharField(max_length=255, blank=True, null=True) name = models.CharField(max_length=255, blank=True, null=True)
arabic_name = models.CharField(max_length=255, blank=True, null=True) arabic_name = models.CharField(max_length=255, blank=True, null=True)
id_parent = models.ForeignKey("self", models.DO_NOTHING, db_column="id_parent", blank=True, null=True) id_parent = models.ForeignKey(
"self", models.DO_NOTHING, db_column="id_parent", blank=True, null=True
)
def __str__(self): def __str__(self):
return self.name return self.name
@ -209,8 +254,12 @@ class CarOption(models.Model, LocalizedNameMixin):
class CarOptionValue(models.Model): class CarOptionValue(models.Model):
id_car_option_value = models.AutoField(primary_key=True) id_car_option_value = models.AutoField(primary_key=True)
id_car_option = models.ForeignKey(CarOption, models.DO_NOTHING, db_column="id_car_option") id_car_option = models.ForeignKey(
id_car_equipment = models.ForeignKey(CarEquipment, models.DO_NOTHING, db_column="id_car_equipment") CarOption, models.DO_NOTHING, db_column="id_car_option"
)
id_car_equipment = models.ForeignKey(
CarEquipment, models.DO_NOTHING, db_column="id_car_equipment"
)
value = models.CharField(max_length=500) value = models.CharField(max_length=500)
unit = models.CharField(max_length=255, blank=True, null=True) unit = models.CharField(max_length=255, blank=True, null=True)
is_base = models.IntegerField() is_base = models.IntegerField()
@ -222,13 +271,22 @@ class CarOptionValue(models.Model):
verbose_name = "Option Value" verbose_name = "Option Value"
class CarTransferStatusChoices(models.TextChoices):
draft = "draft", _("Draft")
approved = "approved", _("Approved")
pending = "pending", _("Pending")
accepted = "accepted", _("Accepted")
success = "success", _("Success")
reject = "reject", _("Reject")
class CarStatusChoices(models.TextChoices): class CarStatusChoices(models.TextChoices):
AVAILABLE = "available", _("Available") AVAILABLE = "available", _("Available")
SOLD = "sold", _("Sold") SOLD = "sold", _("Sold")
HOLD = "hold", _("Hold") HOLD = "hold", _("Hold")
DAMAGED = "damaged", _("Damaged") DAMAGED = "damaged", _("Damaged")
RESERVED = "reserved", _("Reserved") RESERVED = "reserved", _("Reserved")
TRANSFER = "transfer", _("Transfer")
class CarStockTypeChoices(models.TextChoices): class CarStockTypeChoices(models.TextChoices):
NEW = "new", _("New") NEW = "new", _("New")
@ -239,11 +297,25 @@ class AdditionalServices(models.Model, LocalizedNameMixin):
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"))
description = models.TextField(verbose_name=_("Description")) description = models.TextField(verbose_name=_("Description"))
price = models.DecimalField(max_digits=14, decimal_places=2, verbose_name=_("Price")) price = models.DecimalField(
max_digits=14, decimal_places=2, verbose_name=_("Price")
)
taxable = models.BooleanField(default=False, verbose_name=_("taxable")) taxable = models.BooleanField(default=False, verbose_name=_("taxable"))
uom = models.CharField(max_length=10, choices=UnitOfMeasure.choices, verbose_name=_("Unit of Measurement")) uom = models.CharField(
dealer = models.ForeignKey("Dealer", on_delete=models.CASCADE, verbose_name=_("Dealer")) max_length=10,
item = models.OneToOneField(ItemModel, on_delete=models.CASCADE, verbose_name=_("Item"), null=True, blank=True) choices=UnitOfMeasure.choices,
verbose_name=_("Unit of Measurement"),
)
dealer = models.ForeignKey(
"Dealer", on_delete=models.CASCADE, verbose_name=_("Dealer")
)
item = models.OneToOneField(
ItemModel,
on_delete=models.CASCADE,
verbose_name=_("Item"),
null=True,
blank=True,
)
class Meta: class Meta:
verbose_name = _("Additional Services") verbose_name = _("Additional Services")
@ -330,15 +402,71 @@ class Car(models.Model):
active_reservations = self.reservations.filter(reserved_until__gt=now()) active_reservations = self.reservations.filter(reserved_until__gt=now())
return active_reservations.exists() return active_reservations.exists()
def get_transfer(self):
return self.transfer_logs.filter(active=True).first()
@property @property
def get_car_group(self): def get_car_group(self):
return f"{self.id_car_make.get_local_name} {self.id_car_model.get_local_name}" return f"{self.id_car_make.get_local_name} {self.id_car_model.get_local_name}"
class CarTransfer(models.Model):
car = models.ForeignKey(
"Car",
on_delete=models.CASCADE,
related_name="transfer_logs",
verbose_name=_("Car"),
)
from_dealer = models.ForeignKey(
"Dealer",
on_delete=models.CASCADE,
related_name="transfers_out",
verbose_name=_("From Dealer"),
)
to_dealer = models.ForeignKey(
"Dealer",
on_delete=models.CASCADE,
related_name="transfers_in",
verbose_name=_("To Dealer"),
)
transfer_date = models.DateTimeField(
auto_now_add=True, verbose_name=_("Transfer Date")
)
quantity = models.IntegerField(verbose_name=_("Quantity"),default=1)
remarks = models.TextField(blank=True, null=True, verbose_name=_("Remarks"))
status = models.CharField(
CarTransferStatusChoices.choices,
max_length=10,
default=CarTransferStatusChoices.draft,
)
is_approved = models.BooleanField(default=False)
active = models.BooleanField(default=True)
created_at = models.DateTimeField(auto_now_add=True, verbose_name=_("Created At"))
updated_at = models.DateTimeField(auto_now=True, verbose_name=_("Updated At"))
@property
def total_price(self):
return self.quantity * self.car.finances.cost_price
class Meta:
verbose_name = _("Car Transfer Log")
verbose_name_plural = _("Car Transfer Logs")
def __str__(self):
return f"{self.car} Transfer car from {self.from_dealer} to {self.to_dealer}"
class CarReservation(models.Model): class CarReservation(models.Model):
car = models.ForeignKey('Car', on_delete=models.CASCADE, related_name='reservations', verbose_name=_("Car")) car = models.ForeignKey(
reserved_by = models.ForeignKey(User, on_delete=models.CASCADE, related_name='reservations', "Car",
verbose_name=_("Reserved By")) on_delete=models.CASCADE,
related_name="reservations",
verbose_name=_("Car"),
)
reserved_by = models.ForeignKey(
User,
on_delete=models.CASCADE,
related_name="reservations",
verbose_name=_("Reserved By"),
)
reserved_at = models.DateTimeField(auto_now_add=True, verbose_name=_("Reserved At")) reserved_at = models.DateTimeField(auto_now_add=True, verbose_name=_("Reserved At"))
reserved_until = models.DateTimeField(verbose_name=_("Reserved Until")) reserved_until = models.DateTimeField(verbose_name=_("Reserved Until"))
@ -347,25 +475,37 @@ class CarReservation(models.Model):
return self.reserved_until > now() return self.reserved_until > now()
class Meta: class Meta:
unique_together = ('car', 'reserved_until') unique_together = ("car", "reserved_until")
ordering = ['-reserved_at'] ordering = ["-reserved_at"]
verbose_name = _("Car Reservation") verbose_name = _("Car Reservation")
verbose_name_plural = _("Car Reservations") verbose_name_plural = _("Car Reservations")
# Car Finance Model # Car Finance Model
class CarFinance(models.Model): class CarFinance(models.Model):
additional_services = models.ManyToManyField(AdditionalServices, related_name="additional_finances", blank=True) additional_services = models.ManyToManyField(
car = models.OneToOneField(Car, on_delete=models.CASCADE, related_name='finances') AdditionalServices, related_name="additional_finances", blank=True
cost_price = models.DecimalField(max_digits=14, decimal_places=2, verbose_name=_("Cost Price")) )
selling_price = models.DecimalField(max_digits=14, decimal_places=2, verbose_name=_("Selling Price")) car = models.OneToOneField(Car, on_delete=models.CASCADE, related_name="finances")
discount_amount = models.DecimalField(max_digits=14, decimal_places=2, verbose_name=_("Discount Amount"), cost_price = models.DecimalField(
default=Decimal('0.00')) max_digits=14, decimal_places=2, verbose_name=_("Cost Price")
)
selling_price = models.DecimalField(
max_digits=14, decimal_places=2, verbose_name=_("Selling Price")
)
discount_amount = models.DecimalField(
max_digits=14,
decimal_places=2,
verbose_name=_("Discount Amount"),
default=Decimal("0.00"),
)
@property @property
def total(self): def total(self):
if self.additional_services.count() > 0: if self.additional_services.count() > 0:
return self.selling_price + sum(x.price for x in self.additional_services.all()) return self.selling_price + sum(
x.price for x in self.additional_services.all()
)
return self.selling_price return self.selling_price
@property @property
@ -382,8 +522,8 @@ class CarFinance(models.Model):
def vat_amount(self): def vat_amount(self):
vat = VatRate.objects.filter(is_active=True).first() vat = VatRate.objects.filter(is_active=True).first()
if vat: if vat:
return (self.total_discount * Decimal(vat.rate)).quantize(Decimal('0.01')) return (self.total_discount * Decimal(vat.rate)).quantize(Decimal("0.01"))
return Decimal('0.00') return Decimal("0.00")
@property @property
def revenue(self): def revenue(self):
@ -452,7 +592,12 @@ class CarColors(models.Model):
class CustomCard(models.Model): class CustomCard(models.Model):
car = models.OneToOneField(Car, on_delete=models.CASCADE, related_name='custom_cards', verbose_name=_("Car")) car = models.OneToOneField(
Car,
on_delete=models.CASCADE,
related_name="custom_cards",
verbose_name=_("Car"),
)
custom_number = models.CharField(max_length=255, verbose_name=_("Custom Number")) custom_number = models.CharField(max_length=255, verbose_name=_("Custom Number"))
custom_date = models.DateField(verbose_name=_("Custom Date")) custom_date = models.DateField(verbose_name=_("Custom Date"))
@ -466,39 +611,30 @@ class CustomCard(models.Model):
class CarLocation(models.Model): class CarLocation(models.Model):
car = models.OneToOneField( car = models.OneToOneField(
Car, Car, on_delete=models.CASCADE, related_name="location", verbose_name=_("Car")
on_delete=models.CASCADE,
related_name='location',
verbose_name=_("Car")
) )
owner = models.ForeignKey( owner = models.ForeignKey(
'Dealer', "Dealer",
on_delete=models.CASCADE, on_delete=models.CASCADE,
related_name='owned_cars', related_name="owned_cars",
verbose_name=_("Owner"), verbose_name=_("Owner"),
help_text=_("Dealer who owns the car.") help_text=_("Dealer who owns the car."),
) )
showroom = models.ForeignKey( showroom = models.ForeignKey(
'Dealer', "Dealer",
on_delete=models.CASCADE, on_delete=models.CASCADE,
related_name='showroom_cars', related_name="showroom_cars",
verbose_name=_("Showroom"), verbose_name=_("Showroom"),
help_text=_("Dealer where the car is displayed (can be the owner).") help_text=_("Dealer where the car is displayed (can be the owner)."),
) )
description = models.TextField( description = models.TextField(
blank=True, blank=True,
null=True, null=True,
verbose_name=_("Description"), verbose_name=_("Description"),
help_text=_("Optional description about the showroom placement.") help_text=_("Optional description about the showroom placement."),
)
created_at = models.DateTimeField(
auto_now_add=True,
verbose_name=_("Created At")
)
updated_at = models.DateTimeField(
auto_now=True,
verbose_name=_("Last Updated")
) )
created_at = models.DateTimeField(auto_now_add=True, verbose_name=_("Created At"))
updated_at = models.DateTimeField(auto_now=True, verbose_name=_("Last Updated"))
class Meta: class Meta:
verbose_name = _("Car Location") verbose_name = _("Car Location")
@ -525,16 +661,14 @@ class CarRegistration(models.Model):
text1 = models.CharField(max_length=1, verbose_name=_("Text 1")) text1 = models.CharField(max_length=1, verbose_name=_("Text 1"))
text2 = models.CharField(max_length=1, verbose_name=_("Text 2")) text2 = models.CharField(max_length=1, verbose_name=_("Text 2"))
text3 = models.CharField(max_length=1, verbose_name=_("Text 3")) text3 = models.CharField(max_length=1, verbose_name=_("Text 3"))
# registration_type =
registration_date = models.DateTimeField(verbose_name=_("Registration Date")) registration_date = models.DateTimeField(verbose_name=_("Registration Date"))
registration_expiry_date = models.DateTimeField(verbose_name=_("Registration Expiry Date"))
class Meta: class Meta:
verbose_name = _("Registration") verbose_name = _("Registration")
verbose_name_plural = _("Registrations") verbose_name_plural = _("Registrations")
def __str__(self): def __str__(self):
return f"{self.text1} {self.text2} {self.text3} - {self.plate_number}" return f"{self.plate_number} - {self.text1} {self.text2} {self.text3}"
# TimestampedModel Abstract Class # TimestampedModel Abstract Class
@ -547,19 +681,27 @@ class TimestampedModel(models.Model):
class Subscription(models.Model): class Subscription(models.Model):
plan = models.ForeignKey("SubscriptionPlan", on_delete=models.CASCADE, related_name="subscriptions") plan = models.ForeignKey(
"SubscriptionPlan", on_delete=models.CASCADE, related_name="subscriptions"
)
start_date = models.DateField(help_text="Date when the subscription starts") start_date = models.DateField(help_text="Date when the subscription starts")
end_date = models.DateField(help_text="Date when the subscription ends") end_date = models.DateField(help_text="Date when the subscription ends")
users = models.ManyToManyField(User, through='SubscriptionUser') # many-to-many relationship with User model users = models.ManyToManyField(
User, through="SubscriptionUser"
) # many-to-many relationship with User model
is_active = models.BooleanField(default=True) is_active = models.BooleanField(default=True)
billing_cycle = models.CharField( billing_cycle = models.CharField(
max_length=10, max_length=10,
choices=[('monthly', 'Monthly'), ('annual', 'Annual')], choices=[("monthly", "Monthly"), ("annual", "Annual")],
default='monthly', default="monthly",
help_text="Billing cycle for the subscription" 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"
) )
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: class Meta:
verbose_name = _("Subscription") verbose_name = _("Subscription")
@ -586,18 +728,30 @@ class SubscriptionUser(models.Model):
class SubscriptionPlan(models.Model): class SubscriptionPlan(models.Model):
name = models.CharField(max_length=100, unique=True, help_text=_("Name of the subscription plan")) name = models.CharField(
max_length=100, unique=True, help_text=_("Name of the subscription plan")
)
description = models.TextField() description = models.TextField()
price = models.DecimalField(max_digits=10, decimal_places=2) price = models.DecimalField(max_digits=10, decimal_places=2)
max_users = models.PositiveIntegerField(help_text=_("Maximum number of users allowed"), default=1) max_users = models.PositiveIntegerField(
max_inventory_size = models.PositiveIntegerField(help_text=_("Maximum number of cars in inventory"), default=50) 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( support_level = models.CharField(
max_length=50, max_length=50,
choices=[('basic', 'Basic Support'), ('priority', 'Priority Support'), ('dedicated', 'Dedicated Support')], choices=[
default='basic', ("basic", "Basic Support"),
help_text="Level of support provided" ("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")
) )
custom_features = models.JSONField(blank=True, null=True, help_text=_("Additional features specific to this plan"))
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)
@ -611,26 +765,27 @@ class SubscriptionPlan(models.Model):
class Dealer(models.Model, LocalizedNameMixin): class Dealer(models.Model, LocalizedNameMixin):
user = models.OneToOneField(User, on_delete=models.CASCADE, related_name="dealer") user = models.OneToOneField(User, on_delete=models.CASCADE, related_name="dealer")
crn = models.CharField(max_length=10, crn = models.CharField(
verbose_name=_("Commercial Registration Number") max_length=10,
, null=True verbose_name=_("Commercial Registration Number"),
, blank=True) null=True,
vrn = models.CharField(max_length=15, blank=True,
verbose_name=_("VAT Registration Number"), )
null=True, vrn = models.CharField(
blank=True) max_length=15, verbose_name=_("VAT Registration Number"), 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"))
phone_number = PhoneNumberField(region="SA", verbose_name=_("Phone Number")) phone_number = PhoneNumberField(region="SA", verbose_name=_("Phone Number"))
address = models.CharField(max_length=200, address = models.CharField(
blank=True, max_length=200, blank=True, null=True, verbose_name=_("Address")
null=True, )
verbose_name=_("Address")) logo = models.ImageField(
logo = models.ImageField(upload_to="logos/users", upload_to="logos/users", blank=True, null=True, verbose_name=_("Logo")
blank=True, )
null=True, entity = models.ForeignKey(
verbose_name=_("Logo")) EntityModel, on_delete=models.SET_NULL, null=True, blank=True
entity = models.ForeignKey(EntityModel, on_delete=models.SET_NULL, null=True, blank=True) )
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"))
@ -705,7 +860,9 @@ class Staff(models.Model, LocalizedNameMixin):
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"))
phone_number = PhoneNumberField(region="SA", verbose_name=_("Phone Number")) phone_number = PhoneNumberField(region="SA", verbose_name=_("Phone Number"))
staff_type = models.CharField(choices=StaffTypes.choices, max_length=255, verbose_name=_("Staff Type")) staff_type = models.CharField(
choices=StaffTypes.choices, max_length=255, verbose_name=_("Staff Type")
)
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"))
@ -795,17 +952,33 @@ class Priority(models.TextChoices):
class Customer(models.Model): class Customer(models.Model):
dealer = models.ForeignKey(Dealer, on_delete=models.CASCADE, related_name="customers") dealer = models.ForeignKey(
title = models.CharField(choices=Title.choices, default=Title.NA, max_length=10, verbose_name=_("Title")) Dealer, on_delete=models.CASCADE, related_name="customers"
)
title = models.CharField(
choices=Title.choices, default=Title.NA, max_length=10, verbose_name=_("Title")
)
first_name = models.CharField(max_length=50, verbose_name=_("First Name")) first_name = models.CharField(max_length=50, verbose_name=_("First Name"))
middle_name = models.CharField(max_length=50, blank=True, null=True, verbose_name=_("Middle Name")) middle_name = models.CharField(
max_length=50, blank=True, null=True, verbose_name=_("Middle Name")
)
last_name = models.CharField(max_length=50, verbose_name=_("Last Name")) last_name = models.CharField(max_length=50, verbose_name=_("Last Name"))
gender = models.CharField(choices=[('m', _('Male')), ('f', _('Female'))], max_length=1, verbose_name=_("Gender")) gender = models.CharField(
choices=[("m", _("Male")), ("f", _("Female"))],
max_length=1,
verbose_name=_("Gender"),
)
dob = models.DateField(verbose_name=_("Date of Birth")) dob = models.DateField(verbose_name=_("Date of Birth"))
email = models.EmailField(unique=True, verbose_name=_("Email")) email = models.EmailField(unique=True, verbose_name=_("Email"))
national_id = models.CharField(max_length=10, unique=True, verbose_name=_("National ID")) national_id = models.CharField(
phone_number = PhoneNumberField(region="SA", unique=True, verbose_name=_("Phone Number")) max_length=10, unique=True, verbose_name=_("National ID")
address = models.CharField(max_length=200, blank=True, null=True, verbose_name=_("Address")) )
phone_number = PhoneNumberField(
region="SA", unique=True, verbose_name=_("Phone Number")
)
address = models.CharField(
max_length=200, blank=True, null=True, verbose_name=_("Address")
)
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"))
@ -823,14 +996,22 @@ class Customer(models.Model):
class Organization(models.Model, LocalizedNameMixin): class Organization(models.Model, LocalizedNameMixin):
dealer = models.ForeignKey(Dealer, on_delete=models.CASCADE, related_name='organizations') dealer = models.ForeignKey(
Dealer, on_delete=models.CASCADE, related_name="organizations"
)
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(max_length=15, verbose_name=_("Commercial Registration Number")) crn = models.CharField(
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"))
phone_number = PhoneNumberField(region='SA', verbose_name=_("Phone Number")) phone_number = PhoneNumberField(region="SA", verbose_name=_("Phone Number"))
address = models.CharField(max_length=200, blank=True, null=True, verbose_name=_("Address")) address = models.CharField(
logo = models.ImageField(upload_to="logos", blank=True, null=True, verbose_name=_("Logo")) max_length=200, blank=True, null=True, verbose_name=_("Address")
)
logo = models.ImageField(
upload_to="logos", blank=True, null=True, verbose_name=_("Logo")
)
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"))
@ -843,14 +1024,20 @@ class Organization(models.Model, LocalizedNameMixin):
class Representative(models.Model, LocalizedNameMixin): class Representative(models.Model, LocalizedNameMixin):
dealer = models.ForeignKey(Dealer, on_delete=models.CASCADE, related_name='representatives') dealer = models.ForeignKey(
Dealer, on_delete=models.CASCADE, related_name="representatives"
)
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"))
id_number = models.CharField(max_length=10, unique=True, verbose_name=_("ID Number")) id_number = models.CharField(
phone_number = PhoneNumberField(region='SA', verbose_name=_("Phone Number")) max_length=10, unique=True, verbose_name=_("ID Number")
)
phone_number = PhoneNumberField(region="SA", verbose_name=_("Phone Number"))
email = models.EmailField(max_length=255, verbose_name=_("Email Address")) email = models.EmailField(max_length=255, verbose_name=_("Email Address"))
address = models.CharField(max_length=200, blank=True, null=True, verbose_name=_("Address")) address = models.CharField(
organization = models.ManyToManyField(Organization, related_name='representatives') max_length=200, blank=True, null=True, verbose_name=_("Address")
)
organization = models.ManyToManyField(Organization, related_name="representatives")
class Meta: class Meta:
verbose_name = _("Representative") verbose_name = _("Representative")
@ -862,21 +1049,57 @@ class Representative(models.Model, LocalizedNameMixin):
class Lead(models.Model): class Lead(models.Model):
dealer = models.ForeignKey(Dealer, on_delete=models.CASCADE, related_name="leads") dealer = models.ForeignKey(Dealer, on_delete=models.CASCADE, related_name="leads")
customer = models.ForeignKey(Customer, on_delete=models.CASCADE, related_name="leads") customer = models.ForeignKey(
id_car_make = models.ForeignKey(CarMake, on_delete=models.DO_NOTHING, blank=True, null=True, verbose_name=_("Make")) Customer, on_delete=models.CASCADE, related_name="leads"
id_car_model = models.ForeignKey(CarModel, on_delete=models.DO_NOTHING, blank=True, null=True, )
verbose_name=_("Model")) id_car_make = models.ForeignKey(
year = models.PositiveSmallIntegerField(verbose_name=_("Year"), blank=True, null=True) CarMake,
source = models.CharField(max_length=50, choices=Sources.choices, verbose_name=_("Source")) on_delete=models.DO_NOTHING,
channel = models.CharField(max_length=50, choices=Channel.choices, verbose_name=_("Channel")) blank=True,
null=True,
verbose_name=_("Make"),
)
id_car_model = models.ForeignKey(
CarModel,
on_delete=models.DO_NOTHING,
blank=True,
null=True,
verbose_name=_("Model"),
)
year = models.PositiveSmallIntegerField(
verbose_name=_("Year"), blank=True, null=True
)
source = models.CharField(
max_length=50, choices=Sources.choices, verbose_name=_("Source")
)
channel = models.CharField(
max_length=50, choices=Channel.choices, verbose_name=_("Channel")
)
city = models.CharField(max_length=50, verbose_name=_("City")) city = models.CharField(max_length=50, verbose_name=_("City"))
staff = models.ForeignKey(Staff, on_delete=models.SET_NULL, blank=True, null=True, related_name="assigned", staff = models.ForeignKey(
verbose_name=_("Assigned")) Staff,
priority = models.CharField(max_length=10, choices=Priority.choices, default=Priority.MEDIUM, on_delete=models.SET_NULL,
verbose_name=_("Priority")) blank=True,
status = models.CharField(max_length=50, choices=Status.choices, verbose_name=_("Status"), db_index=True, null=True,
default=Status.NEW) related_name="assigned",
created = models.DateTimeField(auto_now_add=True, verbose_name=_("Created"), db_index=True) verbose_name=_("Assigned"),
)
priority = models.CharField(
max_length=10,
choices=Priority.choices,
default=Priority.MEDIUM,
verbose_name=_("Priority"),
)
status = models.CharField(
max_length=50,
choices=Status.choices,
verbose_name=_("Status"),
db_index=True,
default=Status.NEW,
)
created = models.DateTimeField(
auto_now_add=True, verbose_name=_("Created"), db_index=True
)
updated = models.DateTimeField(auto_now=True, verbose_name=_("Updated")) updated = models.DateTimeField(auto_now=True, verbose_name=_("Updated"))
class Meta: class Meta:
@ -884,14 +1107,22 @@ class Lead(models.Model):
verbose_name_plural = _("Leads") verbose_name_plural = _("Leads")
def __str__(self): def __str__(self):
return self.customer.get_full_name return f"{self.first_name} {self.last_name}"
class LeadStatusHistory(models.Model): class LeadStatusHistory(models.Model):
lead = models.ForeignKey(Lead, on_delete=models.CASCADE, related_name="status_history") lead = models.ForeignKey(
old_status = models.CharField(max_length=50, choices=Status.choices, verbose_name=_("Old Status")) Lead, on_delete=models.CASCADE, related_name="status_history"
new_status = models.CharField(max_length=50, choices=Status.choices, verbose_name=_("New Status")) )
changed_by = models.ForeignKey(Staff, on_delete=models.DO_NOTHING, related_name="status_changes") old_status = models.CharField(
max_length=50, choices=Status.choices, verbose_name=_("Old Status")
)
new_status = models.CharField(
max_length=50, choices=Status.choices, verbose_name=_("New Status")
)
changed_by = models.ForeignKey(
Staff, on_delete=models.DO_NOTHING, related_name="status_changes"
)
changed_at = models.DateTimeField(auto_now_add=True, verbose_name=_("Changed At")) changed_at = models.DateTimeField(auto_now_add=True, verbose_name=_("Changed At"))
class Meta: class Meta:
@ -908,13 +1139,31 @@ def validate_probability(value):
class Opportunity(models.Model): class Opportunity(models.Model):
dealer = models.ForeignKey(Dealer, on_delete=models.CASCADE, related_name="opportunities") dealer = models.ForeignKey(
customer = models.ForeignKey(Customer, on_delete=models.CASCADE, related_name="opportunities") Dealer, on_delete=models.CASCADE, related_name="opportunities"
car = models.ForeignKey(Car, on_delete=models.SET_NULL, null=True, blank=True, verbose_name=_("Car")) )
stage = models.CharField(max_length=20, choices=Stage.choices, verbose_name=_("Stage")) customer = models.ForeignKey(
status = models.CharField(max_length=20, choices=Status.choices, verbose_name=_("Status"), default=Status.NEW) Customer, on_delete=models.CASCADE, related_name="opportunities"
staff = models.ForeignKey(Staff, on_delete=models.SET_NULL, null=True, related_name="owner", )
verbose_name=_("Owner")) car = models.ForeignKey(
Car, on_delete=models.SET_NULL, null=True, blank=True, verbose_name=_("Car")
)
stage = models.CharField(
max_length=20, choices=Stage.choices, verbose_name=_("Stage")
)
status = models.CharField(
max_length=20,
choices=Status.choices,
verbose_name=_("Status"),
default=Status.NEW,
)
staff = models.ForeignKey(
Staff,
on_delete=models.SET_NULL,
null=True,
related_name="owner",
verbose_name=_("Owner"),
)
probability = models.PositiveIntegerField(validators=[validate_probability]) probability = models.PositiveIntegerField(validators=[validate_probability])
closing_date = models.DateField(verbose_name=_("Closing Date")) closing_date = models.DateField(verbose_name=_("Closing Date"))
created = models.DateTimeField(auto_now_add=True, verbose_name=_("Created")) created = models.DateTimeField(auto_now_add=True, verbose_name=_("Created"))
@ -932,9 +1181,11 @@ class Opportunity(models.Model):
class Notes(models.Model): class Notes(models.Model):
content_type = models.ForeignKey(ContentType, on_delete=models.CASCADE) content_type = models.ForeignKey(ContentType, on_delete=models.CASCADE)
object_id = models.PositiveIntegerField() object_id = models.PositiveIntegerField()
content_object = GenericForeignKey('content_type', 'object_id') content_object = GenericForeignKey("content_type", "object_id")
note = models.TextField(verbose_name=_("Note")) note = models.TextField(verbose_name=_("Note"))
created_by = models.ForeignKey(User, on_delete=models.DO_NOTHING, related_name="notes_created") created_by = models.ForeignKey(
User, on_delete=models.DO_NOTHING, related_name="notes_created"
)
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"))
@ -949,10 +1200,14 @@ class Notes(models.Model):
class Activity(models.Model): class Activity(models.Model):
content_type = models.ForeignKey(ContentType, on_delete=models.CASCADE) content_type = models.ForeignKey(ContentType, on_delete=models.CASCADE)
object_id = models.PositiveIntegerField() object_id = models.PositiveIntegerField()
content_object = GenericForeignKey('content_type', 'object_id') content_object = GenericForeignKey("content_type", "object_id")
activity_type = models.CharField(max_length=50, choices=ActionChoices.choices, verbose_name=_("Activity Type")) activity_type = models.CharField(
max_length=50, choices=ActionChoices.choices, verbose_name=_("Activity Type")
)
notes = models.TextField(blank=True, null=True, verbose_name=_("Notes")) notes = models.TextField(blank=True, null=True, verbose_name=_("Notes"))
created_by = models.ForeignKey(User, on_delete=models.DO_NOTHING, related_name="activities_created") created_by = models.ForeignKey(
User, on_delete=models.DO_NOTHING, related_name="activities_created"
)
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"))
@ -965,7 +1220,9 @@ class Activity(models.Model):
class Notification(models.Model): class Notification(models.Model):
user = models.ForeignKey(User, on_delete=models.CASCADE, related_name="notifications") user = models.ForeignKey(
User, on_delete=models.CASCADE, related_name="notifications"
)
message = models.CharField(max_length=255, verbose_name=_("Message")) message = models.CharField(max_length=255, verbose_name=_("Message"))
is_read = models.BooleanField(default=False, verbose_name=_("Is Read")) is_read = models.BooleanField(default=False, verbose_name=_("Is Read"))
created = models.DateTimeField(auto_now_add=True, verbose_name=_("Created")) created = models.DateTimeField(auto_now_add=True, verbose_name=_("Created"))
@ -973,7 +1230,7 @@ class Notification(models.Model):
class Meta: class Meta:
verbose_name = _("Notification") verbose_name = _("Notification")
verbose_name_plural = _("Notifications") verbose_name_plural = _("Notifications")
ordering = ['-created'] ordering = ["-created"]
def __str__(self): def __str__(self):
return self.message return self.message
@ -1041,23 +1298,35 @@ class SaleQuotation(models.Model):
updated_at = models.DateTimeField(auto_now=True, verbose_name=_("Updated At")) updated_at = models.DateTimeField(auto_now=True, verbose_name=_("Updated At"))
posted = models.BooleanField(default=False) posted = models.BooleanField(default=False)
payment_id = models.CharField(max_length=255, null=True, blank=True, verbose_name=_("Payment ID")) payment_id = models.CharField(
max_length=255, null=True, blank=True, verbose_name=_("Payment ID")
)
is_paid = models.BooleanField(default=False) is_paid = models.BooleanField(default=False)
date_draft = models.DateTimeField(null=True, blank=True, verbose_name=_('Draft Date')) date_draft = models.DateTimeField(
date_in_review = models.DateTimeField(null=True, blank=True, verbose_name=_('In Review Date')) null=True, blank=True, verbose_name=_("Draft 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_in_review = models.DateTimeField(
date_void = models.DateTimeField(null=True, blank=True, verbose_name=_('Void Date')) null=True, blank=True, verbose_name=_("In Review Date")
date_canceled = models.DateTimeField(null=True, blank=True, verbose_name=_('Canceled 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 @property
def total_quantity(self): def total_quantity(self):
total_quantity = self.quotation_cars.aggregate(total=Sum('quantity'))['total'] total_quantity = self.quotation_cars.aggregate(total=Sum("quantity"))["total"]
return total_quantity or 0 return total_quantity or 0
@property @property
def total(self): def total(self):
total = self.quotation_cars.aggregate(total_price=Sum(F('car__finances__selling_price') * F('quantity'))) total = self.quotation_cars.aggregate(
total_price=Sum(F("car__finances__selling_price") * F("quantity"))
)
if not total: if not total:
return 0 return 0
return total["total_price"] return total["total_price"]
@ -1096,7 +1365,7 @@ class SaleQuotation(models.Model):
@classmethod @classmethod
def _get_quotation_number(cls): def _get_quotation_number(cls):
last_quotation = cls.objects.all().order_by('id').last() last_quotation = cls.objects.all().order_by("id").last()
if last_quotation: if last_quotation:
last_quotation_number = int(last_quotation.quotation_number) last_quotation_number = int(last_quotation.quotation_number)
else: else:
@ -1111,11 +1380,7 @@ class SaleQuotationCar(models.Model):
related_name="quotation_cars", related_name="quotation_cars",
verbose_name=_("Quotation"), verbose_name=_("Quotation"),
) )
car = models.ForeignKey( car = models.ForeignKey(Car, on_delete=models.CASCADE, verbose_name=_("Car"))
Car,
on_delete=models.CASCADE,
verbose_name=_("Car")
)
quantity = models.PositiveIntegerField(default=1, verbose_name=_("Quantity")) quantity = models.PositiveIntegerField(default=1, verbose_name=_("Quantity"))
@property @property
@ -1183,16 +1448,24 @@ class SalesOrder(models.Model):
class Payment(models.Model): class Payment(models.Model):
METHOD_CHOICES = [ METHOD_CHOICES = [
('cash', _('cash')), ("cash", _("cash")),
('credit', _('credit')), ("credit", _("credit")),
('transfer', _('transfer')), ("transfer", _("transfer")),
('debit', _('debit')), ("debit", _("debit")),
('SADAD', _('SADAD')), ("SADAD", _("SADAD")),
] ]
quotation = models.ForeignKey(SaleQuotation, on_delete=models.CASCADE, related_name="payments") quotation = models.ForeignKey(
amount = models.DecimalField(max_digits=10, decimal_places=2, verbose_name=_("amount")) SaleQuotation, on_delete=models.CASCADE, related_name="payments"
payment_method = models.CharField(choices=METHOD_CHOICES, max_length=50, verbose_name=_("method")) )
reference_number = models.CharField(max_length=100, null=True, blank=True, verbose_name=_("reference number")) amount = models.DecimalField(
max_digits=10, decimal_places=2, verbose_name=_("amount")
)
payment_method = models.CharField(
choices=METHOD_CHOICES, max_length=50, verbose_name=_("method")
)
reference_number = models.CharField(
max_length=100, null=True, blank=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"))
# def save(self, *args, **kwargs): # def save(self, *args, **kwargs):
@ -1211,8 +1484,12 @@ class Payment(models.Model):
class Refund(models.Model): class Refund(models.Model):
payment = models.OneToOneField(Payment, on_delete=models.CASCADE, related_name="refund") payment = models.OneToOneField(
amount = models.DecimalField(max_digits=10, decimal_places=2, verbose_name=_("amount")) Payment, on_delete=models.CASCADE, related_name="refund"
)
amount = models.DecimalField(
max_digits=10, decimal_places=2, verbose_name=_("amount")
)
reason = models.TextField(blank=True, verbose_name=_("reason")) reason = models.TextField(blank=True, verbose_name=_("reason"))
refund_date = models.DateField(auto_now_add=True, verbose_name=_("refund date")) refund_date = models.DateField(auto_now_add=True, verbose_name=_("refund date"))
@ -1232,9 +1509,7 @@ class UserActivityLog(models.Model):
class Meta: class Meta:
verbose_name = "User Activity Log" verbose_name = "User Activity Log"
verbose_name_plural = "User Activity Logs" verbose_name_plural = "User Activity Logs"
ordering = ['-timestamp'] ordering = ["-timestamp"]
def __str__(self): def __str__(self):
return f"{self.user.email} - {self.action} - {self.timestamp}" return f"{self.user.email} - {self.action} - {self.timestamp}"

View File

@ -301,7 +301,6 @@
</div> </div>
</li> </li>
{% include "notifications.html" %} {% include "notifications.html" %}
<li class="nav-item dropdown"> <li class="nav-item dropdown">
<a class="nav-link dropdown-toggle" href="#" id="languageDropdown" role="button" data-bs-toggle="dropdown" aria-expanded="false" data-bs-auto-close="outside" aria-haspopup="true"> <a class="nav-link dropdown-toggle" href="#" id="languageDropdown" role="button" data-bs-toggle="dropdown" aria-expanded="false" data-bs-auto-close="outside" aria-haspopup="true">
{% if request.LANGUAGE_CODE == 'ar' %} {% if request.LANGUAGE_CODE == 'ar' %}
@ -344,7 +343,7 @@
<div class="text-center pt-4 pb-3"> <div class="text-center pt-4 pb-3">
<div class="avatar avatar-xl"> <div class="avatar avatar-xl">
{% if user.dealer.logo %} {% if user.dealer.logo %}
<img class="rounded-circle" src="{{ user.dealer.logo.url }}" alt="" /> <img class="rounded-circle" src="{{ user.dealer.logo.url }}" alt="" />
{% elif user.staff.dealer.logo %} {% elif user.staff.dealer.logo %}
<img class="rounded-circle" src="{{ user.staff.dealer.logo.url }}" alt="" /> <img class="rounded-circle" src="{{ user.staff.dealer.logo.url }}" alt="" />

View File

@ -74,7 +74,14 @@
</div> </div>
<tr> <tr>
<td class="align-middle ps-3">{{ bill.bill_number }}</td> <td class="align-middle ps-3">{{ bill.bill_number }}</td>
<td class="align-middle">{{ bill.bill_status }}</td> <td class="align-middle">
{% if bill.bill.status == 'draft' %}
<span class="badge badge-phoenix badge-phoenix-warning">
{{ bill.bill_status }}
</span>
{% endif %}
</td>
<td class="align-middle text-end py-3 pe-3"> <td class="align-middle text-end py-3 pe-3">
{{bill.vendor.vendor_name}} {{bill.vendor.vendor_name}}
</td> </td>