From 6354f0c3265e17bbb58202b0bcf72aab407e3ab9 Mon Sep 17 00:00:00 2001 From: Marwan Alwali Date: Wed, 22 Jan 2025 16:20:54 +0300 Subject: [PATCH] update --- inventory/__pycache__/forms.cpython-311.pyc | Bin 32060 -> 32637 bytes inventory/__pycache__/models.cpython-311.pyc | Bin 82030 -> 85837 bytes inventory/__pycache__/urls.cpython-311.pyc | Bin 21804 -> 23951 bytes inventory/forms.py | 36 +- .../migrations/0007_delete_cartransferlog.py | 16 - inventory/models.py | 671 ++++++++++++------ templates/header.html | 3 +- templates/ledger/bills/bill_list.html | 9 +- 8 files changed, 505 insertions(+), 230 deletions(-) delete mode 100644 inventory/migrations/0007_delete_cartransferlog.py diff --git a/inventory/__pycache__/forms.cpython-311.pyc b/inventory/__pycache__/forms.cpython-311.pyc index 1663fc4c2801cd8379b8e31a26ecf32dc73241cf..8bbef8edbae28575087a59170392edb7c1c409a6 100644 GIT binary patch delta 7722 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 VN)dGOX_00tEP|i7DK27T{vQC57~cQ@ delta 20923 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 U4YYBoG27<1iG|o=H?g$;3+mqYS^xk5 diff --git a/inventory/__pycache__/urls.cpython-311.pyc b/inventory/__pycache__/urls.cpython-311.pyc index f61334df641b604f04ea9fb6d520f3b6754b49ea..3a05f167d728f941f221cef327a72130e0200710 100644 GIT binary patch literal 23951 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 Hp6>ZSsh-`x delta 5297 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 diff --git a/inventory/forms.py b/inventory/forms.py index f5b397b6..92ba04a4 100644 --- a/inventory/forms.py +++ b/inventory/forms.py @@ -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() diff --git a/inventory/migrations/0007_delete_cartransferlog.py b/inventory/migrations/0007_delete_cartransferlog.py deleted file mode 100644 index c926ee10..00000000 --- a/inventory/migrations/0007_delete_cartransferlog.py +++ /dev/null @@ -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', - ), - ] diff --git a/inventory/models.py b/inventory/models.py index 454f0f31..ae0cc00b 100644 --- a/inventory/models.py +++ b/inventory/models.py @@ -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}" - - diff --git a/templates/header.html b/templates/header.html index 15a7172c..fd7571f7 100644 --- a/templates/header.html +++ b/templates/header.html @@ -301,7 +301,6 @@ {% include "notifications.html" %} -