From 6e3a2df75b0b26ae6ff860c4338646b489559784 Mon Sep 17 00:00:00 2001 From: gitea Date: Mon, 20 Jan 2025 15:29:01 +0000 Subject: [PATCH] add car transfer --- inventory/forms.py | 9 + .../migrations/0005_merge_20250119_1555.py | 14 + ..._cartransferlog_delete_invoicemodelbase.py | 32 + ..._cartransferlog_cars_cartransferlog_car.py | 24 + ..._at_cartransferlog_is_approved_and_more.py | 35 + .../0009_cartransfer_delete_cartransferlog.py | 36 + ...010_cartransfer_active_alter_car_status.py | 23 + .../0011_alter_cartransfer_status.py | 18 + .../migrations/0012_cartransfer_quantity.py | 18 + .../0013_alter_cartransfer_status.py | 18 + inventory/models.py | 724 ++++++++++++------ inventory/signals.py | 16 +- inventory/urls.py | 24 +- inventory/utils.py | 207 ++++- inventory/views.py | 107 +++ templates/crm/notifications_history.html | 2 +- templates/header.html | 2 +- templates/inventory/car_detail.html | 66 +- templates/inventory/transfer_details.html | 47 ++ templates/inventory/transfer_preview.html | 339 ++++++++ 20 files changed, 1496 insertions(+), 265 deletions(-) create mode 100644 inventory/migrations/0005_merge_20250119_1555.py create mode 100644 inventory/migrations/0006_cartransferlog_delete_invoicemodelbase.py create mode 100644 inventory/migrations/0007_remove_cartransferlog_cars_cartransferlog_car.py create mode 100644 inventory/migrations/0008_cartransferlog_created_at_cartransferlog_is_approved_and_more.py create mode 100644 inventory/migrations/0009_cartransfer_delete_cartransferlog.py create mode 100644 inventory/migrations/0010_cartransfer_active_alter_car_status.py create mode 100644 inventory/migrations/0011_alter_cartransfer_status.py create mode 100644 inventory/migrations/0012_cartransfer_quantity.py create mode 100644 inventory/migrations/0013_alter_cartransfer_status.py create mode 100644 templates/inventory/transfer_details.html create mode 100644 templates/inventory/transfer_preview.html diff --git a/inventory/forms.py b/inventory/forms.py index 4c1a9b87..88e74760 100644 --- a/inventory/forms.py +++ b/inventory/forms.py @@ -17,6 +17,7 @@ from .models import ( Vendor, Customer, Car, + CarTransfer, CarFinance, CustomCard, CarRegistration, @@ -242,6 +243,14 @@ class CarLocationForm(forms.ModelForm): "description": forms.Textarea(attrs={"rows": 2, "class": "form-control"}), } +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): diff --git a/inventory/migrations/0005_merge_20250119_1555.py b/inventory/migrations/0005_merge_20250119_1555.py new file mode 100644 index 00000000..1ea37506 --- /dev/null +++ b/inventory/migrations/0005_merge_20250119_1555.py @@ -0,0 +1,14 @@ +# Generated by Django 4.2.17 on 2025-01-19 12:55 + +from django.db import migrations + + +class Migration(migrations.Migration): + + dependencies = [ + ('inventory', '0004_invoicemodelbase'), + ('inventory', '0004_rename_assigned_lead_staff_remove_customer_city_and_more'), + ] + + operations = [ + ] diff --git a/inventory/migrations/0006_cartransferlog_delete_invoicemodelbase.py b/inventory/migrations/0006_cartransferlog_delete_invoicemodelbase.py new file mode 100644 index 00000000..e61ddb8f --- /dev/null +++ b/inventory/migrations/0006_cartransferlog_delete_invoicemodelbase.py @@ -0,0 +1,32 @@ +# Generated by Django 4.2.17 on 2025-01-19 14:01 + +from django.db import migrations, models +import django.db.models.deletion + + +class Migration(migrations.Migration): + + dependencies = [ + ('inventory', '0005_merge_20250119_1555'), + ] + + operations = [ + migrations.CreateModel( + name='CarTransferLog', + fields=[ + ('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')), + ('transfer_date', models.DateTimeField(auto_now_add=True, verbose_name='Transfer Date')), + ('remarks', models.TextField(blank=True, null=True, verbose_name='Remarks')), + ('cars', models.ManyToManyField(related_name='transfer_logs', to='inventory.car', verbose_name='Cars')), + ('from_dealer', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, related_name='transfers_out', to='inventory.dealer', verbose_name='From Dealer')), + ('to_dealer', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, related_name='transfers_in', to='inventory.dealer', verbose_name='To Dealer')), + ], + options={ + 'verbose_name': 'Car Transfer Log', + 'verbose_name_plural': 'Car Transfer Logs', + }, + ), + migrations.DeleteModel( + name='InvoiceModelBase', + ), + ] diff --git a/inventory/migrations/0007_remove_cartransferlog_cars_cartransferlog_car.py b/inventory/migrations/0007_remove_cartransferlog_cars_cartransferlog_car.py new file mode 100644 index 00000000..5c5ddb0e --- /dev/null +++ b/inventory/migrations/0007_remove_cartransferlog_cars_cartransferlog_car.py @@ -0,0 +1,24 @@ +# Generated by Django 4.2.17 on 2025-01-19 14:15 + +from django.db import migrations, models +import django.db.models.deletion + + +class Migration(migrations.Migration): + + dependencies = [ + ('inventory', '0006_cartransferlog_delete_invoicemodelbase'), + ] + + operations = [ + migrations.RemoveField( + model_name='cartransferlog', + name='cars', + ), + migrations.AddField( + model_name='cartransferlog', + name='car', + field=models.ForeignKey(default=4, on_delete=django.db.models.deletion.CASCADE, related_name='transfer_logs', to='inventory.car', verbose_name='Car'), + preserve_default=False, + ), + ] diff --git a/inventory/migrations/0008_cartransferlog_created_at_cartransferlog_is_approved_and_more.py b/inventory/migrations/0008_cartransferlog_created_at_cartransferlog_is_approved_and_more.py new file mode 100644 index 00000000..6f16a3ea --- /dev/null +++ b/inventory/migrations/0008_cartransferlog_created_at_cartransferlog_is_approved_and_more.py @@ -0,0 +1,35 @@ +# Generated by Django 4.2.17 on 2025-01-19 14:29 + +import datetime +from django.db import migrations, models + + +class Migration(migrations.Migration): + + dependencies = [ + ('inventory', '0007_remove_cartransferlog_cars_cartransferlog_car'), + ] + + operations = [ + migrations.AddField( + model_name='cartransferlog', + name='created_at', + field=models.DateTimeField(auto_now_add=True, default=datetime.datetime(2025, 1, 19, 14, 29, 29, 771881, tzinfo=datetime.timezone.utc), verbose_name='Created At'), + preserve_default=False, + ), + migrations.AddField( + model_name='cartransferlog', + name='is_approved', + field=models.BooleanField(default=False), + ), + migrations.AddField( + model_name='cartransferlog', + name='status', + field=models.CharField(default='pending', max_length=10, verbose_name=[('draft', 'Draft'), ('approved', 'Approved'), ('pending', 'Pending'), ('success', 'Success'), ('failure', 'Failure')]), + ), + migrations.AddField( + model_name='cartransferlog', + name='updated_at', + field=models.DateTimeField(auto_now=True, verbose_name='Updated At'), + ), + ] diff --git a/inventory/migrations/0009_cartransfer_delete_cartransferlog.py b/inventory/migrations/0009_cartransfer_delete_cartransferlog.py new file mode 100644 index 00000000..17a1981a --- /dev/null +++ b/inventory/migrations/0009_cartransfer_delete_cartransferlog.py @@ -0,0 +1,36 @@ +# Generated by Django 4.2.17 on 2025-01-19 14:31 + +from django.db import migrations, models +import django.db.models.deletion + + +class Migration(migrations.Migration): + + dependencies = [ + ('inventory', '0008_cartransferlog_created_at_cartransferlog_is_approved_and_more'), + ] + + operations = [ + migrations.CreateModel( + name='CarTransfer', + fields=[ + ('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')), + ('transfer_date', models.DateTimeField(auto_now_add=True, verbose_name='Transfer Date')), + ('remarks', models.TextField(blank=True, null=True, verbose_name='Remarks')), + ('status', models.CharField(default='draft', max_length=10, verbose_name=[('draft', 'Draft'), ('approved', 'Approved'), ('pending', 'Pending'), ('success', 'Success'), ('failure', 'Failure')])), + ('is_approved', models.BooleanField(default=False)), + ('created_at', models.DateTimeField(auto_now_add=True, verbose_name='Created At')), + ('updated_at', models.DateTimeField(auto_now=True, verbose_name='Updated At')), + ('car', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, related_name='transfer_logs', to='inventory.car', verbose_name='Car')), + ('from_dealer', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, related_name='transfers_out', to='inventory.dealer', verbose_name='From Dealer')), + ('to_dealer', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, related_name='transfers_in', to='inventory.dealer', verbose_name='To Dealer')), + ], + options={ + 'verbose_name': 'Car Transfer Log', + 'verbose_name_plural': 'Car Transfer Logs', + }, + ), + migrations.DeleteModel( + name='CarTransferLog', + ), + ] diff --git a/inventory/migrations/0010_cartransfer_active_alter_car_status.py b/inventory/migrations/0010_cartransfer_active_alter_car_status.py new file mode 100644 index 00000000..ca3eff35 --- /dev/null +++ b/inventory/migrations/0010_cartransfer_active_alter_car_status.py @@ -0,0 +1,23 @@ +# Generated by Django 4.2.17 on 2025-01-19 15:20 + +from django.db import migrations, models + + +class Migration(migrations.Migration): + + dependencies = [ + ('inventory', '0009_cartransfer_delete_cartransferlog'), + ] + + operations = [ + migrations.AddField( + model_name='cartransfer', + name='active', + field=models.BooleanField(default=True), + ), + migrations.AlterField( + model_name='car', + name='status', + field=models.CharField(choices=[('available', 'Available'), ('sold', 'Sold'), ('hold', 'Hold'), ('damaged', 'Damaged'), ('reserved', 'Reserved'), ('transfer', 'Transfer')], default='available', max_length=10, verbose_name='Status'), + ), + ] diff --git a/inventory/migrations/0011_alter_cartransfer_status.py b/inventory/migrations/0011_alter_cartransfer_status.py new file mode 100644 index 00000000..46056559 --- /dev/null +++ b/inventory/migrations/0011_alter_cartransfer_status.py @@ -0,0 +1,18 @@ +# Generated by Django 4.2.17 on 2025-01-20 08:11 + +from django.db import migrations, models + + +class Migration(migrations.Migration): + + dependencies = [ + ('inventory', '0010_cartransfer_active_alter_car_status'), + ] + + operations = [ + migrations.AlterField( + model_name='cartransfer', + name='status', + field=models.CharField(default='draft', max_length=10, verbose_name=[('draft', 'Draft'), ('approved', 'Approved'), ('pending', 'Pending'), ('accept', 'Accept'), ('success', 'Success'), ('failure', 'Failure')]), + ), + ] diff --git a/inventory/migrations/0012_cartransfer_quantity.py b/inventory/migrations/0012_cartransfer_quantity.py new file mode 100644 index 00000000..a459a94a --- /dev/null +++ b/inventory/migrations/0012_cartransfer_quantity.py @@ -0,0 +1,18 @@ +# Generated by Django 4.2.17 on 2025-01-20 08:21 + +from django.db import migrations, models + + +class Migration(migrations.Migration): + + dependencies = [ + ('inventory', '0011_alter_cartransfer_status'), + ] + + operations = [ + migrations.AddField( + model_name='cartransfer', + name='quantity', + field=models.IntegerField(default=1, verbose_name='Quantity'), + ), + ] diff --git a/inventory/migrations/0013_alter_cartransfer_status.py b/inventory/migrations/0013_alter_cartransfer_status.py new file mode 100644 index 00000000..c48106a3 --- /dev/null +++ b/inventory/migrations/0013_alter_cartransfer_status.py @@ -0,0 +1,18 @@ +# Generated by Django 4.2.17 on 2025-01-20 09:17 + +from django.db import migrations, models + + +class Migration(migrations.Migration): + + dependencies = [ + ('inventory', '0012_cartransfer_quantity'), + ] + + operations = [ + migrations.AlterField( + model_name='cartransfer', + name='status', + field=models.CharField(default='draft', max_length=10, verbose_name=[('draft', 'Draft'), ('approved', 'Approved'), ('pending', 'Pending'), ('accept', 'Accept'), ('success', 'Success'), ('reject', 'Reject')]), + ), + ] diff --git a/inventory/models.py b/inventory/models.py index 62efab07..3b708eed 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 @@ -29,66 +28,105 @@ 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 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) def __str__(self): return f"Rate: {self.rate}%" -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') +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") class CarMake(models.Model, LocalizedNameMixin): @@ -121,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) @@ -137,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) @@ -159,6 +201,7 @@ class CarEquipment(models.Model, LocalizedNameMixin): def __str__(self): return self.name + class Meta: verbose_name = "Equipment" @@ -167,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 @@ -179,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) @@ -194,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 @@ -205,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() @@ -218,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") - + RESERVED = "reserved", _("Reserved") + TRANSFER = "transfer", _("Transfer") class CarStockTypeChoices(models.TextChoices): NEW = "new", _("New") @@ -235,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") @@ -326,13 +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")) @@ -341,53 +475,64 @@ 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 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() 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): - 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 @@ -447,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")) @@ -461,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") @@ -540,19 +681,28 @@ 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") verbose_name_plural = _("Subscriptions") @@ -578,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) @@ -603,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")) @@ -648,14 +811,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": @@ -669,18 +832,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") @@ -697,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")) @@ -725,6 +890,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") @@ -740,6 +906,7 @@ class Status(models.TextChoices): QUALIFIED = "qualified", _("Qualified") CANCELED = "canceled", _("Canceled") + class Title(models.TextChoices): MR = "mr", _("Mr") MRS = "mrs", _("Mrs") @@ -752,6 +919,7 @@ class Title(models.TextChoices): COMPANY = "company", _("Company") NA = "na", _("N/A") + class ActionChoices(models.TextChoices): CALL = "call", _("Call") SMS = "sms", _("SMS") @@ -768,6 +936,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") @@ -775,6 +944,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") @@ -782,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")) @@ -810,33 +996,48 @@ 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")) class Meta: verbose_name = _("Organization") verbose_name_plural = _("Organizations") + def __str__(self): return self.name 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") @@ -848,18 +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: @@ -871,10 +1111,18 @@ class Lead(models.Model): 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: @@ -891,12 +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")) @@ -914,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")) @@ -931,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")) @@ -947,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")) @@ -955,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 @@ -990,7 +1265,6 @@ class Vendor(models.Model, LocalizedNameMixin): return self.name - class SaleQuotation(models.Model): quotation_number = models.CharField(max_length=10, unique=True) @@ -1002,7 +1276,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, @@ -1022,35 +1296,47 @@ 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")) + 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"] - + @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": @@ -1067,11 +1353,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) @@ -1079,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: @@ -1094,16 +1380,13 @@ 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 def finance(self): return self.car.finances + @property def financial_details(self): """ @@ -1132,6 +1415,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): """ @@ -1164,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): @@ -1192,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")) @@ -1213,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/inventory/signals.py b/inventory/signals.py index 2cc18ff8..3d9f36c3 100644 --- a/inventory/signals.py +++ b/inventory/signals.py @@ -155,19 +155,7 @@ def create_ledger_entity(sender, instance, created, **kwargs): balance_type="debit", active=True, ) - - # Inventory Account - asset_ca_inventory = entity.create_account( - coa_model=coa, - code="1106", - role=roles.ASSET_CA_INVENTORY, - name=_("Inventory"), - balance_type="debit", - active=True, - ) - asset_ca_inventory.role_default = True - asset_ca_inventory.save() - + # VAT Payable Account liability_ltl_vat_receivable = entity.create_account( coa_model=coa, @@ -337,7 +325,7 @@ def create_ledger_entity(sender, instance, created, **kwargs): # Mortgage Payable Account liability_ltl_mortgage_payable = entity.create_account( coa_model=coa, - code="2202", + code="2203", role=roles.LIABILITY_LTL_MORTGAGE_PAYABLE, name=_("Mortgage Payable"), balance_type="credit", diff --git a/inventory/urls.py b/inventory/urls.py index aefc18a4..db92d718 100644 --- a/inventory/urls.py +++ b/inventory/urls.py @@ -193,9 +193,29 @@ urlpatterns = [ ), path( "cars//location/update/", - views.CarLocationUpdateView.as_view(), + views.CarTransferCreateView.as_view(), name="transfer", - ), + ), + path( + "cars//location/detail/", + views.CarTransferDetailView, + name="transfer_detail", + ), + path( + "cars//location//transfer_approve/", + views.car_transfer_approve, + name="transfer_confirm", + ), + path( + "cars//location//transfer_accept_reject/", + views.car_transfer_accept_reject, + name="transfer_accept_reject", + ), + path( + "cars//location//preview/", + views.CarTransferPreviewView, + name="transfer_preview", + ), path("cars/inventory/search/", views.SearchCodeView.as_view(), name="car_search"), # path('cars//colors//update/',views.CarColorUpdateView.as_view(),name='color_update'), path("cars/reserve//", views.reserve_car_view, name="reserve_car"), diff --git a/inventory/utils.py b/inventory/utils.py index 0fb8acfb..117e8957 100644 --- a/inventory/utils.py +++ b/inventory/utils.py @@ -1,6 +1,9 @@ +import json +import datetime from django.shortcuts import redirect from django.contrib import messages from django.utils import timezone +from django_ledger.models.entity import UnitOfMeasureModel from django_ledger.models.journal_entry import JournalEntryModel from django_ledger.models.ledger import LedgerModel from django_ledger.models.transactions import TransactionModel @@ -11,7 +14,7 @@ from django.core.mail import send_mail from django.utils.translation import gettext_lazy as _ from inventory.utilities.financials import get_financial_value from django_ledger.models.items import ItemModel -from django_ledger.models import InvoiceModel, EstimateModel +from django_ledger.models import InvoiceModel, EstimateModel,BillModel from decimal import Decimal @@ -253,18 +256,10 @@ def set_invoice_payment(dealer, entity, invoice, amount, payment_method): def set_bill_payment(dealer, entity, bill, amount, payment_method): - vat_amount = 0 total_amount = 0 + for x in bill.get_itemtxs_data()[0].all(): + total_amount += Decimal(x.unit_cost) * Decimal(x.quantity) - if bill.terms == "on_receipt": - for x in bill.get_itemtxs_data()[0].all(): - vat_amount += models.Car.objects.get( - vin=x.item_model.name - ).finances.cost_price * Decimal(x.quantity) - total_amount += Decimal(x.unit_cost) * Decimal(x.quantity) - - # grand_total = total_amount - Decimal(vat_amount) - journal = JournalEntryModel.objects.create( posted=False, description=f"Payment for bill {bill.bill_number}", @@ -279,9 +274,7 @@ def set_bill_payment(dealer, entity, bill, amount, payment_method): name="Accounts Payable", active=True ) - # vat_payable_account = entity.get_default_coa_accounts().get( - # name="VAT Payable", active=True - # ) + TransactionModel.objects.create( journal_entry=journal, account=cash_account, # Debit Cash @@ -298,14 +291,182 @@ def set_bill_payment(dealer, entity, bill, amount, payment_method): description="Payment Received", ) - # if vat_amount > 0: - # TransactionModel.objects.create( - # journal_entry=journal, - # account=vat_payable_account, # Credit VAT Payable - # amount=vat_amount, - # tx_type="credit", - # description="VAT Payable on bill", - # ) - bill.make_payment(amount) bill.save() + + +def transfer_to_dealer(request,cars, to_dealer, remarks=None): + dealer = get_user_type(request) + + if not cars: + raise ValueError("No cars selected for transfer.") + + from_dealer = cars[0].dealer # Assume all cars are from the same dealer + + # Validate that all cars are from the same dealer + for car in cars: + if car.dealer != from_dealer: + raise ValueError("All cars must be from the same dealer.") + + if from_dealer == to_dealer: + raise ValueError("Cannot transfer cars to the same dealer.") + + # Log the transfer + transfer_log = models.CarTransferLog.objects.create( + from_dealer=from_dealer, + to_dealer=to_dealer, + remarks=remarks, + ) + transfer_log.cars.set(cars) # Associate the cars with the transfer log + + # Update the dealer for all cars + for car in cars: + car.dealer = to_dealer + car.save() + +import random +def transfer_car(car,transfer): + from_dealer = transfer.from_dealer + to_dealer = transfer.to_dealer + # add transfer.to_dealer as customer in transfer.from_dealer entity + instance = models.Customer.objects.filter( + dealer=from_dealer, + email=to_dealer.user.email, + ).first() + if not instance: + instance = models.Customer.objects.create( + dealer=from_dealer, + title=models.Title.MR, + email=to_dealer.user.email, + first_name=to_dealer.user.first_name, + last_name=to_dealer.user.last_name, + phone_number=to_dealer.phone_number, + address=to_dealer.address, + national_id=f"{random.randint(100, 9999)}", + dob="1990-01-01", + ) + + # create invoice from transfer.from_dealer to transfer.to_dealer + name = f"{instance.first_name} {instance.middle_name} {instance.last_name}" + customer = from_dealer.entity.get_customers().filter(customer_name=name).first() + + invoice = from_dealer.entity.create_invoice( + customer_model=customer, + terms=InvoiceModel.TERMS_NET_30, + cash_account=from_dealer.entity.get_default_coa_accounts().get(name="Cash", active=True), + prepaid_account=from_dealer.entity.get_default_coa_accounts().get(name="Accounts Receivable", active=True), + coa_model=from_dealer.entity.get_default_coa(), + ) + + ledger = from_dealer.entity.create_ledger(name=str(invoice.pk)) + invoice.ledgar = ledger + ledger.invoicemodel = invoice + ledger.save() + invoice.save() + item = from_dealer.entity.get_items_products().filter(name=car.vin).first() + if not item: + return + + invoice_itemtxs = { + item.item_number: { + "unit_cost": car.finances.cost_price, + "quantity": transfer.quantity, + "total_amount": transfer.total_price, + } + } + + invoice_itemtxs = invoice.migrate_itemtxs( + itemtxs=invoice_itemtxs, + commit=True, + operation=InvoiceModel.ITEMIZE_APPEND, + ) + + invoice.mark_as_review() + invoice.mark_as_approved(from_dealer.entity.slug, from_dealer.entity.admin) + invoice.mark_as_paid(from_dealer.entity.slug, from_dealer.entity.admin) + invoice.save() + + #create car item product in to_dealer entity + uom = to_dealer.entity.get_uom_all().filter(name=item.uom.name).first() + + product = to_dealer.entity.create_item_product( + name=item.name, + uom_model=uom, + item_type=item.item_type, + coa_model=to_dealer.entity.get_default_coa(), + ) + + car_dict = vars(car).copy() + del car_dict["_state"] + for key, value in car_dict.items(): + if isinstance(value, datetime.datetime): + car_dict[key] = value.strftime('%Y-%m-%d %H:%M:%S') + product.additional_info = json.dumps({"car_info": car_dict}) + product.save() + + #add the sender as vendor and create a bill for it + vendor_instance, created = models.Vendor.objects.get_or_create( + dealer=to_dealer, + crn=from_dealer.crn, + vrn=from_dealer.vrn, + name=from_dealer.name, + email=from_dealer.user.email, + arabic_name=from_dealer.arabic_name, + address=from_dealer.address, + phone_number=from_dealer.phone_number, + contact_person='', + ) + + #transfer the car to to_dealer and create items record + + vendor = to_dealer.entity.get_vendors().filter(vendor_name=vendor_instance.name).first() + + bill = to_dealer.entity.create_bill( + vendor_model=vendor, + terms=BillModel.TERMS_NET_30, + cash_account=to_dealer.entity.get_default_coa_accounts().get(name="Cash", active=True), + prepaid_account=to_dealer.entity.get_default_coa_accounts().get(name="Prepaid Expenses", active=True), + coa_model=to_dealer.entity.get_default_coa(), + ) + + bill_itemtxs = { + item.item_number: { + "unit_cost": car.finances.cost_price, + "quantity": transfer.quantity, + "total_amount": transfer.total_price, + } + } + + bill_itemtxs = bill.migrate_itemtxs(itemtxs=bill_itemtxs, + commit=True, + operation=BillModel.ITEMIZE_REPLACE) + + + car.dealer = to_dealer + car.vendor = vendor_instance + car.receiving_date = datetime.datetime.now() + car.finances.additional_services.clear() + if hasattr(car, "custom_cards"): + car.custom_cards.delete() + # car.finances.cost_price = 0 + car.finances.selling_price = 0 + car.finances.discount_amount = 0 + car.finances.save() + car.location.owner = to_dealer + car.location.showroom = to_dealer + car.location.description = "" + car.location.save() + + # car.reservations.all().delete() + car.status = models.CarStatusChoices.AVAILABLE + transfer.status = models.CarTransferStatusChoices.success + transfer.active = False + transfer.save() + car.save() + + return True + #pay the pill + # set_bill_payment(to_dealer,to_dealer.entity,bill,transfer.total_price,"credit") + + + \ No newline at end of file diff --git a/inventory/views.py b/inventory/views.py index e96b97a1..0ed4a537 100644 --- a/inventory/views.py +++ b/inventory/views.py @@ -83,6 +83,7 @@ from .utils import ( get_user_type, set_bill_payment, set_invoice_payment, + transfer_car, ) from django.contrib.auth.models import User from allauth.account import views @@ -777,6 +778,112 @@ class CarLocationUpdateView(UpdateView): def get_success_url(self): return reverse_lazy("car_detail", kwargs={"pk": self.object.car.pk}) +class CarTransferCreateView(CreateView): + model = models.CarTransfer + form_class = forms.CarTransferForm + template_name = "inventory/car_location_form.html" + + def get_form(self, form_class = None): + form = super().get_form(form_class) + form.fields['to_dealer'].queryset = models.Dealer.objects.exclude(pk=get_user_type(self.request).pk).all() + form.fields['car'].queryset = models.Car.objects.filter(pk=self.kwargs["pk"]) + return form + def get_initial(self): + initial = super().get_initial() + initial["car"] = get_object_or_404(models.Car, pk=self.kwargs["pk"]) + return initial + + def form_valid(self, form): + form.instance.from_dealer = get_user_type(self.request) + form.instance.car.status = "transfer" + form.instance.car.save() + return super().form_valid(form) + + def get_success_url(self): + return reverse_lazy("car_detail", kwargs={"pk": self.object.car.pk}) + +def CarTransferDetailView(request, pk): + transfer = get_object_or_404(models.CarTransfer, pk=pk) + context = {"transfer":transfer} + return render(request,'inventory/transfer_details.html',context) + +def car_transfer_approve(request, car_pk,transfer_pk): + car = get_object_or_404(models.Car, pk=car_pk) + transfer = get_object_or_404(models.CarTransfer, pk=transfer_pk) + transfer.status = "approved" + transfer.save() + url = request.build_absolute_uri(reverse('transfer_preview',kwargs={'car_pk':car.pk,"transfer_pk":transfer.pk})) + models.Notification.objects.create( + user=transfer.to_dealer.user, + message=f"Car transfer request from {transfer.from_dealer} is waiting for your acceptance. Accept", + ) + messages.success(request, _("Car transfer approved successfully.")) + return redirect("car_detail", pk=car.pk) + +def car_transfer_accept_reject(request, car_pk,transfer_pk): + car = get_object_or_404(models.Car, pk=car_pk) + transfer = get_object_or_404(models.CarTransfer, pk=transfer_pk) + status = request.GET.get("status") + if status == "rejected": + transfer.status = "reject" + transfer.active = False + messages.success(request, _("Car transfer rejected successfully.")) + transfer.save() + elif status == "accepted": + transfer.status = "accept" + transfer.save() + success = transfer_car(car,transfer) + if success: + messages.success(request, _("Car Transfer Completed successfully.")) + return redirect("inventory_stats") + +def CarTransferPreviewView(request, car_pk,transfer_pk): + transfer = get_object_or_404(models.CarTransfer, pk=transfer_pk) + if transfer.to_dealer != get_user_type(request): + return redirect("car_detail", pk=car_pk) + return render(request,'inventory/transfer_preview.html',{"transfer":transfer}) + # def get_context_data(self, **kwargs): + # estimate = kwargs.get("object") + # if estimate.get_itemtxs_data(): + # data = get_financial_values(estimate) + + # kwargs["vat_amount"] = data["vat_amount"] + # kwargs["total"] = data["grand_total"] + # kwargs["discount_amount"] = data["discount_amount"] + # kwargs["vat"] = data["vat"] + # kwargs["car_and_item_info"] = data["car_and_item_info"] + # kwargs["additional_services"] = data["additional_services"] + # return super().get_context_data(**kwargs) + + + + +# class CarTransferView(View): +# template_name = "inventory/car_location_form.html" + +# def get(self, request, *args, **kwargs): +# form = forms.CarTransferForm() +# car = models.Car.objects.filter(pk=self.kwargs["pk"]) +# form.fields['to_dealer'].queryset = form.fields['to_dealer'].queryset.exclude(pk=get_user_type(request).pk) +# form.fields['car'].queryset = car +# form.initial['car'] = car.first() +# context = {"form": form} +# return render(request, self.template_name,context) + +# def post(self, request, *args, **kwargs): +# form = forms.CarTransferForm(request.POST) +# if form.is_valid(): +# from_dealer = get_user_type(request) +# car = form.cleaned_data['car'] +# to_dealer = form.cleaned_data['to_dealer'] +# remarks = form.cleaned_data['remarks'] +# models.CarTransferLog.objects.create(car=car, from_dealer=from_dealer, to_dealer=to_dealer, remarks=remarks) +# # car = models.Car.objects.filter(pk=self.kwargs["pk"]) +# # form.instance.car = car.first() +# # form.instance.to_dealer = get_user_type(request) +# # form.save() +# # messages.success(request, "Car transfered successfully.") +# return redirect("car_detail", pk=self.kwargs["pk"]) class CustomCardCreateView(LoginRequiredMixin, CreateView): model = models.CustomCard diff --git a/templates/crm/notifications_history.html b/templates/crm/notifications_history.html index d7eb831b..3367144e 100644 --- a/templates/crm/notifications_history.html +++ b/templates/crm/notifications_history.html @@ -13,7 +13,7 @@

