update
This commit is contained in:
parent
d675d50ae6
commit
8df5fbef8c
@ -17,7 +17,7 @@ from django_ledger.models import (
|
||||
UnitOfMeasureModel,
|
||||
CustomerModel,
|
||||
ItemModelQuerySet,
|
||||
|
||||
|
||||
)
|
||||
from django.db.models import Sum
|
||||
from decimal import Decimal, InvalidOperation
|
||||
@ -29,7 +29,7 @@ from sqlalchemy.orm.base import object_state
|
||||
from .utilities.financials import get_financial_value, get_total, get_total_financials
|
||||
from django.db.models import FloatField
|
||||
from .mixins import LocalizedNameMixin
|
||||
from django_ledger.models import EntityModel,ItemModel
|
||||
from django_ledger.models import EntityModel, ItemModel
|
||||
from django_countries.fields import CountryField
|
||||
from django.contrib.contenttypes.fields import GenericForeignKey
|
||||
from django.contrib.contenttypes.models import ContentType
|
||||
@ -38,32 +38,35 @@ 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)
|
||||
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)
|
||||
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):
|
||||
@ -74,6 +77,7 @@ class VatRate(models.Model):
|
||||
def __str__(self):
|
||||
return f"Rate: {self.rate}%"
|
||||
|
||||
|
||||
class CarType(models.IntegerChoices):
|
||||
CAR = 1, _('Car')
|
||||
LIGHT_COMMERCIAL = 2, _('Light Commercial')
|
||||
@ -90,7 +94,6 @@ class CarType(models.IntegerChoices):
|
||||
SNOWMOBILES = 26, _('Snowmobiles')
|
||||
|
||||
|
||||
|
||||
class CarMake(models.Model, LocalizedNameMixin):
|
||||
id_car_make = models.AutoField(primary_key=True)
|
||||
name = models.CharField(max_length=255, blank=True, null=True)
|
||||
@ -159,6 +162,7 @@ class CarEquipment(models.Model, LocalizedNameMixin):
|
||||
|
||||
def __str__(self):
|
||||
return self.name
|
||||
|
||||
class Meta:
|
||||
verbose_name = "Equipment"
|
||||
|
||||
@ -239,7 +243,7 @@ class AdditionalServices(models.Model, LocalizedNameMixin):
|
||||
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)
|
||||
item = models.OneToOneField(ItemModel, on_delete=models.CASCADE, verbose_name=_("Item"), null=True, blank=True)
|
||||
|
||||
class Meta:
|
||||
verbose_name = _("Additional Services")
|
||||
@ -330,9 +334,11 @@ class Car(models.Model):
|
||||
def get_car_group(self):
|
||||
return f"{self.id_car_make.get_local_name} {self.id_car_model.get_local_name}"
|
||||
|
||||
|
||||
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"))
|
||||
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"))
|
||||
|
||||
@ -349,29 +355,29 @@ class CarReservation(models.Model):
|
||||
|
||||
# Car Finance Model
|
||||
class CarFinance(models.Model):
|
||||
additional_services = models.ManyToManyField(AdditionalServices, related_name="additional_finances",blank=True)
|
||||
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
|
||||
|
||||
@property
|
||||
def total_discount(self):
|
||||
if self.discount_amount > 0:
|
||||
return self.total - self.discount_amount
|
||||
return self.total
|
||||
|
||||
|
||||
@property
|
||||
def total_vat(self):
|
||||
return self.total_discount + self.vat_amount
|
||||
|
||||
|
||||
|
||||
@property
|
||||
def vat_amount(self):
|
||||
vat = VatRate.objects.filter(is_active=True).first()
|
||||
@ -381,13 +387,12 @@ class CarFinance(models.Model):
|
||||
|
||||
@property
|
||||
def revenue(self):
|
||||
return self.selling_price-self.cost_price
|
||||
|
||||
|
||||
return self.selling_price - self.cost_price
|
||||
|
||||
def __str__(self):
|
||||
return f"Car: {self.car}, Selling Price: {self.selling_price}"
|
||||
|
||||
# def save(self, *args, **kwargs):
|
||||
# def save(self, *args, **kwargs):
|
||||
# self.full_clean()
|
||||
# try:
|
||||
# price_after_discount = self.selling_price - self.discount_amount
|
||||
@ -555,6 +560,7 @@ class Subscription(models.Model):
|
||||
)
|
||||
last_payment_date = models.DateField(null=True, blank=True, help_text="Date of the last payment made")
|
||||
next_payment_date = models.DateField(null=True, blank=True, help_text="Date of the next payment due")
|
||||
|
||||
class Meta:
|
||||
verbose_name = _("Subscription")
|
||||
verbose_name_plural = _("Subscriptions")
|
||||
@ -607,8 +613,8 @@ 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)
|
||||
, null=True
|
||||
, blank=True)
|
||||
vrn = models.CharField(max_length=15,
|
||||
verbose_name=_("VAT Registration Number"),
|
||||
null=True,
|
||||
@ -650,14 +656,14 @@ class Dealer(models.Model, LocalizedNameMixin):
|
||||
|
||||
class Meta:
|
||||
verbose_name = _("Dealer")
|
||||
verbose_name_plural = _("Dealers")
|
||||
verbose_name_plural = _("Dealers")
|
||||
# permissions = [
|
||||
# ('change_dealer_type', 'Can change dealer type'),
|
||||
# ]
|
||||
|
||||
def __str__(self):
|
||||
return self.name
|
||||
|
||||
|
||||
# @property
|
||||
# def get_sub_dealers(self):
|
||||
# if self.dealer_type == "OWNER":
|
||||
@ -671,18 +677,18 @@ class Dealer(models.Model, LocalizedNameMixin):
|
||||
# def get_root_dealer(self):
|
||||
# return self.parent_dealer if self.parent_dealer else self
|
||||
|
||||
|
||||
##############################
|
||||
# Additional staff types for later
|
||||
|
||||
# COORDINATOR = "coordinator", _("Coordinator")
|
||||
# RECEPTIONIST = "receptionist", _("Receptionist")
|
||||
# AGENT = "agent", _("Agent")
|
||||
# TECHNICIAN = "technician", _("Technician")
|
||||
# DRIVER = "driver", _("Driver")
|
||||
# COORDINATOR = "coordinator", _("Coordinator")
|
||||
# RECEPTIONIST = "receptionist", _("Receptionist")
|
||||
# AGENT = "agent", _("Agent")
|
||||
# TECHNICIAN = "technician", _("Technician")
|
||||
# DRIVER = "driver", _("Driver")
|
||||
##############################
|
||||
|
||||
|
||||
|
||||
class StaffTypes(models.TextChoices):
|
||||
MANAGER = "manager", _("Manager")
|
||||
INVENTORY = "inventory", _("Inventory")
|
||||
@ -727,6 +733,7 @@ class Sources(models.TextChoices):
|
||||
YOUTUBE = "youtube", _("Youtube")
|
||||
CAMPAIGN = "campaign", _("Campaign")
|
||||
|
||||
|
||||
class Channel(models.TextChoices):
|
||||
WALK_IN = "walk_in", _("Walk In")
|
||||
TOLL_FREE = "toll_free", _("Toll Free")
|
||||
@ -742,6 +749,7 @@ class Status(models.TextChoices):
|
||||
QUALIFIED = "qualified", _("Qualified")
|
||||
CANCELED = "canceled", _("Canceled")
|
||||
|
||||
|
||||
class Title(models.TextChoices):
|
||||
MR = "mr", _("Mr")
|
||||
MRS = "mrs", _("Mrs")
|
||||
@ -754,6 +762,7 @@ class Title(models.TextChoices):
|
||||
COMPANY = "company", _("Company")
|
||||
NA = "na", _("N/A")
|
||||
|
||||
|
||||
class ActionChoices(models.TextChoices):
|
||||
CALL = "call", _("Call")
|
||||
SMS = "sms", _("SMS")
|
||||
@ -770,6 +779,7 @@ class ActionChoices(models.TextChoices):
|
||||
CREATE_INVOICE = "create_invoice", _("Create Invoice")
|
||||
CANCEL_INVOICE = "cancel_invoice", _("Cancel Invoice")
|
||||
|
||||
|
||||
class Stage(models.TextChoices):
|
||||
PROSPECT = "prospect", _("Prospect")
|
||||
PROPOSAL = "proposal", _("Proposal")
|
||||
@ -777,6 +787,7 @@ class Stage(models.TextChoices):
|
||||
CLOSED_WON = "closed_won", _("Closed Won")
|
||||
CLOSED_LOST = "closed_lost", _("Closed Lost")
|
||||
|
||||
|
||||
class Priority(models.TextChoices):
|
||||
LOW = "low", _("Low")
|
||||
MEDIUM = "medium", _("Medium")
|
||||
@ -784,7 +795,7 @@ class Priority(models.TextChoices):
|
||||
|
||||
|
||||
class Customer(models.Model):
|
||||
dealer = models.ForeignKey(Dealer, on_delete=models.CASCADE,related_name="customers")
|
||||
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"))
|
||||
@ -826,6 +837,7 @@ class Organization(models.Model, LocalizedNameMixin):
|
||||
class Meta:
|
||||
verbose_name = _("Organization")
|
||||
verbose_name_plural = _("Organizations")
|
||||
|
||||
def __str__(self):
|
||||
return self.name
|
||||
|
||||
@ -852,15 +864,18 @@ 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"))
|
||||
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"))
|
||||
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)
|
||||
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"))
|
||||
|
||||
@ -898,7 +913,8 @@ class Opportunity(models.Model):
|
||||
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"))
|
||||
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"))
|
||||
@ -992,7 +1008,6 @@ class Vendor(models.Model, LocalizedNameMixin):
|
||||
return self.name
|
||||
|
||||
|
||||
|
||||
class SaleQuotation(models.Model):
|
||||
quotation_number = models.CharField(max_length=10, unique=True)
|
||||
|
||||
@ -1004,7 +1019,7 @@ class SaleQuotation(models.Model):
|
||||
]
|
||||
dealer = models.ForeignKey(
|
||||
Dealer, on_delete=models.CASCADE, related_name="sales", null=True
|
||||
)
|
||||
)
|
||||
customer = models.ForeignKey(
|
||||
Customer,
|
||||
on_delete=models.CASCADE,
|
||||
@ -1024,7 +1039,7 @@ class SaleQuotation(models.Model):
|
||||
)
|
||||
created_at = models.DateTimeField(auto_now_add=True, verbose_name=_("Created At"))
|
||||
updated_at = models.DateTimeField(auto_now=True, verbose_name=_("Updated At"))
|
||||
|
||||
|
||||
posted = models.BooleanField(default=False)
|
||||
payment_id = models.CharField(max_length=255, null=True, blank=True, verbose_name=_("Payment ID"))
|
||||
is_paid = models.BooleanField(default=False)
|
||||
@ -1046,13 +1061,13 @@ class SaleQuotation(models.Model):
|
||||
if not total:
|
||||
return 0
|
||||
return total["total_price"]
|
||||
|
||||
|
||||
@property
|
||||
def total_vat(self):
|
||||
if self.total:
|
||||
return float(self.total) * 0.15 + float(self.total)
|
||||
return 0
|
||||
|
||||
|
||||
# def confirm(self):
|
||||
# """Confirm the quotation and lock financial details."""
|
||||
# if self.status != "DRAFT":
|
||||
@ -1069,11 +1084,11 @@ class SaleQuotation(models.Model):
|
||||
|
||||
def __str__(self):
|
||||
return f"Quotation #{self.quotation_number} for {self.customer}"
|
||||
|
||||
|
||||
@property
|
||||
def display_quotation_number(self):
|
||||
return f"QN-{self.quotation_number}"
|
||||
|
||||
|
||||
def save(self, *args, **kwargs):
|
||||
if not self.quotation_number:
|
||||
self.quotation_number = str(next(self._get_quotation_number())).zfill(6)
|
||||
@ -1102,10 +1117,11 @@ class SaleQuotationCar(models.Model):
|
||||
verbose_name=_("Car")
|
||||
)
|
||||
quantity = models.PositiveIntegerField(default=1, verbose_name=_("Quantity"))
|
||||
|
||||
|
||||
@property
|
||||
def finance(self):
|
||||
return self.car.finances
|
||||
|
||||
@property
|
||||
def financial_details(self):
|
||||
"""
|
||||
@ -1134,6 +1150,7 @@ class SaleQuotationCar(models.Model):
|
||||
if not self.car.finances:
|
||||
return Decimal("0.00")
|
||||
return self.car.finances.selling_price * self.quantity
|
||||
|
||||
@property
|
||||
def total_vat(self):
|
||||
"""
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user