update
This commit is contained in:
parent
e0bb0b4eda
commit
6354f0c326
Binary file not shown.
Binary file not shown.
Binary file not shown.
@ -16,6 +16,7 @@ from .models import (
|
||||
Vendor,
|
||||
Customer,
|
||||
Car,
|
||||
CarTransfer,
|
||||
CarFinance,
|
||||
CustomCard,
|
||||
CarRegistration,
|
||||
@ -32,13 +33,12 @@ from .models import (
|
||||
Staff,
|
||||
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.utils.translation import gettext_lazy as _
|
||||
import django_tables2 as tables
|
||||
from django.forms import formset_factory
|
||||
|
||||
|
||||
User = get_user_model()
|
||||
|
||||
|
||||
@ -121,7 +121,6 @@ class CustomerForm(forms.ModelForm, AddClassMixin):
|
||||
}
|
||||
|
||||
|
||||
|
||||
class CarForm(
|
||||
forms.ModelForm,
|
||||
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
|
||||
class CustomCardForm(forms.ModelForm):
|
||||
custom_date = forms.DateTimeField(
|
||||
@ -379,7 +387,7 @@ class CarSelectionTable(tables.Table):
|
||||
template_name = "django_tables2/bootstrap4.html"
|
||||
|
||||
|
||||
class WizardForm1(forms.Form):
|
||||
class WizardForm1(forms.Form):
|
||||
email = forms.EmailField(
|
||||
widget=forms.EmailInput(
|
||||
attrs={
|
||||
@ -561,8 +569,8 @@ class PaymentForm(forms.Form):
|
||||
label="Payment Method",
|
||||
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):
|
||||
invoice = self.cleaned_data['invoice']
|
||||
bill = self.cleaned_data['bill']
|
||||
@ -573,12 +581,11 @@ class PaymentForm(forms.Form):
|
||||
if amount <= 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")
|
||||
return amount
|
||||
raise forms.ValidationError("Payment amount is greater than amount due")
|
||||
return amount
|
||||
|
||||
|
||||
|
||||
class EmailForm(forms.Form):
|
||||
subject = forms.CharField(max_length=255)
|
||||
@ -586,6 +593,7 @@ class EmailForm(forms.Form):
|
||||
from_email = forms.EmailField()
|
||||
to_email = forms.EmailField(label="To")
|
||||
|
||||
|
||||
class LeadForm(forms.ModelForm):
|
||||
class Meta:
|
||||
model = Lead
|
||||
@ -615,6 +623,7 @@ class NoteForm(forms.ModelForm):
|
||||
model = Notes
|
||||
fields = ['note']
|
||||
|
||||
|
||||
class ActivityForm(forms.ModelForm):
|
||||
class Meta:
|
||||
model = Activity
|
||||
@ -630,16 +639,17 @@ class OpportunityForm(forms.ModelForm):
|
||||
class InvoiceModelCreateForm(InvoiceModelCreateFormBase):
|
||||
def __init__(self, *args, **kwargs):
|
||||
super().__init__(*args, **kwargs)
|
||||
|
||||
|
||||
self.fields['cash_account'].widget = forms.HiddenInput()
|
||||
self.fields['prepaid_account'].widget = forms.HiddenInput()
|
||||
self.fields['unearned_account'].widget = forms.HiddenInput()
|
||||
self.fields['date_draft'] = forms.DateField(widget=DateInput(attrs={'type': 'date'}))
|
||||
|
||||
|
||||
|
||||
class BillModelCreateForm(BillModelCreateFormBase):
|
||||
def __init__(self, *args, **kwargs):
|
||||
super().__init__(*args, **kwargs)
|
||||
|
||||
|
||||
self.fields['cash_account'].widget = forms.HiddenInput()
|
||||
self.fields['prepaid_account'].widget = forms.HiddenInput()
|
||||
self.fields['unearned_account'].widget = forms.HiddenInput()
|
||||
|
||||
@ -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',
|
||||
),
|
||||
]
|
||||
@ -17,7 +17,6 @@ from django_ledger.models import (
|
||||
UnitOfMeasureModel,
|
||||
CustomerModel,
|
||||
ItemModelQuerySet,
|
||||
|
||||
)
|
||||
from django.db.models import Sum
|
||||
from decimal import Decimal, InvalidOperation
|
||||
@ -36,41 +35,77 @@ from django.contrib.contenttypes.models import ContentType
|
||||
|
||||
|
||||
class DealerUserManager(UserManager):
|
||||
def create_user_with_dealer(self, email, 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)
|
||||
def create_user_with_dealer(
|
||||
self,
|
||||
email,
|
||||
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
|
||||
|
||||
|
||||
class StaffUserManager(UserManager):
|
||||
def create_user_with_staff(self, email, 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)
|
||||
def create_user_with_staff(
|
||||
self,
|
||||
email,
|
||||
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
|
||||
|
||||
|
||||
class UnitOfMeasure(models.TextChoices):
|
||||
EACH = 'EA', 'Each'
|
||||
PAIR = 'PR', 'Pair'
|
||||
SET = 'SET', 'Set'
|
||||
GALLON = 'GAL', 'Gallon'
|
||||
LITER = 'L', 'Liter'
|
||||
METER = 'M', 'Meter'
|
||||
KILOGRAM = 'KG', 'Kilogram'
|
||||
HOUR = 'HR', 'Hour'
|
||||
BOX = 'BX', 'Box'
|
||||
ROLL = 'RL', 'Roll'
|
||||
PACKAGE = 'PKG', 'Package'
|
||||
DOZEN = 'DZ', 'Dozen'
|
||||
SQUARE_METER = 'SQ_M', 'Square Meter'
|
||||
PIECE = 'PC', 'Piece'
|
||||
BUNDLE = 'BDL', 'Bundle'
|
||||
EACH = "EA", "Each"
|
||||
PAIR = "PR", "Pair"
|
||||
SET = "SET", "Set"
|
||||
GALLON = "GAL", "Gallon"
|
||||
LITER = "L", "Liter"
|
||||
METER = "M", "Meter"
|
||||
KILOGRAM = "KG", "Kilogram"
|
||||
HOUR = "HR", "Hour"
|
||||
BOX = "BX", "Box"
|
||||
ROLL = "RL", "Roll"
|
||||
PACKAGE = "PKG", "Package"
|
||||
DOZEN = "DZ", "Dozen"
|
||||
SQUARE_METER = "SQ_M", "Square Meter"
|
||||
PIECE = "PC", "Piece"
|
||||
BUNDLE = "BDL", "Bundle"
|
||||
|
||||
|
||||
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)
|
||||
created_at = models.DateTimeField(auto_now_add=True)
|
||||
|
||||
@ -79,19 +114,19 @@ class VatRate(models.Model):
|
||||
|
||||
|
||||
class CarType(models.IntegerChoices):
|
||||
CAR = 1, _('Car')
|
||||
LIGHT_COMMERCIAL = 2, _('Light Commercial')
|
||||
HEAVY_DUTY_TRACTORS = 3, _('Heavy-Duty Tractors')
|
||||
TRAILERS = 4, _('Trailers')
|
||||
MEDIUM_TRUCKS = 5, _('Medium Trucks')
|
||||
BUSES = 6, _('Buses')
|
||||
MOTORCYCLES = 20, _('Motorcycles')
|
||||
BUGGY = 21, _('Buggy')
|
||||
MOTO_ATV = 22, _('Moto ATV')
|
||||
SCOOTERS = 23, _('Scooters')
|
||||
KARTING = 24, _('Karting')
|
||||
ATV = 25, _('ATV')
|
||||
SNOWMOBILES = 26, _('Snowmobiles')
|
||||
CAR = 1, _("Car")
|
||||
LIGHT_COMMERCIAL = 2, _("Light Commercial")
|
||||
HEAVY_DUTY_TRACTORS = 3, _("Heavy-Duty Tractors")
|
||||
TRAILERS = 4, _("Trailers")
|
||||
MEDIUM_TRUCKS = 5, _("Medium Trucks")
|
||||
BUSES = 6, _("Buses")
|
||||
MOTORCYCLES = 20, _("Motorcycles")
|
||||
BUGGY = 21, _("Buggy")
|
||||
MOTO_ATV = 22, _("Moto ATV")
|
||||
SCOOTERS = 23, _("Scooters")
|
||||
KARTING = 24, _("Karting")
|
||||
ATV = 25, _("ATV")
|
||||
SNOWMOBILES = 26, _("Snowmobiles")
|
||||
|
||||
|
||||
class CarMake(models.Model, LocalizedNameMixin):
|
||||
@ -124,7 +159,9 @@ class CarModel(models.Model, LocalizedNameMixin):
|
||||
|
||||
class CarSerie(models.Model, LocalizedNameMixin):
|
||||
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)
|
||||
arabic_name = models.CharField(max_length=255, 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):
|
||||
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)
|
||||
arabic_name = models.CharField(max_length=255, 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)
|
||||
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):
|
||||
return self.name
|
||||
@ -183,7 +224,9 @@ class CarSpecification(models.Model, LocalizedNameMixin):
|
||||
class CarSpecificationValue(models.Model):
|
||||
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_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)
|
||||
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)
|
||||
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):
|
||||
return self.name
|
||||
@ -209,8 +254,12 @@ class CarOption(models.Model, LocalizedNameMixin):
|
||||
|
||||
class CarOptionValue(models.Model):
|
||||
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_equipment = models.ForeignKey(CarEquipment, models.DO_NOTHING, db_column="id_car_equipment")
|
||||
id_car_option = models.ForeignKey(
|
||||
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)
|
||||
unit = models.CharField(max_length=255, blank=True, null=True)
|
||||
is_base = models.IntegerField()
|
||||
@ -222,13 +271,22 @@ class CarOptionValue(models.Model):
|
||||
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):
|
||||
AVAILABLE = "available", _("Available")
|
||||
SOLD = "sold", _("Sold")
|
||||
HOLD = "hold", _("Hold")
|
||||
DAMAGED = "damaged", _("Damaged")
|
||||
RESERVED = "reserved", _("Reserved")
|
||||
|
||||
TRANSFER = "transfer", _("Transfer")
|
||||
|
||||
class CarStockTypeChoices(models.TextChoices):
|
||||
NEW = "new", _("New")
|
||||
@ -239,11 +297,25 @@ class AdditionalServices(models.Model, LocalizedNameMixin):
|
||||
name = models.CharField(max_length=255, verbose_name=_("Name"))
|
||||
arabic_name = models.CharField(max_length=255, verbose_name=_("Arabic Name"))
|
||||
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"))
|
||||
uom = models.CharField(max_length=10, 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)
|
||||
uom = models.CharField(
|
||||
max_length=10,
|
||||
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:
|
||||
verbose_name = _("Additional Services")
|
||||
@ -330,15 +402,71 @@ class Car(models.Model):
|
||||
active_reservations = self.reservations.filter(reserved_until__gt=now())
|
||||
return active_reservations.exists()
|
||||
|
||||
def get_transfer(self):
|
||||
return self.transfer_logs.filter(active=True).first()
|
||||
@property
|
||||
def get_car_group(self):
|
||||
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):
|
||||
car = models.ForeignKey('Car', 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"))
|
||||
car = models.ForeignKey(
|
||||
"Car",
|
||||
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_until = models.DateTimeField(verbose_name=_("Reserved Until"))
|
||||
|
||||
@ -347,25 +475,37 @@ class CarReservation(models.Model):
|
||||
return self.reserved_until > now()
|
||||
|
||||
class Meta:
|
||||
unique_together = ('car', 'reserved_until')
|
||||
ordering = ['-reserved_at']
|
||||
unique_together = ("car", "reserved_until")
|
||||
ordering = ["-reserved_at"]
|
||||
verbose_name = _("Car Reservation")
|
||||
verbose_name_plural = _("Car Reservations")
|
||||
|
||||
|
||||
# Car Finance Model
|
||||
class CarFinance(models.Model):
|
||||
additional_services = models.ManyToManyField(AdditionalServices, related_name="additional_finances", blank=True)
|
||||
car = models.OneToOneField(Car, on_delete=models.CASCADE, related_name='finances')
|
||||
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"))
|
||||
discount_amount = models.DecimalField(max_digits=14, decimal_places=2, verbose_name=_("Discount Amount"),
|
||||
default=Decimal('0.00'))
|
||||
additional_services = models.ManyToManyField(
|
||||
AdditionalServices, related_name="additional_finances", blank=True
|
||||
)
|
||||
car = models.OneToOneField(Car, on_delete=models.CASCADE, related_name="finances")
|
||||
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")
|
||||
)
|
||||
discount_amount = models.DecimalField(
|
||||
max_digits=14,
|
||||
decimal_places=2,
|
||||
verbose_name=_("Discount Amount"),
|
||||
default=Decimal("0.00"),
|
||||
)
|
||||
|
||||
@property
|
||||
def total(self):
|
||||
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
|
||||
|
||||
@property
|
||||
@ -382,8 +522,8 @@ class CarFinance(models.Model):
|
||||
def vat_amount(self):
|
||||
vat = VatRate.objects.filter(is_active=True).first()
|
||||
if vat:
|
||||
return (self.total_discount * Decimal(vat.rate)).quantize(Decimal('0.01'))
|
||||
return Decimal('0.00')
|
||||
return (self.total_discount * Decimal(vat.rate)).quantize(Decimal("0.01"))
|
||||
return Decimal("0.00")
|
||||
|
||||
@property
|
||||
def revenue(self):
|
||||
@ -452,7 +592,12 @@ class CarColors(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_date = models.DateField(verbose_name=_("Custom Date"))
|
||||
|
||||
@ -466,39 +611,30 @@ class CustomCard(models.Model):
|
||||
|
||||
class CarLocation(models.Model):
|
||||
car = models.OneToOneField(
|
||||
Car,
|
||||
on_delete=models.CASCADE,
|
||||
related_name='location',
|
||||
verbose_name=_("Car")
|
||||
Car, on_delete=models.CASCADE, related_name="location", verbose_name=_("Car")
|
||||
)
|
||||
owner = models.ForeignKey(
|
||||
'Dealer',
|
||||
"Dealer",
|
||||
on_delete=models.CASCADE,
|
||||
related_name='owned_cars',
|
||||
related_name="owned_cars",
|
||||
verbose_name=_("Owner"),
|
||||
help_text=_("Dealer who owns the car.")
|
||||
help_text=_("Dealer who owns the car."),
|
||||
)
|
||||
showroom = models.ForeignKey(
|
||||
'Dealer',
|
||||
"Dealer",
|
||||
on_delete=models.CASCADE,
|
||||
related_name='showroom_cars',
|
||||
related_name="showroom_cars",
|
||||
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(
|
||||
blank=True,
|
||||
null=True,
|
||||
verbose_name=_("Description"),
|
||||
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")
|
||||
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"))
|
||||
|
||||
class Meta:
|
||||
verbose_name = _("Car Location")
|
||||
@ -525,16 +661,14 @@ class CarRegistration(models.Model):
|
||||
text1 = models.CharField(max_length=1, verbose_name=_("Text 1"))
|
||||
text2 = models.CharField(max_length=1, verbose_name=_("Text 2"))
|
||||
text3 = models.CharField(max_length=1, verbose_name=_("Text 3"))
|
||||
# registration_type =
|
||||
registration_date = models.DateTimeField(verbose_name=_("Registration Date"))
|
||||
registration_expiry_date = models.DateTimeField(verbose_name=_("Registration Expiry Date"))
|
||||
|
||||
class Meta:
|
||||
verbose_name = _("Registration")
|
||||
verbose_name_plural = _("Registrations")
|
||||
|
||||
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
|
||||
@ -547,19 +681,27 @@ class TimestampedModel(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")
|
||||
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)
|
||||
billing_cycle = models.CharField(
|
||||
max_length=10,
|
||||
choices=[('monthly', 'Monthly'), ('annual', 'Annual')],
|
||||
default='monthly',
|
||||
help_text="Billing cycle for the subscription"
|
||||
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"
|
||||
)
|
||||
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")
|
||||
@ -586,18 +728,30 @@ class SubscriptionUser(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()
|
||||
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)
|
||||
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"
|
||||
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")
|
||||
)
|
||||
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)
|
||||
|
||||
@ -611,26 +765,27 @@ class SubscriptionPlan(models.Model):
|
||||
|
||||
class Dealer(models.Model, LocalizedNameMixin):
|
||||
user = models.OneToOneField(User, on_delete=models.CASCADE, related_name="dealer")
|
||||
crn = models.CharField(max_length=10,
|
||||
verbose_name=_("Commercial Registration Number")
|
||||
, null=True
|
||||
, blank=True)
|
||||
vrn = models.CharField(max_length=15,
|
||||
verbose_name=_("VAT Registration Number"),
|
||||
null=True,
|
||||
blank=True)
|
||||
crn = models.CharField(
|
||||
max_length=10,
|
||||
verbose_name=_("Commercial Registration Number"),
|
||||
null=True,
|
||||
blank=True,
|
||||
)
|
||||
vrn = models.CharField(
|
||||
max_length=15, verbose_name=_("VAT Registration Number"), null=True, blank=True
|
||||
)
|
||||
arabic_name = models.CharField(max_length=255, verbose_name=_("Arabic Name"))
|
||||
name = models.CharField(max_length=255, verbose_name=_("English Name"))
|
||||
phone_number = PhoneNumberField(region="SA", verbose_name=_("Phone Number"))
|
||||
address = models.CharField(max_length=200,
|
||||
blank=True,
|
||||
null=True,
|
||||
verbose_name=_("Address"))
|
||||
logo = models.ImageField(upload_to="logos/users",
|
||||
blank=True,
|
||||
null=True,
|
||||
verbose_name=_("Logo"))
|
||||
entity = models.ForeignKey(EntityModel, on_delete=models.SET_NULL, null=True, blank=True)
|
||||
address = models.CharField(
|
||||
max_length=200, blank=True, null=True, verbose_name=_("Address")
|
||||
)
|
||||
logo = models.ImageField(
|
||||
upload_to="logos/users", blank=True, null=True, verbose_name=_("Logo")
|
||||
)
|
||||
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"))
|
||||
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"))
|
||||
arabic_name = models.CharField(max_length=255, verbose_name=_("Arabic Name"))
|
||||
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"))
|
||||
updated = models.DateTimeField(auto_now=True, verbose_name=_("Updated"))
|
||||
|
||||
@ -795,17 +952,33 @@ class Priority(models.TextChoices):
|
||||
|
||||
|
||||
class Customer(models.Model):
|
||||
dealer = models.ForeignKey(Dealer, on_delete=models.CASCADE, related_name="customers")
|
||||
title = models.CharField(choices=Title.choices, default=Title.NA, max_length=10, verbose_name=_("Title"))
|
||||
dealer = models.ForeignKey(
|
||||
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"))
|
||||
middle_name = models.CharField(max_length=50, blank=True, null=True, verbose_name=_("Middle Name"))
|
||||
middle_name = models.CharField(
|
||||
max_length=50, blank=True, null=True, verbose_name=_("Middle Name")
|
||||
)
|
||||
last_name = models.CharField(max_length=50, verbose_name=_("Last Name"))
|
||||
gender = models.CharField(choices=[('m', _('Male')), ('f', _('Female'))], 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"))
|
||||
email = models.EmailField(unique=True, verbose_name=_("Email"))
|
||||
national_id = models.CharField(max_length=10, unique=True, verbose_name=_("National ID"))
|
||||
phone_number = PhoneNumberField(region="SA", unique=True, verbose_name=_("Phone Number"))
|
||||
address = models.CharField(max_length=200, blank=True, null=True, verbose_name=_("Address"))
|
||||
national_id = models.CharField(
|
||||
max_length=10, unique=True, verbose_name=_("National ID")
|
||||
)
|
||||
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"))
|
||||
updated = models.DateTimeField(auto_now=True, verbose_name=_("Updated"))
|
||||
|
||||
@ -823,14 +996,22 @@ class Customer(models.Model):
|
||||
|
||||
|
||||
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"))
|
||||
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"))
|
||||
phone_number = PhoneNumberField(region='SA', verbose_name=_("Phone Number"))
|
||||
address = models.CharField(max_length=200, blank=True, null=True, verbose_name=_("Address"))
|
||||
logo = models.ImageField(upload_to="logos", blank=True, null=True, verbose_name=_("Logo"))
|
||||
phone_number = PhoneNumberField(region="SA", verbose_name=_("Phone Number"))
|
||||
address = models.CharField(
|
||||
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"))
|
||||
updated = models.DateTimeField(auto_now=True, verbose_name=_("Updated"))
|
||||
|
||||
@ -843,14 +1024,20 @@ class Organization(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"))
|
||||
arabic_name = models.CharField(max_length=255, verbose_name=_("Arabic Name"))
|
||||
id_number = models.CharField(max_length=10, unique=True, verbose_name=_("ID Number"))
|
||||
phone_number = PhoneNumberField(region='SA', verbose_name=_("Phone Number"))
|
||||
id_number = models.CharField(
|
||||
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"))
|
||||
address = models.CharField(max_length=200, blank=True, null=True, verbose_name=_("Address"))
|
||||
organization = models.ManyToManyField(Organization, related_name='representatives')
|
||||
address = models.CharField(
|
||||
max_length=200, blank=True, null=True, verbose_name=_("Address")
|
||||
)
|
||||
organization = models.ManyToManyField(Organization, related_name="representatives")
|
||||
|
||||
class Meta:
|
||||
verbose_name = _("Representative")
|
||||
@ -862,21 +1049,57 @@ class Representative(models.Model, LocalizedNameMixin):
|
||||
|
||||
class Lead(models.Model):
|
||||
dealer = models.ForeignKey(Dealer, on_delete=models.CASCADE, related_name="leads")
|
||||
customer = models.ForeignKey(Customer, on_delete=models.CASCADE, related_name="leads")
|
||||
id_car_make = models.ForeignKey(CarMake, on_delete=models.DO_NOTHING, 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"))
|
||||
customer = models.ForeignKey(
|
||||
Customer, on_delete=models.CASCADE, related_name="leads"
|
||||
)
|
||||
id_car_make = models.ForeignKey(
|
||||
CarMake,
|
||||
on_delete=models.DO_NOTHING,
|
||||
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"))
|
||||
staff = models.ForeignKey(Staff, on_delete=models.SET_NULL, blank=True, null=True, related_name="assigned",
|
||||
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)
|
||||
staff = models.ForeignKey(
|
||||
Staff,
|
||||
on_delete=models.SET_NULL,
|
||||
blank=True,
|
||||
null=True,
|
||||
related_name="assigned",
|
||||
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"))
|
||||
|
||||
class Meta:
|
||||
@ -884,14 +1107,22 @@ class Lead(models.Model):
|
||||
verbose_name_plural = _("Leads")
|
||||
|
||||
def __str__(self):
|
||||
return self.customer.get_full_name
|
||||
return f"{self.first_name} {self.last_name}"
|
||||
|
||||
|
||||
class LeadStatusHistory(models.Model):
|
||||
lead = models.ForeignKey(Lead, on_delete=models.CASCADE, related_name="status_history")
|
||||
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")
|
||||
lead = models.ForeignKey(
|
||||
Lead, on_delete=models.CASCADE, related_name="status_history"
|
||||
)
|
||||
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"))
|
||||
|
||||
class Meta:
|
||||
@ -908,13 +1139,31 @@ def validate_probability(value):
|
||||
|
||||
|
||||
class Opportunity(models.Model):
|
||||
dealer = models.ForeignKey(Dealer, on_delete=models.CASCADE, related_name="opportunities")
|
||||
customer = models.ForeignKey(Customer, 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"))
|
||||
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"))
|
||||
dealer = models.ForeignKey(
|
||||
Dealer, on_delete=models.CASCADE, related_name="opportunities"
|
||||
)
|
||||
customer = models.ForeignKey(
|
||||
Customer, 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")
|
||||
)
|
||||
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])
|
||||
closing_date = models.DateField(verbose_name=_("Closing Date"))
|
||||
created = models.DateTimeField(auto_now_add=True, verbose_name=_("Created"))
|
||||
@ -932,9 +1181,11 @@ class Opportunity(models.Model):
|
||||
class Notes(models.Model):
|
||||
content_type = models.ForeignKey(ContentType, on_delete=models.CASCADE)
|
||||
object_id = models.PositiveIntegerField()
|
||||
content_object = GenericForeignKey('content_type', 'object_id')
|
||||
content_object = GenericForeignKey("content_type", "object_id")
|
||||
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"))
|
||||
updated = models.DateTimeField(auto_now=True, verbose_name=_("Updated"))
|
||||
|
||||
@ -949,10 +1200,14 @@ class Notes(models.Model):
|
||||
class Activity(models.Model):
|
||||
content_type = models.ForeignKey(ContentType, on_delete=models.CASCADE)
|
||||
object_id = models.PositiveIntegerField()
|
||||
content_object = GenericForeignKey('content_type', 'object_id')
|
||||
activity_type = models.CharField(max_length=50, choices=ActionChoices.choices, verbose_name=_("Activity Type"))
|
||||
content_object = GenericForeignKey("content_type", "object_id")
|
||||
activity_type = models.CharField(
|
||||
max_length=50, choices=ActionChoices.choices, verbose_name=_("Activity Type")
|
||||
)
|
||||
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"))
|
||||
updated = models.DateTimeField(auto_now=True, verbose_name=_("Updated"))
|
||||
|
||||
@ -965,7 +1220,9 @@ class Activity(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"))
|
||||
is_read = models.BooleanField(default=False, verbose_name=_("Is Read"))
|
||||
created = models.DateTimeField(auto_now_add=True, verbose_name=_("Created"))
|
||||
@ -973,7 +1230,7 @@ class Notification(models.Model):
|
||||
class Meta:
|
||||
verbose_name = _("Notification")
|
||||
verbose_name_plural = _("Notifications")
|
||||
ordering = ['-created']
|
||||
ordering = ["-created"]
|
||||
|
||||
def __str__(self):
|
||||
return self.message
|
||||
@ -1041,23 +1298,35 @@ class SaleQuotation(models.Model):
|
||||
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"))
|
||||
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'))
|
||||
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']
|
||||
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')))
|
||||
total = self.quotation_cars.aggregate(
|
||||
total_price=Sum(F("car__finances__selling_price") * F("quantity"))
|
||||
)
|
||||
if not total:
|
||||
return 0
|
||||
return total["total_price"]
|
||||
@ -1096,7 +1365,7 @@ class SaleQuotation(models.Model):
|
||||
|
||||
@classmethod
|
||||
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:
|
||||
last_quotation_number = int(last_quotation.quotation_number)
|
||||
else:
|
||||
@ -1111,11 +1380,7 @@ class SaleQuotationCar(models.Model):
|
||||
related_name="quotation_cars",
|
||||
verbose_name=_("Quotation"),
|
||||
)
|
||||
car = models.ForeignKey(
|
||||
Car,
|
||||
on_delete=models.CASCADE,
|
||||
verbose_name=_("Car")
|
||||
)
|
||||
car = models.ForeignKey(Car, on_delete=models.CASCADE, verbose_name=_("Car"))
|
||||
quantity = models.PositiveIntegerField(default=1, verbose_name=_("Quantity"))
|
||||
|
||||
@property
|
||||
@ -1183,16 +1448,24 @@ class SalesOrder(models.Model):
|
||||
|
||||
class Payment(models.Model):
|
||||
METHOD_CHOICES = [
|
||||
('cash', _('cash')),
|
||||
('credit', _('credit')),
|
||||
('transfer', _('transfer')),
|
||||
('debit', _('debit')),
|
||||
('SADAD', _('SADAD')),
|
||||
("cash", _("cash")),
|
||||
("credit", _("credit")),
|
||||
("transfer", _("transfer")),
|
||||
("debit", _("debit")),
|
||||
("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"))
|
||||
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"))
|
||||
quotation = models.ForeignKey(
|
||||
SaleQuotation, on_delete=models.CASCADE, related_name="payments"
|
||||
)
|
||||
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"))
|
||||
|
||||
# def save(self, *args, **kwargs):
|
||||
@ -1211,8 +1484,12 @@ class Payment(models.Model):
|
||||
|
||||
|
||||
class Refund(models.Model):
|
||||
payment = models.OneToOneField(Payment, on_delete=models.CASCADE, related_name="refund")
|
||||
amount = models.DecimalField(max_digits=10, decimal_places=2, verbose_name=_("amount"))
|
||||
payment = models.OneToOneField(
|
||||
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"))
|
||||
refund_date = models.DateField(auto_now_add=True, verbose_name=_("refund date"))
|
||||
|
||||
@ -1232,9 +1509,7 @@ class UserActivityLog(models.Model):
|
||||
class Meta:
|
||||
verbose_name = "User Activity Log"
|
||||
verbose_name_plural = "User Activity Logs"
|
||||
ordering = ['-timestamp']
|
||||
ordering = ["-timestamp"]
|
||||
|
||||
def __str__(self):
|
||||
return f"{self.user.email} - {self.action} - {self.timestamp}"
|
||||
|
||||
|
||||
|
||||
@ -301,7 +301,6 @@
|
||||
</div>
|
||||
</li>
|
||||
{% include "notifications.html" %}
|
||||
|
||||
<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">
|
||||
{% if request.LANGUAGE_CODE == 'ar' %}
|
||||
@ -344,7 +343,7 @@
|
||||
<div class="text-center pt-4 pb-3">
|
||||
|
||||
<div class="avatar avatar-xl">
|
||||
{% if user.dealer.logo %}
|
||||
{% if user.dealer.logo %}
|
||||
<img class="rounded-circle" src="{{ user.dealer.logo.url }}" alt="" />
|
||||
{% elif user.staff.dealer.logo %}
|
||||
<img class="rounded-circle" src="{{ user.staff.dealer.logo.url }}" alt="" />
|
||||
|
||||
@ -74,7 +74,14 @@
|
||||
</div>
|
||||
<tr>
|
||||
<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">
|
||||
{{bill.vendor.vendor_name}}
|
||||
</td>
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user