{{ _("System")}}:

{% if not notification.is_read %} -

{{ notification.message }} {{ notification.created|timesince }}

+

{{ notification.message|safe }} {{ notification.created|timesince }}

{% else %}

{{ notification.message }} {{ notification.created|timesince }}

{% endif %} diff --git a/templates/header.html b/templates/header.html index 994ce1e0..cb72d234 100644 --- a/templates/header.html +++ b/templates/header.html @@ -339,7 +339,7 @@
-{% if user.dealer.logo %} + {% if user.dealer.logo %} {% elif user.staff.dealer.logo %} diff --git a/templates/inventory/car_detail.html b/templates/inventory/car_detail.html index df18cefc..59003cda 100644 --- a/templates/inventory/car_detail.html +++ b/templates/inventory/car_detail.html @@ -93,18 +93,21 @@ {% endif %} + {% trans 'Location'|capfirst %} - {% if car.location %} {% if car.location.is_owner_showroom %} {% trans 'Our Showroom' %} {% else %} {{ car.location.showroom.get_local_name }} {% endif %} - - {% trans "transfer"|capfirst %} - - {% else %} {% trans "No location available." %} - - {% trans "Add" %} - + {% if car.finances and not car.get_transfer %} + {% if car.location %} {% if car.location.is_owner_showroom %} {% trans 'Our Showroom' %} {% else %} {{ car.location.showroom.get_local_name }} {% endif %} + + {% trans "transfer"|capfirst %} + + {% else %} {% trans "No location available." %} + + {% trans "Add" %} + + {% endif %} + {% endif %} - {% endif %}
@@ -215,6 +218,7 @@
+ {% if car.status != 'transfer' %}

