From 3f89935d1ba833d2a1411a7e85e948fdff1f489b Mon Sep 17 00:00:00 2001 From: = <=> Date: Thu, 20 Mar 2025 20:37:39 +0300 Subject: [PATCH 1/2] update --- haikalbot/migrations/0001_initial.py | 2 +- haikalbot/migrations/0002_initial.py | 6 +- inventory/apps.py | 10 +- inventory/migrations/0001_initial.py | 868 +++++++++--------- .../0002_alter_saleorder_payment_method.py | 18 - inventory/models.py | 2 - inventory/signals.py | 27 +- inventory/views.py | 31 +- requirements.txt | 2 - scripts/generate.py | 11 +- 10 files changed, 484 insertions(+), 493 deletions(-) delete mode 100644 inventory/migrations/0002_alter_saleorder_payment_method.py diff --git a/haikalbot/migrations/0001_initial.py b/haikalbot/migrations/0001_initial.py index 40c4a0ce..9c202e5c 100644 --- a/haikalbot/migrations/0001_initial.py +++ b/haikalbot/migrations/0001_initial.py @@ -1,4 +1,4 @@ -# Generated by Django 5.1.6 on 2025-03-06 01:43 +# Generated by Django 4.2.20 on 2025-03-20 17:15 from django.db import migrations, models diff --git a/haikalbot/migrations/0002_initial.py b/haikalbot/migrations/0002_initial.py index 2ef50ed1..62877afe 100644 --- a/haikalbot/migrations/0002_initial.py +++ b/haikalbot/migrations/0002_initial.py @@ -1,7 +1,7 @@ -# Generated by Django 5.1.6 on 2025-03-06 01:43 +# Generated by Django 4.2.20 on 2025-03-20 17:15 -import django.db.models.deletion from django.db import migrations, models +import django.db.models.deletion class Migration(migrations.Migration): @@ -9,8 +9,8 @@ class Migration(migrations.Migration): initial = True dependencies = [ - ('haikalbot', '0001_initial'), ('inventory', '0001_initial'), + ('haikalbot', '0001_initial'), ] operations = [ diff --git a/inventory/apps.py b/inventory/apps.py index 9a82aef1..6e8c8729 100644 --- a/inventory/apps.py +++ b/inventory/apps.py @@ -5,8 +5,8 @@ class InventoryConfig(AppConfig): name = 'inventory' def ready(self): - import inventory.signals - # from decimal import Decimal - # from inventory.models import VatRate - # VatRate.objects.get_or_create(rate=Decimal('0.15'), is_active=True) - + import inventory.signals + from decimal import Decimal + from inventory.models import VatRate + VatRate.objects.get_or_create(rate=Decimal('0.15'), is_active=True) + diff --git a/inventory/migrations/0001_initial.py b/inventory/migrations/0001_initial.py index e76d84e3..1c658e0f 100644 --- a/inventory/migrations/0001_initial.py +++ b/inventory/migrations/0001_initial.py @@ -1,13 +1,13 @@ -# Generated by Django 5.1.6 on 2025-03-06 01:43 +# Generated by Django 4.2.20 on 2025-03-20 17:15 import datetime +from decimal import Decimal +from django.conf import settings +from django.db import migrations, models import django.db.models.deletion import inventory.mixins import inventory.models import phonenumber_field.modelfields -from decimal import Decimal -from django.conf import settings -from django.db import migrations, models class Migration(migrations.Migration): @@ -15,20 +15,55 @@ class Migration(migrations.Migration): initial = True dependencies = [ - ('appointment', '__first__'), - ('auth', '0012_alter_user_first_name_max_length'), + migrations.swappable_dependency(settings.DJANGO_LEDGER_INVOICE_MODEL), ('contenttypes', '0002_remove_content_type_name'), + migrations.swappable_dependency(settings.DJANGO_LEDGER_CUSTOMER_MODEL), + migrations.swappable_dependency(settings.DJANGO_LEDGER_ESTIMATE_MODEL), migrations.swappable_dependency(settings.AUTH_USER_MODEL), migrations.swappable_dependency(settings.DJANGO_LEDGER_ACCOUNT_MODEL), - migrations.swappable_dependency(settings.DJANGO_LEDGER_CUSTOMER_MODEL), migrations.swappable_dependency(settings.DJANGO_LEDGER_ENTITY_MODEL), - migrations.swappable_dependency(settings.DJANGO_LEDGER_ESTIMATE_MODEL), - migrations.swappable_dependency(settings.DJANGO_LEDGER_INVOICE_MODEL), - migrations.swappable_dependency(settings.DJANGO_LEDGER_ITEM_MODEL), migrations.swappable_dependency(settings.DJANGO_LEDGER_VENDOR_MODEL), + migrations.swappable_dependency(settings.DJANGO_LEDGER_ITEM_MODEL), + ('auth', '0012_alter_user_first_name_max_length'), + ('appointment', '__first__'), ] operations = [ + migrations.CreateModel( + name='AdditionalServices', + fields=[ + ('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')), + ('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(decimal_places=2, max_digits=14, verbose_name='Price')), + ('taxable', models.BooleanField(default=False, verbose_name='taxable')), + ('uom', models.CharField(choices=[('EA', 'Each'), ('PR', 'Pair'), ('SET', 'Set'), ('GAL', 'Gallon'), ('L', 'Liter'), ('M', 'Meter'), ('KG', 'Kilogram'), ('HR', 'Hour'), ('BX', 'Box'), ('RL', 'Roll'), ('PKG', 'Package'), ('DZ', 'Dozen'), ('SQ_M', 'Square Meter'), ('PC', 'Piece'), ('BDL', 'Bundle')], max_length=10, verbose_name='Unit of Measurement')), + ], + options={ + 'verbose_name': 'Additional Services', + 'verbose_name_plural': 'Additional Services', + }, + bases=(models.Model, inventory.mixins.LocalizedNameMixin), + ), + migrations.CreateModel( + name='Car', + fields=[ + ('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')), + ('vin', models.CharField(max_length=17, unique=True, verbose_name='VIN')), + ('year', models.IntegerField(verbose_name='Year')), + ('status', models.CharField(choices=[('available', 'Available'), ('sold', 'Sold'), ('hold', 'Hold'), ('damaged', 'Damaged'), ('reserved', 'Reserved'), ('transfer', 'Transfer')], default='available', max_length=10, verbose_name='Status')), + ('stock_type', models.CharField(choices=[('new', 'New'), ('used', 'Used')], default='new', max_length=10, verbose_name='Stock Type')), + ('remarks', models.TextField(blank=True, null=True, verbose_name='Remarks')), + ('mileage', models.IntegerField(blank=True, null=True, verbose_name='Mileage')), + ('receiving_date', models.DateTimeField(verbose_name='Receiving Date')), + ('hash', models.CharField(blank=True, max_length=64, null=True, verbose_name='Hash')), + ], + options={ + 'verbose_name': 'Car', + 'verbose_name_plural': 'Cars', + }, + ), migrations.CreateModel( name='CarEquipment', fields=[ @@ -57,110 +92,6 @@ class Migration(migrations.Migration): }, bases=(models.Model, inventory.mixins.LocalizedNameMixin), ), - migrations.CreateModel( - name='ExteriorColors', - fields=[ - ('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')), - ('name', models.CharField(max_length=255, verbose_name='Name')), - ('arabic_name', models.CharField(max_length=255, verbose_name='Arabic Name')), - ('rgb', models.CharField(blank=True, max_length=24, null=True, verbose_name='RGB')), - ], - options={ - 'verbose_name': 'Exterior Colors', - 'verbose_name_plural': 'Exterior Colors', - }, - bases=(models.Model, inventory.mixins.LocalizedNameMixin), - ), - migrations.CreateModel( - name='InteriorColors', - fields=[ - ('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')), - ('name', models.CharField(max_length=255, verbose_name='Name')), - ('arabic_name', models.CharField(max_length=255, verbose_name='Arabic Name')), - ('rgb', models.CharField(blank=True, max_length=24, null=True, verbose_name='RGB')), - ], - options={ - 'verbose_name': 'Interior Colors', - 'verbose_name_plural': 'Interior Colors', - }, - bases=(models.Model, inventory.mixins.LocalizedNameMixin), - ), - migrations.CreateModel( - name='Payment', - fields=[ - ('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')), - ('amount', models.DecimalField(decimal_places=2, max_digits=10, verbose_name='amount')), - ('payment_method', models.CharField(choices=[('cash', 'cash'), ('credit', 'credit'), ('transfer', 'transfer'), ('debit', 'debit'), ('SADAD', 'SADAD')], max_length=50, verbose_name='method')), - ('reference_number', models.CharField(blank=True, max_length=100, null=True, verbose_name='reference number')), - ('payment_date', models.DateField(auto_now_add=True, verbose_name='date')), - ], - options={ - 'verbose_name': 'payment', - 'verbose_name_plural': 'payments', - }, - ), - migrations.CreateModel( - name='VatRate', - fields=[ - ('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')), - ('rate', models.DecimalField(decimal_places=2, default=Decimal('0.15'), max_digits=5)), - ('is_active', models.BooleanField(default=True)), - ('created_at', models.DateTimeField(auto_now_add=True)), - ], - ), - migrations.CreateModel( - name='AdditionalServices', - fields=[ - ('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')), - ('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(decimal_places=2, max_digits=14, verbose_name='Price')), - ('taxable', models.BooleanField(default=False, verbose_name='taxable')), - ('uom', models.CharField(choices=[('EA', 'Each'), ('PR', 'Pair'), ('SET', 'Set'), ('GAL', 'Gallon'), ('L', 'Liter'), ('M', 'Meter'), ('KG', 'Kilogram'), ('HR', 'Hour'), ('BX', 'Box'), ('RL', 'Roll'), ('PKG', 'Package'), ('DZ', 'Dozen'), ('SQ_M', 'Square Meter'), ('PC', 'Piece'), ('BDL', 'Bundle')], max_length=10, verbose_name='Unit of Measurement')), - ('item', models.OneToOneField(blank=True, null=True, on_delete=django.db.models.deletion.CASCADE, to=settings.DJANGO_LEDGER_ITEM_MODEL, verbose_name='Item')), - ], - options={ - 'verbose_name': 'Additional Services', - 'verbose_name_plural': 'Additional Services', - }, - bases=(models.Model, inventory.mixins.LocalizedNameMixin), - ), - migrations.CreateModel( - name='Car', - fields=[ - ('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')), - ('vin', models.CharField(max_length=17, unique=True, verbose_name='VIN')), - ('year', models.IntegerField(verbose_name='Year')), - ('status', models.CharField(choices=[('available', 'Available'), ('sold', 'Sold'), ('hold', 'Hold'), ('damaged', 'Damaged'), ('reserved', 'Reserved'), ('transfer', 'Transfer')], default='available', max_length=10, verbose_name='Status')), - ('stock_type', models.CharField(choices=[('new', 'New'), ('used', 'Used')], default='new', max_length=10, verbose_name='Stock Type')), - ('remarks', models.TextField(blank=True, null=True, verbose_name='Remarks')), - ('mileage', models.IntegerField(blank=True, null=True, verbose_name='Mileage')), - ('receiving_date', models.DateTimeField(verbose_name='Receiving Date')), - ('hash', models.CharField(blank=True, max_length=64, null=True, verbose_name='Hash')), - ('vendor', models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.DO_NOTHING, related_name='cars', to=settings.DJANGO_LEDGER_VENDOR_MODEL, verbose_name='Vendor')), - ('id_car_make', models.ForeignKey(blank=True, db_column='id_car_make', null=True, on_delete=django.db.models.deletion.DO_NOTHING, to='inventory.carmake', verbose_name='Make')), - ], - options={ - 'verbose_name': 'Car', - 'verbose_name_plural': 'Cars', - }, - ), - migrations.CreateModel( - name='CarFinance', - fields=[ - ('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')), - ('cost_price', models.DecimalField(decimal_places=2, max_digits=14, verbose_name='Cost Price')), - ('selling_price', models.DecimalField(decimal_places=2, max_digits=14, verbose_name='Selling Price')), - ('discount_amount', models.DecimalField(decimal_places=2, default=Decimal('0.00'), max_digits=14, verbose_name='Discount Amount')), - ('additional_services', models.ManyToManyField(blank=True, related_name='additional_finances', to='inventory.additionalservices')), - ('car', models.OneToOneField(on_delete=django.db.models.deletion.CASCADE, related_name='finances', to='inventory.car')), - ], - options={ - 'verbose_name': 'Car Financial Details', - 'verbose_name_plural': 'Car Financial Details', - }, - ), migrations.CreateModel( name='CarModel', fields=[ @@ -174,11 +105,6 @@ class Migration(migrations.Migration): }, bases=(models.Model, inventory.mixins.LocalizedNameMixin), ), - migrations.AddField( - model_name='car', - name='id_car_model', - field=models.ForeignKey(blank=True, db_column='id_car_model', null=True, on_delete=django.db.models.deletion.DO_NOTHING, to='inventory.carmodel', verbose_name='Model'), - ), migrations.CreateModel( name='CarOption', fields=[ @@ -192,36 +118,6 @@ class Migration(migrations.Migration): }, bases=(models.Model, inventory.mixins.LocalizedNameMixin), ), - migrations.CreateModel( - name='CarOptionValue', - fields=[ - ('id_car_option_value', models.AutoField(primary_key=True, serialize=False)), - ('value', models.CharField(max_length=500)), - ('unit', models.CharField(blank=True, max_length=255, null=True)), - ('is_base', models.IntegerField()), - ('id_car_equipment', models.ForeignKey(db_column='id_car_equipment', on_delete=django.db.models.deletion.DO_NOTHING, to='inventory.carequipment')), - ('id_car_option', models.ForeignKey(db_column='id_car_option', on_delete=django.db.models.deletion.DO_NOTHING, to='inventory.caroption')), - ], - options={ - 'verbose_name': 'Option Value', - }, - ), - migrations.CreateModel( - name='CarRegistration', - fields=[ - ('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')), - ('plate_number', models.IntegerField(verbose_name='Plate Number')), - ('text1', models.CharField(max_length=1, verbose_name='Text 1')), - ('text2', models.CharField(blank=True, max_length=1, null=True, verbose_name='Text 2')), - ('text3', models.CharField(blank=True, max_length=1, null=True, verbose_name='Text 3')), - ('registration_date', models.DateTimeField(verbose_name='Registration Date')), - ('car', models.OneToOneField(on_delete=django.db.models.deletion.CASCADE, related_name='registrations', to='inventory.car', verbose_name='Car')), - ], - options={ - 'verbose_name': 'Registration', - 'verbose_name_plural': 'Registrations', - }, - ), migrations.CreateModel( name='CarSerie', fields=[ @@ -238,11 +134,6 @@ class Migration(migrations.Migration): }, bases=(models.Model, inventory.mixins.LocalizedNameMixin), ), - migrations.AddField( - model_name='car', - name='id_car_serie', - field=models.ForeignKey(blank=True, db_column='id_car_serie', null=True, on_delete=django.db.models.deletion.DO_NOTHING, to='inventory.carserie', verbose_name='Series'), - ), migrations.CreateModel( name='CarSpecification', fields=[ @@ -256,57 +147,6 @@ class Migration(migrations.Migration): }, bases=(models.Model, inventory.mixins.LocalizedNameMixin), ), - migrations.CreateModel( - name='CarTrim', - fields=[ - ('id_car_trim', models.AutoField(primary_key=True, serialize=False)), - ('name', models.CharField(blank=True, max_length=255, null=True)), - ('arabic_name', models.CharField(blank=True, max_length=255, null=True)), - ('start_production_year', models.IntegerField(blank=True, null=True)), - ('end_production_year', models.IntegerField(blank=True, null=True)), - ('id_car_serie', models.ForeignKey(db_column='id_car_serie', on_delete=django.db.models.deletion.DO_NOTHING, to='inventory.carserie')), - ], - options={ - 'verbose_name': 'Trim', - }, - bases=(models.Model, inventory.mixins.LocalizedNameMixin), - ), - migrations.CreateModel( - name='CarSpecificationValue', - fields=[ - ('id_car_specification_value', models.AutoField(primary_key=True, serialize=False)), - ('value', models.CharField(max_length=500)), - ('unit', models.CharField(blank=True, max_length=255, null=True)), - ('id_car_specification', models.ForeignKey(db_column='id_car_specification', on_delete=django.db.models.deletion.DO_NOTHING, to='inventory.carspecification')), - ('id_car_trim', models.ForeignKey(db_column='id_car_trim', on_delete=django.db.models.deletion.DO_NOTHING, to='inventory.cartrim')), - ], - options={ - 'verbose_name': 'Specification Value', - }, - ), - migrations.AddField( - model_name='carequipment', - name='id_car_trim', - field=models.ForeignKey(db_column='id_car_trim', on_delete=django.db.models.deletion.DO_NOTHING, to='inventory.cartrim'), - ), - migrations.AddField( - model_name='car', - name='id_car_trim', - field=models.ForeignKey(blank=True, db_column='id_car_trim', null=True, on_delete=django.db.models.deletion.DO_NOTHING, to='inventory.cartrim', verbose_name='Trim'), - ), - migrations.CreateModel( - name='CustomCard', - fields=[ - ('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')), - ('custom_number', models.CharField(max_length=255, verbose_name='Custom Number')), - ('custom_date', models.DateField(verbose_name='Custom Date')), - ('car', models.OneToOneField(on_delete=django.db.models.deletion.CASCADE, related_name='custom_cards', to='inventory.car', verbose_name='Car')), - ], - options={ - 'verbose_name': 'Custom Card', - 'verbose_name_plural': 'Custom Cards', - }, - ), migrations.CreateModel( name='Dealer', fields=[ @@ -333,136 +173,32 @@ class Migration(migrations.Migration): ], ), migrations.CreateModel( - name='CustomGroup', + name='ExteriorColors', fields=[ ('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')), - ('name', models.CharField(max_length=100)), - ('group', models.OneToOneField(on_delete=django.db.models.deletion.CASCADE, to='auth.group', verbose_name='')), - ('dealer', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, related_name='groups', to='inventory.dealer')), - ], - ), - migrations.CreateModel( - name='Customer', - fields=[ - ('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')), - ('title', models.CharField(choices=[('mr', 'Mr'), ('mrs', 'Mrs'), ('ms', 'Ms'), ('miss', 'Miss'), ('dr', 'Dr'), ('prof', 'Prof'), ('prince', 'Prince'), ('princess', 'Princess'), ('company', 'Company'), ('na', 'N/A')], default='na', max_length=10, verbose_name='Title')), - ('first_name', models.CharField(max_length=50, verbose_name='First Name')), - ('middle_name', models.CharField(blank=True, max_length=50, 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')), - ('dob', models.DateField(verbose_name='Date of Birth')), - ('email', models.EmailField(max_length=254, unique=True, verbose_name='Email')), - ('national_id', models.CharField(max_length=10, unique=True, verbose_name='National ID')), - ('phone_number', phonenumber_field.modelfields.PhoneNumberField(max_length=128, region='SA', unique=True, verbose_name='Phone Number')), - ('address', models.CharField(blank=True, max_length=200, null=True, verbose_name='Address')), - ('created', models.DateTimeField(auto_now_add=True, verbose_name='Created')), - ('updated', models.DateTimeField(auto_now=True, verbose_name='Updated')), - ('user', models.OneToOneField(on_delete=django.db.models.deletion.CASCADE, related_name='customer_profile', to=settings.AUTH_USER_MODEL)), - ('dealer', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, related_name='customers', to='inventory.dealer')), + ('name', models.CharField(max_length=255, verbose_name='Name')), + ('arabic_name', models.CharField(max_length=255, verbose_name='Arabic Name')), + ('rgb', models.CharField(blank=True, max_length=24, null=True, verbose_name='RGB')), ], options={ - 'verbose_name': 'Customer', - 'verbose_name_plural': 'Customers', + 'verbose_name': 'Exterior Colors', + 'verbose_name_plural': 'Exterior Colors', }, + bases=(models.Model, inventory.mixins.LocalizedNameMixin), ), migrations.CreateModel( - name='CarTransfer', + name='InteriorColors', 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')), - ('quantity', models.IntegerField(default=1, verbose_name='Quantity')), - ('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'), ('accepted', 'Accepted'), ('success', 'Success'), ('reject', 'Reject'), ('cancelled', 'Cancelled')])), - ('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')), - ('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')), + ('name', models.CharField(max_length=255, verbose_name='Name')), + ('arabic_name', models.CharField(max_length=255, verbose_name='Arabic Name')), + ('rgb', models.CharField(blank=True, max_length=24, null=True, verbose_name='RGB')), ], options={ - 'verbose_name': 'Car Transfer Log', - 'verbose_name_plural': 'Car Transfer Logs', - }, - ), - migrations.CreateModel( - name='CarLocation', - fields=[ - ('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')), - ('description', models.TextField(blank=True, help_text='Optional description about the showroom placement.', null=True, verbose_name='Description')), - ('created_at', models.DateTimeField(auto_now_add=True, verbose_name='Created At')), - ('updated_at', models.DateTimeField(auto_now=True, verbose_name='Last Updated')), - ('car', models.OneToOneField(on_delete=django.db.models.deletion.CASCADE, related_name='location', to='inventory.car', verbose_name='Car')), - ('owner', models.ForeignKey(help_text='Dealer who owns the car.', on_delete=django.db.models.deletion.CASCADE, related_name='owned_cars', to='inventory.dealer', verbose_name='Owner')), - ('showroom', models.ForeignKey(help_text='Dealer where the car is displayed (can be the owner).', on_delete=django.db.models.deletion.CASCADE, related_name='showroom_cars', to='inventory.dealer', verbose_name='Showroom')), - ], - options={ - 'verbose_name': 'Car Location', - 'verbose_name_plural': 'Car Locations', - }, - ), - migrations.AddField( - model_name='car', - name='dealer', - field=models.ForeignKey(on_delete=django.db.models.deletion.DO_NOTHING, related_name='cars', to='inventory.dealer', verbose_name='Dealer'), - ), - migrations.AddField( - model_name='additionalservices', - name='dealer', - field=models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to='inventory.dealer', verbose_name='Dealer'), - ), - migrations.CreateModel( - name='Activity', - fields=[ - ('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')), - ('object_id', models.PositiveIntegerField()), - ('activity_type', models.CharField(choices=[('call', 'Call'), ('sms', 'SMS'), ('email', 'Email'), ('whatsapp', 'WhatsApp'), ('visit', 'Visit'), ('add_car', 'Add Car'), ('sale_car', 'Sale Car'), ('reserve_car', 'Reserve Car'), ('transfer_car', 'Transfer Car'), ('remove_car', 'Remove Car'), ('create_quotation', 'Create Quotation'), ('cancel_quotation', 'Cancel Quotation'), ('create_order', 'Create Order'), ('cancel_order', 'Cancel Order'), ('create_invoice', 'Create Invoice'), ('cancel_invoice', 'Cancel Invoice')], max_length=50, verbose_name='Activity Type')), - ('notes', models.TextField(blank=True, null=True, verbose_name='Notes')), - ('created', models.DateTimeField(auto_now_add=True, verbose_name='Created')), - ('updated', models.DateTimeField(auto_now=True, verbose_name='Updated')), - ('content_type', models.ForeignKey(on_delete=django.db.models.deletion.DO_NOTHING, to='contenttypes.contenttype')), - ('created_by', models.ForeignKey(on_delete=django.db.models.deletion.DO_NOTHING, related_name='activities_created_by', to=settings.AUTH_USER_MODEL)), - ('dealer', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, related_name='activities', to='inventory.dealer')), - ], - options={ - 'verbose_name': 'Activity', - 'verbose_name_plural': 'Activities', - }, - ), - migrations.CreateModel( - name='DealerSettings', - fields=[ - ('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')), - ('additional_info', models.JSONField(blank=True, default=dict, null=True)), - ('bill_cash_account', models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.SET_NULL, related_name='bill_cash', to=settings.DJANGO_LEDGER_ACCOUNT_MODEL)), - ('bill_prepaid_account', models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.SET_NULL, related_name='bill_prepaid', to=settings.DJANGO_LEDGER_ACCOUNT_MODEL)), - ('bill_unearned_account', models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.SET_NULL, related_name='bill_unearned', to=settings.DJANGO_LEDGER_ACCOUNT_MODEL)), - ('dealer', models.OneToOneField(blank=True, null=True, on_delete=django.db.models.deletion.CASCADE, related_name='settings', to='inventory.dealer')), - ('invoice_cash_account', models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.SET_NULL, related_name='invoice_cash', to=settings.DJANGO_LEDGER_ACCOUNT_MODEL)), - ('invoice_prepaid_account', models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.SET_NULL, related_name='invoice_prepaid', to=settings.DJANGO_LEDGER_ACCOUNT_MODEL)), - ('invoice_unearned_account', models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.SET_NULL, related_name='invoice_unearned', to=settings.DJANGO_LEDGER_ACCOUNT_MODEL)), - ], - ), - migrations.CreateModel( - name='Email', - fields=[ - ('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')), - ('object_id', models.PositiveIntegerField()), - ('from_email', models.TextField(blank=True, null=True, verbose_name='From Email')), - ('to_email', models.TextField(blank=True, null=True, verbose_name='To Email')), - ('subject', models.TextField(blank=True, null=True, verbose_name='Subject')), - ('message', models.TextField(blank=True, null=True, verbose_name='Message')), - ('status', models.CharField(choices=[('SENT', 'Sent'), ('FAILED', 'Failed'), ('DELIVERED', 'Delivered'), ('OPEN', 'Open'), ('DRAFT', 'Draft')], default='OPEN', max_length=20, verbose_name='Status')), - ('created', models.DateTimeField(auto_now_add=True, verbose_name='Created')), - ('updated', models.DateTimeField(auto_now=True, verbose_name='Updated')), - ('content_type', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to='contenttypes.contenttype')), - ('created_by', models.ForeignKey(on_delete=django.db.models.deletion.DO_NOTHING, related_name='emails_created', to=settings.AUTH_USER_MODEL)), - ], - options={ - 'verbose_name': 'Email', - 'verbose_name_plural': 'Emails', + 'verbose_name': 'Interior Colors', + 'verbose_name_plural': 'Interior Colors', }, + bases=(models.Model, inventory.mixins.LocalizedNameMixin), ), migrations.CreateModel( name='Lead', @@ -493,37 +229,6 @@ class Migration(migrations.Migration): 'verbose_name_plural': 'Leads', }, ), - migrations.CreateModel( - name='Notes', - fields=[ - ('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')), - ('object_id', models.PositiveIntegerField()), - ('note', models.TextField(verbose_name='Note')), - ('created', models.DateTimeField(auto_now_add=True, verbose_name='Created')), - ('updated', models.DateTimeField(auto_now=True, verbose_name='Updated')), - ('content_type', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to='contenttypes.contenttype')), - ('created_by', models.ForeignKey(on_delete=django.db.models.deletion.DO_NOTHING, related_name='notes_created', to=settings.AUTH_USER_MODEL)), - ], - options={ - 'verbose_name': 'Note', - 'verbose_name_plural': 'Notes', - }, - ), - migrations.CreateModel( - name='Notification', - fields=[ - ('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')), - ('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')), - ('user', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, related_name='notifications', to=settings.AUTH_USER_MODEL)), - ], - options={ - 'verbose_name': 'Notification', - 'verbose_name_plural': 'Notifications', - 'ordering': ['-created'], - }, - ), migrations.CreateModel( name='Organization', fields=[ @@ -546,71 +251,62 @@ class Migration(migrations.Migration): bases=(models.Model, inventory.mixins.LocalizedNameMixin), ), migrations.CreateModel( - name='Refund', + name='Payment', fields=[ ('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')), ('amount', models.DecimalField(decimal_places=2, max_digits=10, verbose_name='amount')), - ('reason', models.TextField(blank=True, verbose_name='reason')), - ('refund_date', models.DateField(auto_now_add=True, verbose_name='refund date')), - ('payment', models.OneToOneField(on_delete=django.db.models.deletion.CASCADE, related_name='refund', to='inventory.payment')), + ('payment_method', models.CharField(choices=[('cash', 'cash'), ('credit', 'credit'), ('transfer', 'transfer'), ('debit', 'debit'), ('SADAD', 'SADAD')], max_length=50, verbose_name='method')), + ('reference_number', models.CharField(blank=True, max_length=100, null=True, verbose_name='reference number')), + ('payment_date', models.DateField(auto_now_add=True, verbose_name='date')), ], options={ - 'verbose_name': 'refund', - 'verbose_name_plural': 'refunds', + 'verbose_name': 'payment', + 'verbose_name_plural': 'payments', }, ), migrations.CreateModel( - name='Representative', + name='VatRate', fields=[ ('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')), - ('name', models.CharField(max_length=255, verbose_name='Name')), + ('rate', models.DecimalField(decimal_places=2, default=Decimal('0.15'), max_digits=5)), + ('is_active', models.BooleanField(default=True)), + ('created_at', models.DateTimeField(auto_now_add=True)), + ], + ), + migrations.CreateModel( + name='Vendor', + fields=[ + ('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')), + ('crn', models.CharField(max_length=10, unique=True, verbose_name='Commercial Registration Number')), + ('vrn', models.CharField(max_length=15, unique=True, verbose_name='VAT Registration Number')), ('arabic_name', models.CharField(max_length=255, verbose_name='Arabic Name')), - ('id_number', models.CharField(max_length=10, unique=True, verbose_name='ID Number')), + ('name', models.CharField(max_length=255, verbose_name='English Name')), + ('contact_person', models.CharField(max_length=100, verbose_name='Contact Person')), ('phone_number', phonenumber_field.modelfields.PhoneNumberField(max_length=128, region='SA', verbose_name='Phone Number')), ('email', models.EmailField(max_length=255, verbose_name='Email Address')), ('address', models.CharField(blank=True, max_length=200, null=True, verbose_name='Address')), - ('dealer', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, related_name='representatives', to='inventory.dealer')), - ('organization', models.ManyToManyField(related_name='representatives', to='inventory.organization')), + ('logo', models.ImageField(blank=True, null=True, upload_to='logos/vendors', verbose_name='Logo')), + ('created_at', models.DateTimeField(auto_now_add=True, verbose_name='Created At')), + ('dealer', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, related_name='vendors', to='inventory.dealer')), ], options={ - 'verbose_name': 'Representative', - 'verbose_name_plural': 'Representatives', + 'verbose_name': 'Vendor', + 'verbose_name_plural': 'Vendors', }, bases=(models.Model, inventory.mixins.LocalizedNameMixin), ), migrations.CreateModel( - name='SaleOrder', + name='UserActivityLog', fields=[ ('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')), - ('payment_method', models.CharField(choices=[('cash', 'Cash'), ('finance', 'Finance'), ('lease', 'Lease')], max_length=20)), - ('comments', models.TextField(blank=True, null=True)), - ('formatted_order_id', models.CharField(editable=False, max_length=10, unique=True)), - ('created', models.DateTimeField(auto_now_add=True)), - ('estimate', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, related_name='sale_orders', to=settings.DJANGO_LEDGER_ESTIMATE_MODEL, verbose_name='Estimate')), - ('invoice', models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.CASCADE, related_name='sale_orders', to=settings.DJANGO_LEDGER_INVOICE_MODEL, verbose_name='Invoice')), + ('action', models.TextField()), + ('timestamp', models.DateTimeField(auto_now_add=True)), + ('user', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to=settings.AUTH_USER_MODEL)), ], options={ - 'ordering': ['-created'], - }, - ), - migrations.CreateModel( - name='Schedule', - fields=[ - ('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')), - ('purpose', models.CharField(choices=[('Product Demo', 'Product Demo'), ('Follow-Up Call', 'Follow-Up Call'), ('Contract Discussion', 'Contract Discussion'), ('Sales Meeting', 'Sales Meeting'), ('Support Call', 'Support Call'), ('Other', 'Other')], max_length=200)), - ('scheduled_at', models.DateTimeField()), - ('scheduled_type', models.CharField(choices=[('Call', 'Call'), ('Meeting', 'Meeting'), ('Email', 'Email')], default='Call', max_length=200)), - ('duration', models.DurationField(default=datetime.timedelta(seconds=300))), - ('notes', models.TextField(blank=True, null=True)), - ('status', models.CharField(choices=[('Scheduled', 'Scheduled'), ('Completed', 'Completed'), ('Canceled', 'Canceled')], default='Scheduled', max_length=200)), - ('created_at', models.DateTimeField(auto_now_add=True)), - ('updated_at', models.DateTimeField(auto_now=True)), - ('customer', models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.CASCADE, related_name='schedules', to=settings.DJANGO_LEDGER_CUSTOMER_MODEL)), - ('lead', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, related_name='schedules', to='inventory.lead')), - ('scheduled_by', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to=settings.AUTH_USER_MODEL)), - ], - options={ - 'ordering': ['-scheduled_at'], + 'verbose_name': 'User Activity Log', + 'verbose_name_plural': 'User Activity Logs', + 'ordering': ['-timestamp'], }, ), migrations.CreateModel( @@ -636,6 +332,74 @@ class Migration(migrations.Migration): ('objects', inventory.models.StaffUserManager()), ], ), + migrations.CreateModel( + name='Schedule', + fields=[ + ('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')), + ('purpose', models.CharField(choices=[('Product Demo', 'Product Demo'), ('Follow-Up Call', 'Follow-Up Call'), ('Contract Discussion', 'Contract Discussion'), ('Sales Meeting', 'Sales Meeting'), ('Support Call', 'Support Call'), ('Other', 'Other')], max_length=200)), + ('scheduled_at', models.DateTimeField()), + ('scheduled_type', models.CharField(choices=[('Call', 'Call'), ('Meeting', 'Meeting'), ('Email', 'Email')], default='Call', max_length=200)), + ('duration', models.DurationField(default=datetime.timedelta(seconds=300))), + ('notes', models.TextField(blank=True, null=True)), + ('status', models.CharField(choices=[('Scheduled', 'Scheduled'), ('Completed', 'Completed'), ('Canceled', 'Canceled')], default='Scheduled', max_length=200)), + ('created_at', models.DateTimeField(auto_now_add=True)), + ('updated_at', models.DateTimeField(auto_now=True)), + ('customer', models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.CASCADE, related_name='schedules', to=settings.DJANGO_LEDGER_CUSTOMER_MODEL)), + ('lead', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, related_name='schedules', to='inventory.lead')), + ('scheduled_by', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to=settings.AUTH_USER_MODEL)), + ], + options={ + 'ordering': ['-scheduled_at'], + }, + ), + migrations.CreateModel( + name='SaleOrder', + fields=[ + ('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')), + ('payment_method', models.CharField(choices=[('cash', 'Cash'), ('finance', 'Finance'), ('lease', 'Lease'), ('credit_card', 'Credit Card'), ('bank_transfer', 'Bank Transfer'), ('SADAD', 'SADAD')], max_length=20)), + ('comments', models.TextField(blank=True, null=True)), + ('formatted_order_id', models.CharField(editable=False, max_length=10, unique=True)), + ('created', models.DateTimeField(auto_now_add=True)), + ('estimate', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, related_name='sale_orders', to=settings.DJANGO_LEDGER_ESTIMATE_MODEL, verbose_name='Estimate')), + ('invoice', models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.CASCADE, related_name='sale_orders', to=settings.DJANGO_LEDGER_INVOICE_MODEL, verbose_name='Invoice')), + ], + options={ + 'ordering': ['-created'], + }, + ), + migrations.CreateModel( + name='Representative', + fields=[ + ('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')), + ('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', phonenumber_field.modelfields.PhoneNumberField(max_length=128, region='SA', verbose_name='Phone Number')), + ('email', models.EmailField(max_length=255, verbose_name='Email Address')), + ('address', models.CharField(blank=True, max_length=200, null=True, verbose_name='Address')), + ('dealer', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, related_name='representatives', to='inventory.dealer')), + ('organization', models.ManyToManyField(related_name='representatives', to='inventory.organization')), + ], + options={ + 'verbose_name': 'Representative', + 'verbose_name_plural': 'Representatives', + }, + bases=(models.Model, inventory.mixins.LocalizedNameMixin), + ), + migrations.CreateModel( + name='Refund', + fields=[ + ('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')), + ('amount', models.DecimalField(decimal_places=2, max_digits=10, verbose_name='amount')), + ('reason', models.TextField(blank=True, verbose_name='reason')), + ('refund_date', models.DateField(auto_now_add=True, verbose_name='refund date')), + ('payment', models.OneToOneField(on_delete=django.db.models.deletion.CASCADE, related_name='refund', to='inventory.payment')), + ], + options={ + 'verbose_name': 'refund', + 'verbose_name_plural': 'refunds', + }, + ), migrations.CreateModel( name='Opportunity', fields=[ @@ -659,6 +423,37 @@ class Migration(migrations.Migration): 'verbose_name_plural': 'Opportunities', }, ), + migrations.CreateModel( + name='Notification', + fields=[ + ('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')), + ('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')), + ('user', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, related_name='notifications', to=settings.AUTH_USER_MODEL)), + ], + options={ + 'verbose_name': 'Notification', + 'verbose_name_plural': 'Notifications', + 'ordering': ['-created'], + }, + ), + migrations.CreateModel( + name='Notes', + fields=[ + ('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')), + ('object_id', models.PositiveIntegerField()), + ('note', models.TextField(verbose_name='Note')), + ('created', models.DateTimeField(auto_now_add=True, verbose_name='Created')), + ('updated', models.DateTimeField(auto_now=True, verbose_name='Updated')), + ('content_type', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to='contenttypes.contenttype')), + ('created_by', models.ForeignKey(on_delete=django.db.models.deletion.DO_NOTHING, related_name='notes_created', to=settings.AUTH_USER_MODEL)), + ], + options={ + 'verbose_name': 'Note', + 'verbose_name_plural': 'Notes', + }, + ), migrations.CreateModel( name='LeadStatusHistory', fields=[ @@ -666,8 +461,8 @@ class Migration(migrations.Migration): ('old_status', models.CharField(choices=[('new', 'New'), ('pending', 'Pending'), ('in_progress', 'In Progress'), ('qualified', 'Qualified'), ('contacted', 'Contacted'), ('converted', 'Converted'), ('canceled', 'Canceled')], max_length=50, verbose_name='Old Status')), ('new_status', models.CharField(choices=[('new', 'New'), ('pending', 'Pending'), ('in_progress', 'In Progress'), ('qualified', 'Qualified'), ('contacted', 'Contacted'), ('converted', 'Converted'), ('canceled', 'Canceled')], max_length=50, verbose_name='New Status')), ('changed_at', models.DateTimeField(auto_now_add=True, verbose_name='Changed At')), - ('lead', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, related_name='status_history', to='inventory.lead')), ('changed_by', models.ForeignKey(on_delete=django.db.models.deletion.DO_NOTHING, related_name='status_changes', to='inventory.staff')), + ('lead', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, related_name='status_history', to='inventory.lead')), ], options={ 'verbose_name': 'Lead Status History', @@ -680,41 +475,270 @@ class Migration(migrations.Migration): field=models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.SET_NULL, related_name='assigned', to='inventory.staff', verbose_name='Assigned'), ), migrations.CreateModel( - name='UserActivityLog', + name='Email', fields=[ ('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')), - ('action', models.TextField()), - ('timestamp', models.DateTimeField(auto_now_add=True)), - ('user', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to=settings.AUTH_USER_MODEL)), + ('object_id', models.PositiveIntegerField()), + ('from_email', models.TextField(blank=True, null=True, verbose_name='From Email')), + ('to_email', models.TextField(blank=True, null=True, verbose_name='To Email')), + ('subject', models.TextField(blank=True, null=True, verbose_name='Subject')), + ('message', models.TextField(blank=True, null=True, verbose_name='Message')), + ('status', models.CharField(choices=[('SENT', 'Sent'), ('FAILED', 'Failed'), ('DELIVERED', 'Delivered'), ('OPEN', 'Open'), ('DRAFT', 'Draft')], default='OPEN', max_length=20, verbose_name='Status')), + ('created', models.DateTimeField(auto_now_add=True, verbose_name='Created')), + ('updated', models.DateTimeField(auto_now=True, verbose_name='Updated')), + ('content_type', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to='contenttypes.contenttype')), + ('created_by', models.ForeignKey(on_delete=django.db.models.deletion.DO_NOTHING, related_name='emails_created', to=settings.AUTH_USER_MODEL)), ], options={ - 'verbose_name': 'User Activity Log', - 'verbose_name_plural': 'User Activity Logs', - 'ordering': ['-timestamp'], + 'verbose_name': 'Email', + 'verbose_name_plural': 'Emails', }, ), migrations.CreateModel( - name='Vendor', + name='DealerSettings', fields=[ ('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')), - ('crn', models.CharField(max_length=10, unique=True, verbose_name='Commercial Registration Number')), - ('vrn', models.CharField(max_length=15, unique=True, verbose_name='VAT Registration Number')), - ('arabic_name', models.CharField(max_length=255, verbose_name='Arabic Name')), - ('name', models.CharField(max_length=255, verbose_name='English Name')), - ('contact_person', models.CharField(max_length=100, verbose_name='Contact Person')), - ('phone_number', phonenumber_field.modelfields.PhoneNumberField(max_length=128, region='SA', verbose_name='Phone Number')), - ('email', models.EmailField(max_length=255, verbose_name='Email Address')), + ('additional_info', models.JSONField(blank=True, default=dict, null=True)), + ('bill_cash_account', models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.SET_NULL, related_name='bill_cash', to=settings.DJANGO_LEDGER_ACCOUNT_MODEL)), + ('bill_prepaid_account', models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.SET_NULL, related_name='bill_prepaid', to=settings.DJANGO_LEDGER_ACCOUNT_MODEL)), + ('bill_unearned_account', models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.SET_NULL, related_name='bill_unearned', to=settings.DJANGO_LEDGER_ACCOUNT_MODEL)), + ('dealer', models.OneToOneField(blank=True, null=True, on_delete=django.db.models.deletion.CASCADE, related_name='settings', to='inventory.dealer')), + ('invoice_cash_account', models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.SET_NULL, related_name='invoice_cash', to=settings.DJANGO_LEDGER_ACCOUNT_MODEL)), + ('invoice_prepaid_account', models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.SET_NULL, related_name='invoice_prepaid', to=settings.DJANGO_LEDGER_ACCOUNT_MODEL)), + ('invoice_unearned_account', models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.SET_NULL, related_name='invoice_unearned', to=settings.DJANGO_LEDGER_ACCOUNT_MODEL)), + ], + ), + migrations.CreateModel( + name='CustomGroup', + fields=[ + ('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')), + ('name', models.CharField(max_length=100)), + ('dealer', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, related_name='groups', to='inventory.dealer')), + ('group', models.OneToOneField(on_delete=django.db.models.deletion.CASCADE, to='auth.group', verbose_name='')), + ], + ), + migrations.CreateModel( + name='Customer', + fields=[ + ('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')), + ('title', models.CharField(choices=[('mr', 'Mr'), ('mrs', 'Mrs'), ('ms', 'Ms'), ('miss', 'Miss'), ('dr', 'Dr'), ('prof', 'Prof'), ('prince', 'Prince'), ('princess', 'Princess'), ('company', 'Company'), ('na', 'N/A')], default='na', max_length=10, verbose_name='Title')), + ('first_name', models.CharField(max_length=50, verbose_name='First Name')), + ('middle_name', models.CharField(blank=True, max_length=50, 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')), + ('dob', models.DateField(verbose_name='Date of Birth')), + ('email', models.EmailField(max_length=254, unique=True, verbose_name='Email')), + ('national_id', models.CharField(max_length=10, unique=True, verbose_name='National ID')), + ('phone_number', phonenumber_field.modelfields.PhoneNumberField(max_length=128, region='SA', unique=True, verbose_name='Phone Number')), ('address', models.CharField(blank=True, max_length=200, null=True, verbose_name='Address')), - ('logo', models.ImageField(blank=True, null=True, upload_to='logos/vendors', verbose_name='Logo')), - ('created_at', models.DateTimeField(auto_now_add=True, verbose_name='Created At')), - ('dealer', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, related_name='vendors', to='inventory.dealer')), + ('created', models.DateTimeField(auto_now_add=True, verbose_name='Created')), + ('updated', models.DateTimeField(auto_now=True, verbose_name='Updated')), + ('dealer', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, related_name='customers', to='inventory.dealer')), + ('user', models.OneToOneField(on_delete=django.db.models.deletion.CASCADE, related_name='customer_profile', to=settings.AUTH_USER_MODEL)), ], options={ - 'verbose_name': 'Vendor', - 'verbose_name_plural': 'Vendors', + 'verbose_name': 'Customer', + 'verbose_name_plural': 'Customers', + }, + ), + migrations.CreateModel( + name='CustomCard', + fields=[ + ('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')), + ('custom_number', models.CharField(max_length=255, verbose_name='Custom Number')), + ('custom_date', models.DateField(verbose_name='Custom Date')), + ('car', models.OneToOneField(on_delete=django.db.models.deletion.CASCADE, related_name='custom_cards', to='inventory.car', verbose_name='Car')), + ], + options={ + 'verbose_name': 'Custom Card', + 'verbose_name_plural': 'Custom Cards', + }, + ), + migrations.CreateModel( + name='CarTrim', + fields=[ + ('id_car_trim', models.AutoField(primary_key=True, serialize=False)), + ('name', models.CharField(blank=True, max_length=255, null=True)), + ('arabic_name', models.CharField(blank=True, max_length=255, null=True)), + ('start_production_year', models.IntegerField(blank=True, null=True)), + ('end_production_year', models.IntegerField(blank=True, null=True)), + ('id_car_serie', models.ForeignKey(db_column='id_car_serie', on_delete=django.db.models.deletion.DO_NOTHING, to='inventory.carserie')), + ], + options={ + 'verbose_name': 'Trim', }, bases=(models.Model, inventory.mixins.LocalizedNameMixin), ), + 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')), + ('quantity', models.IntegerField(default=1, verbose_name='Quantity')), + ('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'), ('accepted', 'Accepted'), ('success', 'Success'), ('reject', 'Reject'), ('cancelled', 'Cancelled')])), + ('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')), + ('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.CreateModel( + name='CarSpecificationValue', + fields=[ + ('id_car_specification_value', models.AutoField(primary_key=True, serialize=False)), + ('value', models.CharField(max_length=500)), + ('unit', models.CharField(blank=True, max_length=255, null=True)), + ('id_car_specification', models.ForeignKey(db_column='id_car_specification', on_delete=django.db.models.deletion.DO_NOTHING, to='inventory.carspecification')), + ('id_car_trim', models.ForeignKey(db_column='id_car_trim', on_delete=django.db.models.deletion.DO_NOTHING, to='inventory.cartrim')), + ], + options={ + 'verbose_name': 'Specification Value', + }, + ), + migrations.CreateModel( + name='CarRegistration', + fields=[ + ('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')), + ('plate_number', models.IntegerField(verbose_name='Plate Number')), + ('text1', models.CharField(max_length=1, verbose_name='Text 1')), + ('text2', models.CharField(blank=True, max_length=1, null=True, verbose_name='Text 2')), + ('text3', models.CharField(blank=True, max_length=1, null=True, verbose_name='Text 3')), + ('registration_date', models.DateTimeField(verbose_name='Registration Date')), + ('car', models.OneToOneField(on_delete=django.db.models.deletion.CASCADE, related_name='registrations', to='inventory.car', verbose_name='Car')), + ], + options={ + 'verbose_name': 'Registration', + 'verbose_name_plural': 'Registrations', + }, + ), + migrations.CreateModel( + name='CarOptionValue', + fields=[ + ('id_car_option_value', models.AutoField(primary_key=True, serialize=False)), + ('value', models.CharField(max_length=500)), + ('unit', models.CharField(blank=True, max_length=255, null=True)), + ('is_base', models.IntegerField()), + ('id_car_equipment', models.ForeignKey(db_column='id_car_equipment', on_delete=django.db.models.deletion.DO_NOTHING, to='inventory.carequipment')), + ('id_car_option', models.ForeignKey(db_column='id_car_option', on_delete=django.db.models.deletion.DO_NOTHING, to='inventory.caroption')), + ], + options={ + 'verbose_name': 'Option Value', + }, + ), + migrations.CreateModel( + name='CarLocation', + fields=[ + ('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')), + ('description', models.TextField(blank=True, help_text='Optional description about the showroom placement.', null=True, verbose_name='Description')), + ('created_at', models.DateTimeField(auto_now_add=True, verbose_name='Created At')), + ('updated_at', models.DateTimeField(auto_now=True, verbose_name='Last Updated')), + ('car', models.OneToOneField(on_delete=django.db.models.deletion.CASCADE, related_name='location', to='inventory.car', verbose_name='Car')), + ('owner', models.ForeignKey(help_text='Dealer who owns the car.', on_delete=django.db.models.deletion.CASCADE, related_name='owned_cars', to='inventory.dealer', verbose_name='Owner')), + ('showroom', models.ForeignKey(help_text='Dealer where the car is displayed (can be the owner).', on_delete=django.db.models.deletion.CASCADE, related_name='showroom_cars', to='inventory.dealer', verbose_name='Showroom')), + ], + options={ + 'verbose_name': 'Car Location', + 'verbose_name_plural': 'Car Locations', + }, + ), + migrations.CreateModel( + name='CarFinance', + fields=[ + ('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')), + ('cost_price', models.DecimalField(decimal_places=2, max_digits=14, verbose_name='Cost Price')), + ('selling_price', models.DecimalField(decimal_places=2, max_digits=14, verbose_name='Selling Price')), + ('discount_amount', models.DecimalField(decimal_places=2, default=Decimal('0.00'), max_digits=14, verbose_name='Discount Amount')), + ('additional_services', models.ManyToManyField(blank=True, related_name='additional_finances', to='inventory.additionalservices')), + ('car', models.OneToOneField(on_delete=django.db.models.deletion.CASCADE, related_name='finances', to='inventory.car')), + ], + options={ + 'verbose_name': 'Car Financial Details', + 'verbose_name_plural': 'Car Financial Details', + }, + ), + migrations.AddField( + model_name='carequipment', + name='id_car_trim', + field=models.ForeignKey(db_column='id_car_trim', on_delete=django.db.models.deletion.DO_NOTHING, to='inventory.cartrim'), + ), + migrations.AddField( + model_name='car', + name='dealer', + field=models.ForeignKey(on_delete=django.db.models.deletion.DO_NOTHING, related_name='cars', to='inventory.dealer', verbose_name='Dealer'), + ), + migrations.AddField( + model_name='car', + name='id_car_make', + field=models.ForeignKey(blank=True, db_column='id_car_make', null=True, on_delete=django.db.models.deletion.DO_NOTHING, to='inventory.carmake', verbose_name='Make'), + ), + migrations.AddField( + model_name='car', + name='id_car_model', + field=models.ForeignKey(blank=True, db_column='id_car_model', null=True, on_delete=django.db.models.deletion.DO_NOTHING, to='inventory.carmodel', verbose_name='Model'), + ), + migrations.AddField( + model_name='car', + name='id_car_serie', + field=models.ForeignKey(blank=True, db_column='id_car_serie', null=True, on_delete=django.db.models.deletion.DO_NOTHING, to='inventory.carserie', verbose_name='Series'), + ), + migrations.AddField( + model_name='car', + name='id_car_trim', + field=models.ForeignKey(blank=True, db_column='id_car_trim', null=True, on_delete=django.db.models.deletion.DO_NOTHING, to='inventory.cartrim', verbose_name='Trim'), + ), + migrations.AddField( + model_name='car', + name='vendor', + field=models.ForeignKey(on_delete=django.db.models.deletion.DO_NOTHING, related_name='cars', to=settings.DJANGO_LEDGER_VENDOR_MODEL, verbose_name='Vendor'), + ), + migrations.AddField( + model_name='additionalservices', + name='dealer', + field=models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to='inventory.dealer', verbose_name='Dealer'), + ), + migrations.AddField( + model_name='additionalservices', + name='item', + field=models.OneToOneField(blank=True, null=True, on_delete=django.db.models.deletion.CASCADE, to=settings.DJANGO_LEDGER_ITEM_MODEL, verbose_name='Item'), + ), + migrations.CreateModel( + name='Activity', + fields=[ + ('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')), + ('object_id', models.PositiveIntegerField()), + ('activity_type', models.CharField(choices=[('call', 'Call'), ('sms', 'SMS'), ('email', 'Email'), ('whatsapp', 'WhatsApp'), ('visit', 'Visit'), ('add_car', 'Add Car'), ('sale_car', 'Sale Car'), ('reserve_car', 'Reserve Car'), ('transfer_car', 'Transfer Car'), ('remove_car', 'Remove Car'), ('create_quotation', 'Create Quotation'), ('cancel_quotation', 'Cancel Quotation'), ('create_order', 'Create Order'), ('cancel_order', 'Cancel Order'), ('create_invoice', 'Create Invoice'), ('cancel_invoice', 'Cancel Invoice')], max_length=50, verbose_name='Activity Type')), + ('notes', models.TextField(blank=True, null=True, verbose_name='Notes')), + ('created', models.DateTimeField(auto_now_add=True, verbose_name='Created')), + ('updated', models.DateTimeField(auto_now=True, verbose_name='Updated')), + ('content_type', models.ForeignKey(on_delete=django.db.models.deletion.DO_NOTHING, to='contenttypes.contenttype')), + ('created_by', models.ForeignKey(on_delete=django.db.models.deletion.DO_NOTHING, related_name='activities_created_by', to=settings.AUTH_USER_MODEL)), + ('dealer', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, related_name='activities', to='inventory.dealer')), + ], + options={ + 'verbose_name': 'Activity', + 'verbose_name_plural': 'Activities', + }, + ), + migrations.CreateModel( + name='DealersMake', + fields=[ + ('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')), + ('added_at', models.DateTimeField(auto_now_add=True)), + ('car_make', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, related_name='car_dealers', to='inventory.carmake')), + ('dealer', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, related_name='dealer_makes', to='inventory.dealer')), + ], + options={ + 'unique_together': {('dealer', 'car_make')}, + }, + ), migrations.CreateModel( name='CarReservation', fields=[ @@ -731,18 +755,6 @@ class Migration(migrations.Migration): 'unique_together': {('car', 'reserved_until')}, }, ), - migrations.CreateModel( - name='DealersMake', - fields=[ - ('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')), - ('added_at', models.DateTimeField(auto_now_add=True)), - ('car_make', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, related_name='car_dealers', to='inventory.carmake')), - ('dealer', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, related_name='dealer_makes', to='inventory.dealer')), - ], - options={ - 'unique_together': {('dealer', 'car_make')}, - }, - ), migrations.CreateModel( name='CarColors', fields=[ diff --git a/inventory/migrations/0002_alter_saleorder_payment_method.py b/inventory/migrations/0002_alter_saleorder_payment_method.py deleted file mode 100644 index dc6c31ca..00000000 --- a/inventory/migrations/0002_alter_saleorder_payment_method.py +++ /dev/null @@ -1,18 +0,0 @@ -# Generated by Django 5.1.7 on 2025-03-16 19:09 - -from django.db import migrations, models - - -class Migration(migrations.Migration): - - dependencies = [ - ('inventory', '0001_initial'), - ] - - operations = [ - migrations.AlterField( - model_name='saleorder', - name='payment_method', - field=models.CharField(choices=[('cash', 'Cash'), ('finance', 'Finance'), ('lease', 'Lease'), ('credit_card', 'Credit Card'), ('bank_transfer', 'Bank Transfer'), ('SADAD', 'SADAD')], max_length=20), - ), - ] diff --git a/inventory/models.py b/inventory/models.py index 55d06bb1..45d873bd 100644 --- a/inventory/models.py +++ b/inventory/models.py @@ -358,8 +358,6 @@ class Car(models.Model): vendor = models.ForeignKey( VendorModel, models.DO_NOTHING, - null=True, - blank=True, related_name="cars", verbose_name=_("Vendor"), ) diff --git a/inventory/signals.py b/inventory/signals.py index 33a902f7..ac99e298 100644 --- a/inventory/signals.py +++ b/inventory/signals.py @@ -623,7 +623,7 @@ def create_ledger_entity(sender, instance, created, **kwargs): entity.create_account(coa_model=coa, code="6302", role=roles.EXPENSE_OTHER, name=_("Taxes"), balance_type="debit", active=True) entity.create_account(coa_model=coa, code="6303", role=roles.EXPENSE_OTHER, name=_("Foreign Currency Translation"), balance_type="debit", active=True) entity.create_account(coa_model=coa, code="6304", role=roles.EXPENSE_OTHER, name=_("Interest Expenses"), balance_type="debit", active=True) - + @receiver(post_save, sender=models.Dealer) def create_dealer_groups(sender, instance, created, **kwargs): @@ -658,7 +658,7 @@ def create_ledger_vendor(sender, instance, created, **kwargs): }, } ) - + coa = entity.get_default_coa() last_account = entity.get_all_accounts().filter(role=roles.LIABILITY_CL_ACC_PAYABLE).order_by('-created').first() # code = f"{int(last_account.code)}{1:03d}" @@ -674,7 +674,7 @@ def create_ledger_vendor(sender, instance, created, **kwargs): coa_model=coa, balance_type="credit", active=True - ) + ) print(f"VendorModel created for Vendor: {instance.name}") @@ -898,11 +898,6 @@ def create_dealer_settings(sender, instance, created, **kwargs): bill_prepaid_account=instance.entity.get_all_accounts().filter(role=roles.ASSET_CA_PREPAID).first(), bill_unearned_account=instance.entity.get_all_accounts().filter(role=roles.LIABILITY_CL_ACC_PAYABLE).first() ) - -@receiver(post_save, sender=models.Dealer) -def check_if_vat_exists(sender, instance, created, **kwargs): - if created: - models.VatRate.objects.get_create(is_active=True) # @receiver(post_save, sender=EstimateModel) # def update_estimate_status(sender, instance,created, **kwargs): @@ -951,7 +946,7 @@ def create_make_ledger_accounts(sender, instance, created, **kwargs): # coa_model=entity.get_default_coa(), # balance_type="credit", # active=True -# ) +# ) @receiver(post_save, sender=models.CarFinance) def update_finance_cost(sender, instance, created, **kwargs): @@ -969,17 +964,17 @@ def update_finance_cost(sender, instance, created, **kwargs): ) ledger.additional_info["je_number"] = journal.je_number ledger.save() - + inventory_account = entity.get_default_coa_accounts().filter(role=roles.ASSET_CA_INVENTORY).first() vendor_account = entity.get_default_coa_accounts().get(name=vendor.vendor_name) - # Debit Inventory Account + # Debit Inventory Account TransactionModel.objects.create( journal_entry=journal, account=inventory_account, amount=instance.total + instance.total_additionals, tx_type='debit' - ) + ) # Credit Vendor Account TransactionModel.objects.create( @@ -1003,13 +998,13 @@ def update_finance_cost(sender, instance, created, **kwargs): inventory_account = entity.get_default_coa_accounts().filter(role=roles.ASSET_CA_INVENTORY).first() vendor_account = entity.get_default_coa_accounts().get(name=vendor.vendor_name, active=True) - # Debit Inventory Account + # Debit Inventory Account TransactionModel.objects.create( journal_entry=journal, account=inventory_account, amount=instance.cost_price, tx_type='debit' - ) + ) # Credit Vendor Account TransactionModel.objects.create( @@ -1023,9 +1018,9 @@ def update_finance_cost(sender, instance, created, **kwargs): journal = JournalEntryModel.objects.filter(je_number=ledger.additional_info.get("je_number")).first() debit = journal.get_transaction_queryset().filter(tx_type='debit').first() credit = journal.get_transaction_queryset().filter(tx_type='credit').first() - + debit.amount = instance.cost_price credit.amount = instance.cost_price - + debit.save() credit.save() \ No newline at end of file diff --git a/inventory/views.py b/inventory/views.py index 63162e9d..b200852a 100644 --- a/inventory/views.py +++ b/inventory/views.py @@ -4070,8 +4070,9 @@ class LedgerModelListView(LoginRequiredMixin, ListView,ArchiveIndexView): show_all = False show_current = False show_visible = False + allow_empty = True + - def get_queryset(self): qs = super().get_queryset() dealer = get_user_type(self.request) @@ -4091,51 +4092,51 @@ class LedgerModelDetailView(LoginRequiredMixin, DetailView): model = LedgerModel context_object_name = "ledger" template_name = "ledger/ledger/ledger_detail.html" - + # class LedgerModelCreateView(LoginRequiredMixin,SuccessMessageMixin, CreateView): # model = LedgerModel # template_name = "ledger/ledger/ledger_form.html" # form_class = forms.LedgerModelCreateForm # success_message = "Ledger created" - + # def get_form(self, form_class=None): # dealer = get_user_type(self.request) # form = forms.LedgerModelCreateForm(entity_slug=dealer.entity.slug,user_model=dealer.entity.admin,**self.get_form_kwargs()) # return form # def get_success_url(self): # return reverse('ledger_list') - + # def form_valid(self, form): # instance = form.save(commit=False) # dealer = get_user_type(self.request) # instance.entity = dealer.entity -# instance.save() +# instance.save() # return super().form_valid(form) class JournalEntryListView(LoginRequiredMixin, ListView): model = JournalEntryModel context_object_name = "journal_entries" template_name = "ledger/journal_entry/journal_entry_list.html" - + def get_queryset(self): qs = super().get_queryset() ledger = LedgerModel.objects.filter(pk=self.kwargs['pk']).first() qs = qs.filter(ledger=ledger) return qs - + def get_context_data(self, **kwargs): context = super().get_context_data(**kwargs) context['ledger'] = LedgerModel.objects.filter(pk=self.kwargs['pk']).first() return context - + class JournalEntryCreateView(LoginRequiredMixin,SuccessMessageMixin, CreateView): model = JournalEntryModel template_name = "ledger/journal_entry/journal_entry_form.html" form_class = forms.JournalEntryModelCreateForm ledger_model = None success_message = "Journal Entry created" - + def get_form(self, form_class=None): dealer = get_user_type(self.request) ledger = LedgerModel.objects.filter(pk=self.kwargs['pk']).first() @@ -4150,11 +4151,11 @@ class JournalEntryCreateView(LoginRequiredMixin,SuccessMessageMixin, CreateView) context = super().get_context_data(**kwargs) context["ledger"] = LedgerModel.objects.filter(pk=self.kwargs['pk']).first() return context - + def get_success_url(self): ledger = LedgerModel.objects.filter(pk=self.kwargs['pk']).first() return reverse("journalentry_list", kwargs={"pk": ledger.pk}) - + def JournalEntryDeleteView(request,pk): journal_entry = get_object_or_404(JournalEntryModel, pk=pk) @@ -4182,7 +4183,7 @@ def JournalEntryTransactionsView(request, pk): class JournalEntryModelTXSDetailView(JournalEntryModelTXSDetailViewBase): template_name = 'ledger/journal_entry/journal_entry_txs.html' - + def ledger_lock_all_journals(request,entity_slug,pk): ledger = LedgerModel.objects.filter(pk=pk).first() @@ -4199,7 +4200,7 @@ def ledger_unlock_all_journals(request,entity_slug,pk): if not ledger.is_locked(): messages.error(request, "Ledger is already Unlocked.") return redirect("journalentry_list", pk=ledger.pk) - + ledger.unlock() ledger.save() qs = ledger.journal_entries.locked() @@ -4224,10 +4225,10 @@ def ledger_unpost_all_journals(request,entity_slug,pk): messages.error(request, "Ledger is already Unposted.") return redirect("journalentry_list", pk=ledger.pk) qs = ledger.journal_entries.posted() - for je in qs: + for je in qs: je.mark_as_unposted() je.save() - + ledger.unpost() ledger.save() return redirect("journalentry_list", pk=ledger.pk) diff --git a/requirements.txt b/requirements.txt index 6a0fd620..6f10ed53 100644 --- a/requirements.txt +++ b/requirements.txt @@ -29,8 +29,6 @@ commonmark contourpy crispy-bootstrap5 cryptography -cssselect2 -ctranslate2 cycler Cython decorator diff --git a/scripts/generate.py b/scripts/generate.py index d235fb3e..864598a1 100644 --- a/scripts/generate.py +++ b/scripts/generate.py @@ -1,4 +1,5 @@ from inventory.models import * +from django_ledger.models import VendorModel from rich import print import random import datetime @@ -8,6 +9,9 @@ from inventory.services import decodevin def run(): # car = Car.objects.filter(vin='2C3HD46R4WH170267') + dealer = Dealer.objects.first() + vendors = [VendorModel.objects.create(vendor_name=f'vendor{i}',entity_model=dealer.entity) for i in range(1, 5)] + vin_list = [ "1B3ES56C13D120225", "1GB4KYC86FF131536", @@ -24,8 +28,8 @@ def run(): ] for vin in vin_list: try: - for _ in range(15): - dealer = Dealer.objects.get(user__email="ismail.mosa.ibrahim@gmail.com") + for _ in range(5): + vin = f"{vin[:-4]}{random.randint(0, 9)}{random.randint(0, 9)}{random.randint(0, 9)}{random.randint(0, 9)}" result = decodevin(vin) make = CarMake.objects.get(name=result["maker"]) @@ -35,13 +39,14 @@ def run(): year = result["modelYear"] serie = random.choice(model.carserie_set.all()) trim = random.choice(serie.cartrim_set.all()) - + vendor = random.choice(vendors) car = Car.objects.create( vin=vin, id_car_make=make, id_car_model=model, id_car_serie=serie, id_car_trim=trim, + vendor=vendor, year=(int(year) or 2025), receiving_date=datetime.datetime.now(), dealer=dealer, From e5d09b6e0d92be6809ed59ce9ade18d7983a3077 Mon Sep 17 00:00:00 2001 From: = <=> Date: Sun, 23 Mar 2025 21:03:13 +0300 Subject: [PATCH 2/2] update --- inventory/models.py | 4 + inventory/signals.py | 216 +++++++++++++++------------- inventory/urls.py | 16 +-- inventory/utils.py | 80 +++++++---- inventory/views.py | 23 ++- templates/vendors/vendors_list.html | 7 +- 6 files changed, 209 insertions(+), 137 deletions(-) diff --git a/inventory/models.py b/inventory/models.py index 45d873bd..860bb2f9 100644 --- a/inventory/models.py +++ b/inventory/models.py @@ -592,6 +592,10 @@ class CarFinance(models.Model): def total(self): return self.selling_price + @property + def total_additionals_no_vat(self): + return sum(x.price for x in self.additional_services.all()) + @property def total_additionals(self): return sum(x.price_ for x in self.additional_services.all()) diff --git a/inventory/signals.py b/inventory/signals.py index ac99e298..075e4181 100644 --- a/inventory/signals.py +++ b/inventory/signals.py @@ -19,7 +19,9 @@ from django_ledger.models import ( CustomerModel, JournalEntryModel, TransactionModel, - LedgerModel + LedgerModel, + BillModel, + ItemTransactionModel ) from . import models from django.utils.timezone import now @@ -642,20 +644,18 @@ def create_dealer_groups(sender, instance, created, **kwargs): def create_ledger_vendor(sender, instance, created, **kwargs): if created: entity = EntityModel.objects.filter(name=instance.dealer.name).first() - + additionals = to_dict(instance) entity.create_vendor( vendor_model_kwargs={ "vendor_name": instance.name, "vendor_number": instance.crn, "address_1": instance.address, "phone": instance.phone_number, + "email": instance.email, "tax_id_number": instance.vrn, "active": True, "hidden": False, - "additional_info": { - "arabic_name": instance.arabic_name, - "contact_person": instance.contact_person, - }, + "additional_info": additionals, } ) @@ -676,25 +676,37 @@ def create_ledger_vendor(sender, instance, created, **kwargs): active=True ) print(f"VendorModel created for Vendor: {instance.name}") - + else: + additionals = to_dict(instance) + entity.get_vendors().filter(email=instance.email).first().update( + vendor_name= instance.name, + vendor_number= instance.crn, + address_1= instance.address, + phone= instance.phone_number, + email= instance.email, + tax_id_number= instance.vrn, + additional_info= additionals, + ) @receiver(post_save, sender=models.CustomerModel) def create_customer_user(sender, instance, created, **kwargs): if created: - first_name = instance.additional_info.get("customer_info").get("first_name") - last_name = instance.additional_info.get("customer_info").get("last_name") - user = User.objects.create( - username=instance.email, - email=instance.email, - first_name=first_name if first_name else '', - last_name=last_name if last_name else '', - ) - instance.additional_info.update({"user_info": to_dict(user)}) - user.set_unusable_password() - user.save() - instance.user = user - instance.save() - + try: + first_name = instance.additional_info.get("customer_info").get("first_name") + last_name = instance.additional_info.get("customer_info").get("last_name") + user = User.objects.create( + username=instance.email, + email=instance.email, + first_name=first_name if first_name else '', + last_name=last_name if last_name else '', + ) + instance.additional_info.update({"user_info": to_dict(user)}) + user.set_unusable_password() + user.save() + instance.user = user + instance.save() + except Exception as e: + print(e) # Create Item @receiver(post_save, sender=models.Car) @@ -929,98 +941,106 @@ def create_dealer_settings(sender, instance, created, **kwargs): @receiver(post_save, sender=models.Dealer) def create_make_ledger_accounts(sender, instance, created, **kwargs): if created: - entity_name = instance.user.dealer.name - entity = EntityModel.objects.get(name=entity_name) + entity = instance.entity + coa = entity.get_default_coa() + last_account = entity.get_all_accounts().filter(role=roles.ASSET_CA_RECEIVABLES).order_by('-created').first() + if len(last_account.code) == 4: + code = f"{int(last_account.code)}{1:03d}" + elif len(last_account.code) > 4: + code = f"{int(last_account.code)+1}" + + for make in models.CarMake.objects.all(): + entity.create_account( + name=make.name, + code=code, + role=roles.ASSET_CA_RECEIVABLES, + coa_model=coa, + balance_type="credit", + active=True + ) # @receiver(post_save, sender=VendorModel) # def create_vendor_accounts(sender, instance, created, **kwargs): # if created: # entity = instance.entity_model +# coa = entity.get_default_coa() + # last_account = entity.get_all_accounts().filter(role=roles.LIABILITY_CL_ACC_PAYABLE).order_by('-created').first() -# code = str(int(last_account.code) + 1) -# account = entity.create_account( +# if len(last_account.code) == 4: +# code = f"{int(last_account.path)}{1:03d}" +# elif len(last_account.code) > 4: +# code = f"{int(last_account.path)+1}" +# entity.create_account( # name=instance.vendor_name, # code=code, # role=roles.LIABILITY_CL_ACC_PAYABLE, -# coa_model=entity.get_default_coa(), +# coa_model=coa, # balance_type="credit", # active=True # ) +def save_journal(car_finance,ledger,vendor): + entity = ledger.entity + + journal = JournalEntryModel.objects.create( + posted=False, + description=f"Finances of Car:{car_finance.car.vin} for Vendor:{car_finance.car.vendor.vendor_name}", + ledger=ledger, + locked=False, + origin="Payment", + ) + ledger.additional_info["je_number"] = journal.je_number + ledger.save() + + inventory_account = entity.get_default_coa_accounts().filter(role=roles.ASSET_CA_INVENTORY).first() + vendor_account = entity.get_default_coa_accounts().get(name=vendor.vendor_name) + additional_services_account = entity.get_default_coa_accounts().filter(name="Additional Services",role=roles.COGS).first() + + # Debit Inventory Account + TransactionModel.objects.create( + journal_entry=journal, + account=inventory_account, + amount=car_finance.cost_price, + tx_type='debit' + ) + + # Credit Vendor Account + TransactionModel.objects.create( + journal_entry=journal, + account=vendor_account, + amount=car_finance.cost_price, + tx_type='credit', + + ) + @receiver(post_save, sender=models.CarFinance) def update_finance_cost(sender, instance, created, **kwargs): - entity = instance.car.dealer.entity - ledger,created = LedgerModel.objects.get_or_create(name=instance.car.vin, entity=entity) - vendor = instance.car.vendor - if created: - journal = JournalEntryModel.objects.create( - posted=False, - description=f"Finances of Car:{instance.car.vin} for Vendor:{instance.car.vendor.vendor_name}", - ledger=ledger, - locked=False, - origin="Payment", - ) - ledger.additional_info["je_number"] = journal.je_number - ledger.save() + entity = instance.car.dealer.entity + vendor = instance.car.vendor + name = f"{instance.car.vin}-{instance.car.id_car_make.name}-{instance.car.id_car_model.name}-{instance.car.year}-{vendor.vendor_name}" + ledger,_ = LedgerModel.objects.get_or_create(name=name, entity=entity) + save_journal(instance,ledger,vendor) - inventory_account = entity.get_default_coa_accounts().filter(role=roles.ASSET_CA_INVENTORY).first() - vendor_account = entity.get_default_coa_accounts().get(name=vendor.vendor_name) - - # Debit Inventory Account - TransactionModel.objects.create( - journal_entry=journal, - account=inventory_account, - amount=instance.total + instance.total_additionals, - tx_type='debit' - ) - - # Credit Vendor Account - TransactionModel.objects.create( - journal_entry=journal, - account=vendor_account, - amount=instance.cost_price, - tx_type='credit' - ) - else: - if not ledger.additional_info.get("je_number"): - journal = JournalEntryModel.objects.create( - posted=False, - description=f"Finances of Car:{instance.car.vin} for Vendor:{instance.car.vendor.vendor_name}", - ledger=ledger, - locked=False, - origin="Payment", - ) - ledger.additional_info["je_number"] = journal.je_number - ledger.save() - - inventory_account = entity.get_default_coa_accounts().filter(role=roles.ASSET_CA_INVENTORY).first() - vendor_account = entity.get_default_coa_accounts().get(name=vendor.vendor_name, active=True) - - # Debit Inventory Account - TransactionModel.objects.create( - journal_entry=journal, - account=inventory_account, - amount=instance.cost_price, - tx_type='debit' - ) - - # Credit Vendor Account - TransactionModel.objects.create( - journal_entry=journal, - account=vendor_account, - amount=instance.cost_price, - tx_type='credit' - ) - - else: - journal = JournalEntryModel.objects.filter(je_number=ledger.additional_info.get("je_number")).first() - debit = journal.get_transaction_queryset().filter(tx_type='debit').first() - credit = journal.get_transaction_queryset().filter(tx_type='credit').first() - - debit.amount = instance.cost_price - credit.amount = instance.cost_price - - debit.save() - credit.save() \ No newline at end of file + # if not created: + # if ledger.additional_info.get("je_number"): + # journal = JournalEntryModel.objects.filter(je_number=ledger.additional_info.get("je_number")).first() + # journal.description = f"Finances of Car:{instance.car.vin} for Vendor:{instance.car.vendor.vendor_name}" + # journal.save() + # debit = journal.get_transaction_queryset().filter(tx_type='debit').first() + # credit = journal.get_transaction_queryset().filter(tx_type='credit').first() + # if debit and credit: + # if journal.is_locked(): + # journal.mark_as_unlocked() + # journal.save() + # debit.amount = instance.cost_price + # credit.amount = instance.cost_price + # debit.save() + # credit.save() + # else: + # save_journal(instance,ledger,vendor,journal=journal) + # else: + # save_journal(instance,ledger,vendor) + # else: + # save_journal(instance,ledger,vendor) \ No newline at end of file diff --git a/inventory/urls.py b/inventory/urls.py index db4d86fd..b29a9dca 100644 --- a/inventory/urls.py +++ b/inventory/urls.py @@ -134,7 +134,7 @@ urlpatterns = [ views.lead_transfer, name="lead_transfer", ), - + path( "crm/opportunities//add_note/", views.add_note_to_opportunity, @@ -346,7 +346,7 @@ path( # views.payment_create, # name="payment_create", # ), - + # Users URLs path("user/create/", views.UserCreateView.as_view(), name="user_create"), path("user//update/", views.UserUpdateView.as_view(), name="user_update"), @@ -415,7 +415,7 @@ path( # Ledger path( "ledgers/", views.LedgerModelListView.as_view(), name="ledger_list" - ), + ), path( "ledgers//detail//", views.LedgerModelDetailView.as_view(), name="ledger_detail" ), @@ -436,10 +436,10 @@ path( # ), path( "journalentries//list/", views.JournalEntryListView.as_view(), name="journalentry_list" - ), + ), path( "journalentries//create/", views.JournalEntryCreateView.as_view(), name="journalentry_create" - ), + ), path( "journalentries//delete/", views.JournalEntryDeleteView, name="journalentry_delete" ), @@ -640,11 +640,11 @@ path( views.bill_mark_as_paid, name="bill_mark_as_paid", ), - - + + # orders path("orders/", views.OrderListView.as_view(), name="order_list_view"), - + # BALANCE SHEET Reports... # Entities... path('entity//balance-sheet/', diff --git a/inventory/utils.py b/inventory/utils.py index 6609239b..1e42766f 100644 --- a/inventory/utils.py +++ b/inventory/utils.py @@ -97,7 +97,7 @@ def send_email(from_, to_, subject, message): send_mail(subject, message, from_email, recipient_list) -def get_user_type(request): +def get_user_type(request): if request.is_dealer: return request.user.dealer elif request.is_staff: @@ -286,7 +286,7 @@ def get_financial_values(model): def set_invoice_payment(dealer, entity, invoice, amount, payment_method): calculator = CarFinanceCalculator(invoice) - finance_data = calculator.get_finance_data() + finance_data = calculator.get_finance_data() # journal = JournalEntryModel.objects.create( # posted=False, @@ -295,11 +295,11 @@ def set_invoice_payment(dealer, entity, invoice, amount, payment_method): # locked=False, # origin="Payment", # ) - + # credit_account = entity.get_default_coa_accounts().get(name="Sales Revenue") # debit_account = entity.get_default_coa_accounts().get(name="Cash", active=True) # vat_payable_account = entity.get_default_coa_accounts().get(name="VAT Payable", active=True) - + # TransactionModel.objects.create( # journal_entry=journal, # account=debit_account, # Debit Account @@ -316,7 +316,7 @@ def set_invoice_payment(dealer, entity, invoice, amount, payment_method): # description="Payment Received", # ) - + # TransactionModel.objects.create( # journal_entry=journal, # account=vat_payable_account, # Credit VAT Payable @@ -457,7 +457,7 @@ class CarTransfer: self._add_car_item_to_invoice() - def _add_car_item_to_invoice(self): + def _add_car_item_to_invoice(self): self.item = self.from_dealer.entity.get_items_products().filter(name=self.car.vin).first() if not self.item: return @@ -475,7 +475,7 @@ class CarTransfer: commit=True, operation=InvoiceModel.ITEMIZE_APPEND, ) - + if self.invoice.can_review(): self.invoice.mark_as_review() self.invoice.mark_as_approved(self.from_dealer.entity.slug, self.from_dealer.entity.admin) @@ -759,26 +759,26 @@ class CarFinanceCalculator: "make": car_info.get('make'), "model": car_info.get('model'), "year": car_info.get('year'), - "trim": car_info.get('trim'), + "trim": car_info.get('trim'), "mileage": car_info.get('mileage'), "cost_price": car_finance.get('cost_price'), "selling_price": car_finance.get('selling_price'), "discount": car_finance.get('discount_amount'), "quantity": quantity, - "unit_price": unit_price, + "unit_price": unit_price, "total": unit_price * Decimal(quantity), "total_vat": car_finance.get('total_vat'), "additional_services": self._get_nested_value(item, self.ADDITIONAL_SERVICES_KEY), } - + def _get_additional_services(self): return [ {"name": service.get('name'), "price": service.get('price'), "taxable": service.get('taxable'),"price_": service.get('price_')} for item in self.item_transactions for service in self._get_nested_value(item, self.ADDITIONAL_SERVICES_KEY) or [] - ] - + ] + def calculate_totals(self): total_price = sum( Decimal(self._get_nested_value(item, self.CAR_FINANCE_KEY, 'selling_price')) * @@ -786,14 +786,14 @@ class CarFinanceCalculator: for item in self.item_transactions ) total_additionals = sum(Decimal(x.get('price_')) for x in self._get_additional_services()) - + total_discount = sum( Decimal(self._get_nested_value(item, self.CAR_FINANCE_KEY, 'discount_amount')) for item in self.item_transactions ) total_price_discounted = total_price - total_discount total_vat_amount = total_price_discounted * self.vat_rate - + return { "total_price": round(total_price_discounted, 2), # total_price_discounted, "total_vat_amount": round(total_vat_amount, 2), # total_vat_amount, @@ -856,7 +856,7 @@ def handle_account_process(invoice,amount,finance_data): car = models.Car.objects.get(vin=invoice.get_itemtxs_data()[0].first().item_model.name) entity = invoice.ledger.entity coa = entity.get_default_coa() - + make_account = entity.get_all_accounts().filter(name=car.id_car_make.name,role=roles.COGS).first() if not make_account: last_account = entity.get_all_accounts().filter(role=roles.COGS).order_by('-created').first() @@ -864,8 +864,8 @@ def handle_account_process(invoice,amount,finance_data): code = f"{int(last_account.code)}{1:03d}" elif len(last_account.code) > 4: code = f"{int(last_account.code)+1}" - - make_account = entity.create_account( + + make_account = entity.create_account( name=car.id_car_make.name, code=code, role=roles.COGS, @@ -873,7 +873,7 @@ def handle_account_process(invoice,amount,finance_data): balance_type="debit", active=True ) - + # get or create additional services account additional_services_account = entity.get_default_coa_accounts().filter(name="Additional Services",role=roles.COGS).first() if not additional_services_account: @@ -882,8 +882,8 @@ def handle_account_process(invoice,amount,finance_data): code = f"{int(last_account.code)}{1:03d}" elif len(last_account.code) > 4: code = f"{int(last_account.code)+1}" - - additional_services_account = entity.create_account( + + additional_services_account = entity.create_account( name="Additional Services", code=code, role=roles.COGS, @@ -891,12 +891,12 @@ def handle_account_process(invoice,amount,finance_data): balance_type="debit", active=True ) - + inventory_account = entity.get_default_coa_accounts().filter(role=roles.ASSET_CA_INVENTORY).first() - + vat_payable_account = entity.get_default_coa_accounts().get(name="VAT Payable", active=True) - - + + journal = JournalEntryModel.objects.create( posted=False, description=f"Payment for Invoice {invoice.invoice_number}", @@ -904,7 +904,7 @@ def handle_account_process(invoice,amount,finance_data): locked=False, origin="Payment", ) - + TransactionModel.objects.create( journal_entry=journal, account=make_account, # Debit car make Account @@ -926,12 +926,38 @@ def handle_account_process(invoice,amount,finance_data): amount=Decimal(car.finances.total), tx_type="credit", description="Account Adjustment", - ) - + ) + TransactionModel.objects.create( journal_entry=journal, account=vat_payable_account, # Credit VAT Payable amount=finance_data.get("total_vat_amount"), tx_type="credit", description="VAT Payable on Invoice", + ) + +def create_make_accounts(dealer): + entity = dealer.entity + coa = entity.get_default_coa() + + # Create a unique account name for the dealer and car make combination + makes = models.DealersMake.objects.filter(dealer=dealer).all() + for make in makes: + account_name = f"{make.car_make.name} Inventory Account" + + account = entity.get_all_accounts().filter(coa_model=coa,name=account_name).first() + if not account: + last_account = entity.get_all_accounts().filter(role=roles.ASSET_CA_INVENTORY).order_by('-created').first() + if len(last_account.code) == 4: + code = f"{int(last_account.code)}{1:03d}" + elif len(last_account.code) > 4: + code = f"{int(last_account.code)+1}" + + account = entity.create_account( + name=account_name, + code=code, + role=roles.ASSET_CA_INVENTORY, + coa_model=coa, + balance_type="credit", + active=True ) \ No newline at end of file diff --git a/inventory/views.py b/inventory/views.py index b200852a..af410b8d 100644 --- a/inventory/views.py +++ b/inventory/views.py @@ -127,6 +127,7 @@ from .services import ( ) from .utils import ( CarFinanceCalculator, + create_make_accounts, get_car_finance_data, get_financial_values, get_item_transactions, @@ -136,6 +137,7 @@ from .utils import ( set_bill_payment, set_invoice_payment, CarTransfer, + to_dict, ) ##################################################################### @@ -1504,13 +1506,31 @@ class VendorUpdateView( SuccessMessageMixin, UpdateView, ): - model = models.Vendor + model = VendorModel form_class = forms.VendorForm template_name = "vendors/vendor_form.html" success_url = reverse_lazy("vendor_list") success_message = _("Vendor updated successfully.") + def get_initial(self): + initial = super().get_initial() + initial = self.object.additional_info + return initial + def form_valid(self, form): + instance = form.save(commit=False) + + instance.vendor_name = self.request.POST["name"] + instance.vendor_number = self.request.POST["crn"] + instance.address_1 = self.request.POST["address"] + instance.phone = self.request.POST["phone_number"] + instance.email = self.request.POST["email"] + instance.tax_id_number = self.request.POST["vrn"] + additionals = form.cleaned_data + additionals['phone_number'] = str(additionals['phone_number']) + instance.additional_info = additionals + instance.save() + return super().form_valid(form) @login_required def delete_vendor(request, pk): vendor = get_object_or_404(models.Vendor, pk=pk) @@ -4052,6 +4072,7 @@ def assign_car_makes(request): form = forms.DealersMakeForm(request.POST, dealer=dealer) if form.is_valid(): form.save() + create_make_accounts(dealer) return redirect("dealer_detail", pk=dealer.pk) else: # Pre-fill the form with existing selections diff --git a/templates/vendors/vendors_list.html b/templates/vendors/vendors_list.html index d39f408f..9919173f 100644 --- a/templates/vendors/vendors_list.html +++ b/templates/vendors/vendors_list.html @@ -118,7 +118,7 @@

{{ vendor.vendor_name }}

{{ vendor.id}}
- + {{ vendor.email }} @@ -130,11 +130,12 @@
-