From 8df5fbef8c989cef3bc8300774016a310ebbc512 Mon Sep 17 00:00:00 2001 From: Marwan Alwali Date: Wed, 22 Jan 2025 16:02:43 +0300 Subject: [PATCH] update --- inventory/models.py | 127 +++++++++++++++++++++++++------------------- 1 file changed, 72 insertions(+), 55 deletions(-) diff --git a/inventory/models.py b/inventory/models.py index 2751a19a..454f0f31 100644 --- a/inventory/models.py +++ b/inventory/models.py @@ -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): """