{% trans 'Reservations Details' %}

@@ -266,6 +270,50 @@
+ {% endif %} + + {% if car.status == 'transfer' and car.get_transfer %} +
+

{% trans 'Transfer Details' %}

+
+
+ + + + + + + + + + + + + + + + + + + + + + +
{% trans "Action" %}{% trans "Status" %}{% trans "From Showroom" %}{% trans "To Showroom" %}{% trans 'Date' %}
Transfer + {% if car.get_transfer.status == "draft" %} + waiting for approval + {% elif car.get_transfer.status == "approved" %} + waiting for dealer acceptance + {% endif %} + {{ car.get_transfer.from_dealer }}{{ car.get_transfer.to_dealer }}{{ car.get_transfer.transfer_date }} + {% if car.get_transfer.status == "draft" %} + Approve + {% endif %} +
+
+
+
+ {% endif %} diff --git a/templates/inventory/transfer_details.html b/templates/inventory/transfer_details.html new file mode 100644 index 00000000..9dadec25 --- /dev/null +++ b/templates/inventory/transfer_details.html @@ -0,0 +1,47 @@ +{% extends 'base.html' %} +{% load crispy_forms_filters %} +{% load i18n %} +{% load custom_filters %} + +{% block title %} + {% trans 'Car Transfer Details' %} +{% endblock %} + +{% block content %} + + +
+
+
+
+
+
+ + {% trans 'Cancel' %} +
+
+
+ + +{% endblock %} diff --git a/templates/inventory/transfer_preview.html b/templates/inventory/transfer_preview.html new file mode 100644 index 00000000..6086ffc0 --- /dev/null +++ b/templates/inventory/transfer_preview.html @@ -0,0 +1,339 @@ +{% load static i18n %} + + + + + + transfer + + + + + + + {% if LANGUAGE_CODE == 'en' %} + + + {% else %} + + + {% endif %} + + + +{% if transfer.status != "approved" %} +
+
+
+
+
+
+
+

Page Missing!

+

But no worries! Our ostrich is looking everywhere
+

+
+
+
+
+ +
+
+
+ + + +
+
+ +
+
+{% else%} +
+ + + +
+ + + + + + + +
+ +
+ + + +

{% trans "Transfer" %}

+

{% trans "Thank you for choosing us. We appreciate your business" %}

+
+ + +
+

{% trans "Date" %} : {{transfer.created_at}}

+

{% trans "From" %} : {{transfer.from_dealer}}

+

{% trans "To" %} : {{transfer.to_dealer}}

+
+ + +
+ + + + + + + + + + + + + + + + + +
{% trans "Item" %}{% trans "Quantity" %}{% trans "Unit Price" %}{% trans "Total" %}
{{ transfer.car }}{{ transfer.quantity }}{{ transfer.car.finances.cost_price }}{{ transfer.total_price }}
+
+ + + +
+

{%trans "Total Amount" %}: ${{transfer.total_price}}

+
+ + + +
+ {% endif %} + + + + + + + + \ No newline at end of file