diff --git a/haikalbot/migrations/0001_initial.py b/haikalbot/migrations/0001_initial.py index 2c9008dd..479d3428 100644 --- a/haikalbot/migrations/0001_initial.py +++ b/haikalbot/migrations/0001_initial.py @@ -1,4 +1,4 @@ -# Generated by Django 5.1.4 on 2024-12-26 16:17 +# Generated by Django 4.2.17 on 2025-01-02 08:05 from django.db import migrations, models diff --git a/haikalbot/migrations/0002_initial.py b/haikalbot/migrations/0002_initial.py index 6bf7d854..d51e54e4 100644 --- a/haikalbot/migrations/0002_initial.py +++ b/haikalbot/migrations/0002_initial.py @@ -1,7 +1,7 @@ -# Generated by Django 5.1.4 on 2024-12-26 16:17 +# Generated by Django 4.2.17 on 2025-01-02 08:05 -import django.db.models.deletion from django.db import migrations, models +import django.db.models.deletion class Migration(migrations.Migration): diff --git a/inventory/forms.py b/inventory/forms.py index 9123e860..b0a1af6a 100644 --- a/inventory/forms.py +++ b/inventory/forms.py @@ -1,4 +1,5 @@ from phonenumber_field.formfields import PhoneNumberField +from django.core.validators import MinLengthValidator from django.core.validators import RegexValidator from django import forms from django.contrib.auth import get_user_model @@ -519,7 +520,8 @@ class WizardForm3(forms.Form): class ItemForm(forms.Form): item = forms.ModelChoiceField( - queryset=ItemModel.objects.all(), label="Item", required=True + queryset=ItemModel.objects.all(), label="Item", required=True, + validators=[MinLengthValidator(5)], ) quantity = forms.DecimalField(label="Quantity", required=True) # unit = forms.DecimalField(label="Unit", required=True) @@ -552,11 +554,7 @@ class EmailForm(forms.Form): subject = forms.CharField(max_length=255) message = forms.CharField(widget=forms.Textarea) from_email = forms.EmailField() - to_email = forms.EmailField() - - - - + to_email = forms.EmailField(label="To") class OpportunityForm(forms.ModelForm): class Meta: diff --git a/inventory/migrations/0001_initial.py b/inventory/migrations/0001_initial.py index 8f596b0e..45df25f7 100644 --- a/inventory/migrations/0001_initial.py +++ b/inventory/migrations/0001_initial.py @@ -1,11 +1,12 @@ -# Generated by Django 5.1.4 on 2024-12-26 16:17 +# Generated by Django 4.2.17 on 2025-01-02 08:05 -import django.db.models.deletion -import inventory.mixins -import phonenumber_field.modelfields 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 class Migration(migrations.Migration): @@ -18,23 +19,6 @@ class Migration(migrations.Migration): ] 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=[('Kg', 'Kg'), ('L', 'L'), ('m', 'm'), ('cm', 'cm'), ('m2', 'm2'), ('m3', 'm3'), ('m3', 'm3')], 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=[ @@ -66,6 +50,91 @@ class Migration(migrations.Migration): }, bases=(models.Model, inventory.mixins.LocalizedNameMixin), ), + migrations.CreateModel( + name='CarModel', + fields=[ + ('id_car_model', models.AutoField(primary_key=True, serialize=False)), + ('name', models.CharField(max_length=255)), + ('arabic_name', models.CharField(max_length=255)), + ('id_car_make', models.ForeignKey(db_column='id_car_make', on_delete=django.db.models.deletion.DO_NOTHING, to='inventory.carmake')), + ], + options={ + 'verbose_name': 'Model', + }, + bases=(models.Model, inventory.mixins.LocalizedNameMixin), + ), + migrations.CreateModel( + name='CarSerie', + fields=[ + ('id_car_serie', models.AutoField(primary_key=True, serialize=False)), + ('name', models.CharField(max_length=255)), + ('arabic_name', models.CharField(max_length=255)), + ('year_begin', models.IntegerField(blank=True, null=True)), + ('year_end', models.IntegerField(blank=True, null=True)), + ('id_car_model', models.ForeignKey(db_column='id_car_model', on_delete=django.db.models.deletion.DO_NOTHING, to='inventory.carmodel')), + ], + options={ + 'verbose_name': 'Series', + }, + bases=(models.Model, inventory.mixins.LocalizedNameMixin), + ), + migrations.CreateModel( + name='CarSpecification', + fields=[ + ('id_car_specification', models.AutoField(primary_key=True, serialize=False)), + ('name', models.CharField(max_length=255)), + ('arabic_name', models.CharField(max_length=255)), + ('id_parent', models.ForeignKey(blank=True, db_column='id_parent', null=True, on_delete=django.db.models.deletion.DO_NOTHING, to='inventory.carspecification')), + ], + options={ + 'verbose_name': 'Specification', + }, + bases=(models.Model, inventory.mixins.LocalizedNameMixin), + ), + migrations.CreateModel( + name='Customer', + fields=[ + ('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')), + ('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')), + ('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')), + ('is_lead', models.BooleanField(default=False, verbose_name='Is Lead')), + ], + options={ + 'verbose_name': 'Customer', + 'verbose_name_plural': 'Customers', + }, + ), + migrations.CreateModel( + name='Dealer', + fields=[ + ('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')), + ('crn', models.CharField(blank=True, max_length=10, null=True, verbose_name='Commercial Registration Number')), + ('vrn', models.CharField(blank=True, max_length=15, null=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')), + ('phone_number', phonenumber_field.modelfields.PhoneNumberField(max_length=128, region='SA', 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/users', verbose_name='Logo')), + ('joined_at', models.DateTimeField(auto_now_add=True, verbose_name='Joined At')), + ('updated_at', models.DateTimeField(auto_now=True, verbose_name='Updated At')), + ('entity', models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.SET_NULL, to='django_ledger.entitymodel')), + ('user', models.OneToOneField(on_delete=django.db.models.deletion.CASCADE, related_name='dealer', to=settings.AUTH_USER_MODEL)), + ], + options={ + 'verbose_name': 'Dealer', + 'verbose_name_plural': 'Dealers', + }, + bases=(models.Model, inventory.mixins.LocalizedNameMixin), + managers=[ + ('objects', inventory.models.DealerUserManager()), + ], + ), migrations.CreateModel( name='ExteriorColors', fields=[ @@ -94,6 +163,44 @@ class Migration(migrations.Migration): }, bases=(models.Model, inventory.mixins.LocalizedNameMixin), ), + migrations.CreateModel( + name='Opportunity', + fields=[ + ('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')), + ('deal_name', models.CharField(max_length=255, verbose_name='Deal Name')), + ('deal_value', models.DecimalField(decimal_places=2, max_digits=10, verbose_name='Deal Value')), + ('deal_status', models.CharField(choices=[('new', 'New'), ('pending', 'Pending'), ('canceled', 'Canceled'), ('completed', 'Completed')], default='new', max_length=50, verbose_name='Deal Status')), + ('priority', models.CharField(choices=[('low', 'Low'), ('medium', 'Medium'), ('high', 'High')], default='low', max_length=50, verbose_name='Priority')), + ('source', models.CharField(choices=[('referrals', 'Referrals'), ('walk_in', 'Walk In'), ('toll_free', 'Toll Free'), ('whatsapp', 'Whatsapp'), ('showrooms', 'Showrooms'), ('website', 'Website'), ('other', 'Other')], default='showrooms', max_length=255, verbose_name='Source')), + ('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(blank=True, null=True, on_delete=django.db.models.deletion.SET_NULL, to='inventory.car', verbose_name='Car')), + ], + options={ + 'verbose_name': 'Opportunity', + 'verbose_name_plural': 'Opportunities', + }, + ), + migrations.CreateModel( + name='Organization', + 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')), + ('crn', models.CharField(max_length=15, verbose_name='Commercial Registration Number')), + ('vrn', models.CharField(max_length=15, verbose_name='VAT Registration Number')), + ('phone_number', phonenumber_field.modelfields.PhoneNumberField(max_length=128, region='SA', 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', 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='organizations', to='inventory.dealer')), + ], + options={ + 'verbose_name': 'Organization', + 'verbose_name_plural': 'Organizations', + }, + bases=(models.Model, inventory.mixins.LocalizedNameMixin), + ), migrations.CreateModel( name='Payment', fields=[ @@ -108,6 +215,30 @@ class Migration(migrations.Migration): 'verbose_name_plural': 'payments', }, ), + migrations.CreateModel( + name='SaleQuotation', + fields=[ + ('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')), + ('quotation_number', models.CharField(max_length=10, unique=True)), + ('amount', models.DecimalField(decimal_places=2, default=Decimal('0.00'), max_digits=10, verbose_name='Amount')), + ('remarks', models.TextField(blank=True, null=True, verbose_name='Remarks')), + ('is_approved', models.BooleanField(default=False)), + ('status', models.CharField(choices=[('Draft', 'Draft'), ('Approved', 'Approved'), ('In Review', 'In Review'), ('Paid', 'Paid')], default='Draft', max_length=10, verbose_name='Status')), + ('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(blank=True, max_length=255, null=True, verbose_name='Payment ID')), + ('is_paid', models.BooleanField(default=False)), + ('date_draft', models.DateTimeField(blank=True, null=True, verbose_name='Draft Date')), + ('date_in_review', models.DateTimeField(blank=True, null=True, verbose_name='In Review Date')), + ('date_approved', models.DateTimeField(blank=True, null=True, verbose_name='Approved Date')), + ('date_paid', models.DateTimeField(blank=True, null=True, verbose_name='Paid Date')), + ('date_void', models.DateTimeField(blank=True, null=True, verbose_name='Void Date')), + ('date_canceled', models.DateTimeField(blank=True, null=True, verbose_name='Canceled Date')), + ('customer', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, related_name='quotations', to='inventory.customer', verbose_name='Customer')), + ('dealer', models.ForeignKey(null=True, on_delete=django.db.models.deletion.CASCADE, related_name='sales', to='inventory.dealer')), + ], + ), migrations.CreateModel( name='Subscription', fields=[ @@ -130,92 +261,215 @@ class Migration(migrations.Migration): ], ), migrations.CreateModel( - name='CarFinance', + name='VatRate', 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')), + ('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)), ], - options={ - 'verbose_name': 'Car Financial Details', - 'verbose_name_plural': 'Car Financial Details', - }, - ), - 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.CreateModel( - name='CarModel', - fields=[ - ('id_car_model', models.AutoField(primary_key=True, serialize=False)), - ('name', models.CharField(max_length=255)), - ('arabic_name', models.CharField(max_length=255)), - ('id_car_make', models.ForeignKey(db_column='id_car_make', on_delete=django.db.models.deletion.DO_NOTHING, to='inventory.carmake')), - ], - options={ - 'verbose_name': 'Model', - }, - 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='CarRegistration', + name='Vendor', 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(max_length=1, verbose_name='Text 2')), - ('text3', models.CharField(max_length=1, verbose_name='Text 3')), - ('registration_date', models.DateTimeField(verbose_name='Registration Date')), - ('car', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, related_name='registrations', to='inventory.car', verbose_name='Car')), + ('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')), + ('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')), ], options={ - 'verbose_name': 'Registration', - 'verbose_name_plural': 'Registrations', + 'verbose_name': 'Vendor', + 'verbose_name_plural': 'Vendors', + }, + bases=(models.Model, inventory.mixins.LocalizedNameMixin), + ), + migrations.CreateModel( + name='UserActivityLog', + 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)), + ], + options={ + 'verbose_name': 'User Activity Log', + 'verbose_name_plural': 'User Activity Logs', + 'ordering': ['-timestamp'], }, ), migrations.CreateModel( - name='CarSerie', + name='SubscriptionUser', fields=[ - ('id_car_serie', models.AutoField(primary_key=True, serialize=False)), - ('name', models.CharField(max_length=255)), - ('arabic_name', models.CharField(max_length=255)), - ('year_begin', models.IntegerField(blank=True, null=True)), - ('year_end', models.IntegerField(blank=True, null=True)), - ('id_car_model', models.ForeignKey(db_column='id_car_model', on_delete=django.db.models.deletion.DO_NOTHING, to='inventory.carmodel')), + ('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')), + ('subscription', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to='inventory.subscription')), + ('user', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to=settings.AUTH_USER_MODEL)), ], - options={ - 'verbose_name': 'Series', - }, - 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'), + model_name='subscription', + name='users', + field=models.ManyToManyField(through='inventory.SubscriptionUser', to=settings.AUTH_USER_MODEL), ), migrations.CreateModel( - name='CarSpecification', + name='Staff', fields=[ - ('id_car_specification', models.AutoField(primary_key=True, serialize=False)), - ('name', models.CharField(max_length=255)), - ('arabic_name', models.CharField(max_length=255)), - ('id_parent', models.ForeignKey(blank=True, db_column='id_parent', null=True, on_delete=django.db.models.deletion.DO_NOTHING, to='inventory.carspecification')), + ('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')), + ('phone_number', phonenumber_field.modelfields.PhoneNumberField(max_length=128, region='SA', verbose_name='Phone Number')), + ('staff_type', models.CharField(choices=[('manager', 'Manager'), ('inventory', 'Inventory'), ('accountant', 'Accountant'), ('sales', 'Sales')], max_length=255, verbose_name='Staff Type')), + ('created_at', models.DateTimeField(auto_now_add=True, verbose_name='Created At')), + ('updated_at', models.DateTimeField(auto_now=True, verbose_name='Updated At')), + ('dealer', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, related_name='staff', to='inventory.dealer')), + ('user', models.OneToOneField(on_delete=django.db.models.deletion.CASCADE, related_name='staff', to=settings.AUTH_USER_MODEL)), ], options={ - 'verbose_name': 'Specification', + 'verbose_name': 'Staff', + 'verbose_name_plural': 'Staff', + 'permissions': [], }, bases=(models.Model, inventory.mixins.LocalizedNameMixin), ), + migrations.CreateModel( + name='SalesOrder', + fields=[ + ('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')), + ('created_at', models.DateTimeField(auto_now_add=True, verbose_name='Created At')), + ('total_amount', models.DecimalField(decimal_places=2, max_digits=14, verbose_name='Total Amount')), + ('quotation', models.OneToOneField(on_delete=django.db.models.deletion.CASCADE, related_name='sales_order', to='inventory.salequotation', verbose_name='Quotation')), + ], + ), + migrations.CreateModel( + name='SaleQuotationCar', + fields=[ + ('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')), + ('quantity', models.PositiveIntegerField(default=1, verbose_name='Quantity')), + ('car', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to='inventory.car', verbose_name='Car')), + ('quotation', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, related_name='quotation_cars', to='inventory.salequotation', verbose_name='Quotation')), + ], + ), + 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, 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.AddField( + model_name='payment', + name='quotation', + field=models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, related_name='payments', to='inventory.salequotation'), + ), + migrations.CreateModel( + name='OpportunityLog', + fields=[ + ('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')), + ('action', models.CharField(choices=[('create', 'Create'), ('update', 'Update'), ('delete', 'Delete'), ('status_change', 'Status Change')], max_length=50, verbose_name='Action')), + ('old_status', models.CharField(blank=True, choices=[('new', 'New'), ('pending', 'Pending'), ('canceled', 'Canceled'), ('completed', 'Completed')], max_length=50, null=True, verbose_name='Old Status')), + ('new_status', models.CharField(blank=True, choices=[('new', 'New'), ('pending', 'Pending'), ('canceled', 'Canceled'), ('completed', 'Completed')], max_length=50, null=True, verbose_name='New Status')), + ('details', models.TextField(blank=True, null=True, verbose_name='Details')), + ('created_at', models.DateTimeField(auto_now_add=True, verbose_name='Created At')), + ('opportunity', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, related_name='logs', to='inventory.opportunity')), + ('staff', models.ForeignKey(null=True, on_delete=django.db.models.deletion.SET_NULL, to='inventory.staff', verbose_name='Staff')), + ], + options={ + 'verbose_name': 'Log', + 'verbose_name_plural': 'Logs', + 'ordering': ['-created_at'], + }, + ), + migrations.AddField( + model_name='opportunity', + name='created_by', + field=models.ForeignKey(null=True, on_delete=django.db.models.deletion.SET_NULL, related_name='deals_created', to='inventory.staff'), + ), + migrations.AddField( + model_name='opportunity', + name='customer', + field=models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, related_name='opportunities', to='inventory.customer'), + ), + 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_at', models.DateTimeField(auto_now_add=True, verbose_name='Created At')), + ('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_at'], + }, + ), + migrations.CreateModel( + name='Notes', + fields=[ + ('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')), + ('note', models.TextField(verbose_name='Note')), + ('created_at', models.DateTimeField(auto_now_add=True, verbose_name='Created At')), + ('updated_at', models.DateTimeField(auto_now=True, verbose_name='Updated At')), + ('created_by', models.ForeignKey(null=True, on_delete=django.db.models.deletion.SET_NULL, related_name='notes_created', to=settings.AUTH_USER_MODEL)), + ('opportunity', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, related_name='notes', to='inventory.opportunity')), + ], + options={ + 'verbose_name': 'Notes', + 'verbose_name_plural': 'Notes', + }, + ), + migrations.AddField( + model_name='customer', + name='dealer', + field=models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, related_name='customers', to='inventory.dealer'), + ), + 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=[ @@ -245,66 +499,20 @@ class Migration(migrations.Migration): 'verbose_name': 'Specification Value', }, ), - 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', + name='CarRegistration', 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')), + ('plate_number', models.IntegerField(verbose_name='Plate Number')), + ('text1', models.CharField(max_length=1, verbose_name='Text 1')), + ('text2', models.CharField(max_length=1, verbose_name='Text 2')), + ('text3', models.CharField(max_length=1, verbose_name='Text 3')), + ('registration_date', models.DateTimeField(verbose_name='Registration Date')), + ('car', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, related_name='registrations', to='inventory.car', verbose_name='Car')), ], options={ - 'verbose_name': 'Custom Card', - 'verbose_name_plural': 'Custom Cards', - }, - ), - migrations.CreateModel( - name='Dealer', - fields=[ - ('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')), - ('crn', models.CharField(blank=True, max_length=10, null=True, verbose_name='Commercial Registration Number')), - ('vrn', models.CharField(blank=True, max_length=15, null=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')), - ('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/users', verbose_name='Logo')), - ('joined_at', models.DateTimeField(auto_now_add=True, verbose_name='Joined At')), - ('email', models.EmailField(max_length=100, unique=True, verbose_name='Email')), - ('dealer_type', models.CharField(choices=[('Owner', 'Owner'), ('Inventory', 'Inventory'), ('Accountent', 'Accountent'), ('sales', 'Sales')], default='Owner', max_length=255, verbose_name='Dealer Type')), - ('entity', models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.SET_NULL, to='django_ledger.entitymodel')), - ('parent_dealer', models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.SET_NULL, related_name='sub_dealers', to='inventory.dealer', verbose_name='Parent Dealer')), - ('user', models.OneToOneField(on_delete=django.db.models.deletion.CASCADE, related_name='dealer', to=settings.AUTH_USER_MODEL)), - ], - options={ - 'verbose_name': 'Dealer', - 'verbose_name_plural': 'Dealers', - 'permissions': [('change_dealer_type', 'Can change dealer type')], - }, - bases=(models.Model, inventory.mixins.LocalizedNameMixin), - ), - migrations.CreateModel( - name='Customer', - fields=[ - ('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')), - ('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')), - ('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')), - ('dealer', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, related_name='customers', to='inventory.dealer')), - ], - options={ - 'verbose_name': 'Customer', - 'verbose_name_plural': 'Customers', + 'verbose_name': 'Registration', + 'verbose_name_plural': 'Registrations', }, ), migrations.CreateModel( @@ -323,152 +531,69 @@ class Migration(migrations.Migration): '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='django_ledger.itemmodel')), + ('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='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='Organization', - 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')), - ('crn', models.CharField(max_length=15, verbose_name='Commercial Registration Number')), - ('vrn', models.CharField(max_length=15, verbose_name='VAT Registration Number')), - ('phone_number', phonenumber_field.modelfields.PhoneNumberField(max_length=128, region='SA', 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', verbose_name='Logo')), - ('dealer', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, related_name='organizations', to='inventory.dealer')), - ], - options={ - 'verbose_name': 'Organization', - 'verbose_name_plural': 'Organizations', - }, - 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='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, verbose_name='ID Number')), - ('phone_number', phonenumber_field.modelfields.PhoneNumberField(max_length=128, region='SA', verbose_name='Phone Number')), - ('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='SaleQuotation', - fields=[ - ('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')), - ('quotation_number', models.CharField(max_length=10, unique=True)), - ('amount', models.DecimalField(decimal_places=2, default=Decimal('0.00'), max_digits=10, verbose_name='Amount')), - ('remarks', models.TextField(blank=True, null=True, verbose_name='Remarks')), - ('is_approved', models.BooleanField(default=False)), - ('status', models.CharField(choices=[('Draft', 'Draft'), ('Approved', 'Approved'), ('In Review', 'In Review'), ('Paid', 'Paid')], default='Draft', max_length=10, verbose_name='Status')), - ('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(blank=True, max_length=255, null=True, verbose_name='Payment ID')), - ('is_paid', models.BooleanField(default=False)), - ('date_draft', models.DateTimeField(blank=True, null=True, verbose_name='Draft Date')), - ('date_in_review', models.DateTimeField(blank=True, null=True, verbose_name='In Review Date')), - ('date_approved', models.DateTimeField(blank=True, null=True, verbose_name='Approved Date')), - ('date_paid', models.DateTimeField(blank=True, null=True, verbose_name='Paid Date')), - ('date_void', models.DateTimeField(blank=True, null=True, verbose_name='Void Date')), - ('date_canceled', models.DateTimeField(blank=True, null=True, verbose_name='Canceled Date')), - ('customer', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, related_name='quotations', to='inventory.customer', verbose_name='Customer')), - ('dealer', models.ForeignKey(null=True, on_delete=django.db.models.deletion.CASCADE, related_name='sales', to='inventory.dealer')), - ], + 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='payment', - name='quotation', - field=models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, related_name='payments', to='inventory.salequotation'), - ), - migrations.CreateModel( - name='SaleQuotationCar', - fields=[ - ('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')), - ('quantity', models.PositiveIntegerField(default=1, verbose_name='Quantity')), - ('car', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to='inventory.car', verbose_name='Car')), - ('quotation', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, related_name='quotation_cars', to='inventory.salequotation', verbose_name='Quotation')), - ], - ), - migrations.CreateModel( - name='SalesOrder', - fields=[ - ('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')), - ('created_at', models.DateTimeField(auto_now_add=True, verbose_name='Created At')), - ('total_amount', models.DecimalField(decimal_places=2, max_digits=14, verbose_name='Total Amount')), - ('quotation', models.OneToOneField(on_delete=django.db.models.deletion.CASCADE, related_name='sales_order', to='inventory.salequotation', verbose_name='Quotation')), - ], - ), - migrations.CreateModel( - name='SubscriptionUser', - fields=[ - ('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')), - ('subscription', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to='inventory.subscription')), - ('user', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to=settings.AUTH_USER_MODEL)), - ], + 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='subscription', - name='users', - field=models.ManyToManyField(through='inventory.SubscriptionUser', to=settings.AUTH_USER_MODEL), + 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='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')), - ('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')), - ('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')), - ('dealer', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, related_name='vendors', to='inventory.dealer')), - ], - options={ - 'verbose_name': 'Vendor', - 'verbose_name_plural': 'Vendors', - }, - bases=(models.Model, inventory.mixins.LocalizedNameMixin), + 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(blank=True, null=True, on_delete=django.db.models.deletion.DO_NOTHING, related_name='cars', to='inventory.vendor', verbose_name='Vendor'), ), + 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=[('Unit', 'Unit'), ('Kg', 'Kg'), ('L', 'L'), ('m', 'm'), ('cm', 'cm'), ('m2', 'm2'), ('m3', 'm3'), ('m3', 'm3')], max_length=10, verbose_name='Unit of Measurement')), + ('dealer', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to='inventory.dealer', verbose_name='Dealer')), + ], + options={ + 'verbose_name': 'Additional Services', + 'verbose_name_plural': 'Additional Services', + }, + bases=(models.Model, inventory.mixins.LocalizedNameMixin), + ), migrations.CreateModel( name='CarReservation', fields=[ diff --git a/inventory/migrations/0002_remove_dealer_email.py b/inventory/migrations/0002_remove_dealer_email.py deleted file mode 100644 index 8a551dd0..00000000 --- a/inventory/migrations/0002_remove_dealer_email.py +++ /dev/null @@ -1,17 +0,0 @@ -# Generated by Django 5.1.4 on 2024-12-26 17:33 - -from django.db import migrations - - -class Migration(migrations.Migration): - - dependencies = [ - ('inventory', '0001_initial'), - ] - - operations = [ - migrations.RemoveField( - model_name='dealer', - name='email', - ), - ] diff --git a/inventory/migrations/0003_alter_dealer_phone_number.py b/inventory/migrations/0003_alter_dealer_phone_number.py deleted file mode 100644 index 1d8f30aa..00000000 --- a/inventory/migrations/0003_alter_dealer_phone_number.py +++ /dev/null @@ -1,19 +0,0 @@ -# Generated by Django 5.1.4 on 2024-12-26 18:27 - -import phonenumber_field.modelfields -from django.db import migrations - - -class Migration(migrations.Migration): - - dependencies = [ - ('inventory', '0002_remove_dealer_email'), - ] - - operations = [ - migrations.AlterField( - model_name='dealer', - name='phone_number', - field=phonenumber_field.modelfields.PhoneNumberField(max_length=128, region='SA', verbose_name='Phone Number'), - ), - ] diff --git a/inventory/migrations/0004_alter_dealer_managers_remove_dealer_entity.py b/inventory/migrations/0004_alter_dealer_managers_remove_dealer_entity.py deleted file mode 100644 index 43966977..00000000 --- a/inventory/migrations/0004_alter_dealer_managers_remove_dealer_entity.py +++ /dev/null @@ -1,24 +0,0 @@ -# Generated by Django 5.1.4 on 2024-12-26 23:03 - -import inventory.models -from django.db import migrations - - -class Migration(migrations.Migration): - - dependencies = [ - ('inventory', '0003_alter_dealer_phone_number'), - ] - - operations = [ - migrations.AlterModelManagers( - name='dealer', - managers=[ - ('objects', inventory.models.DealerUserManager()), - ], - ), - migrations.RemoveField( - model_name='dealer', - name='entity', - ), - ] diff --git a/inventory/migrations/0005_dealer_entity.py b/inventory/migrations/0005_dealer_entity.py deleted file mode 100644 index b9239e65..00000000 --- a/inventory/migrations/0005_dealer_entity.py +++ /dev/null @@ -1,20 +0,0 @@ -# Generated by Django 5.1.4 on 2024-12-26 23:57 - -import django.db.models.deletion -from django.db import migrations, models - - -class Migration(migrations.Migration): - - dependencies = [ - ('django_ledger', '0017_alter_accountmodel_unique_together_and_more'), - ('inventory', '0004_alter_dealer_managers_remove_dealer_entity'), - ] - - operations = [ - migrations.AddField( - model_name='dealer', - name='entity', - field=models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.SET_NULL, to='django_ledger.entitymodel'), - ), - ] diff --git a/inventory/migrations/0006_vendor_email.py b/inventory/migrations/0006_vendor_email.py deleted file mode 100644 index fd7ae27f..00000000 --- a/inventory/migrations/0006_vendor_email.py +++ /dev/null @@ -1,19 +0,0 @@ -# Generated by Django 5.1.4 on 2024-12-29 11:52 - -from django.db import migrations, models - - -class Migration(migrations.Migration): - - dependencies = [ - ('inventory', '0005_dealer_entity'), - ] - - operations = [ - migrations.AddField( - model_name='vendor', - name='email', - field=models.EmailField(default='email@email.com', max_length=255, verbose_name='Email Address'), - preserve_default=False, - ), - ] diff --git a/inventory/migrations/0007_vendor_created_at.py b/inventory/migrations/0007_vendor_created_at.py deleted file mode 100644 index 56338ab5..00000000 --- a/inventory/migrations/0007_vendor_created_at.py +++ /dev/null @@ -1,20 +0,0 @@ -# Generated by Django 5.1.4 on 2024-12-29 13:04 - -import django.utils.timezone -from django.db import migrations, models - - -class Migration(migrations.Migration): - - dependencies = [ - ('inventory', '0006_vendor_email'), - ] - - operations = [ - migrations.AddField( - model_name='vendor', - name='created_at', - field=models.DateTimeField(auto_now_add=True, default=django.utils.timezone.now, verbose_name='Created At'), - preserve_default=False, - ), - ] diff --git a/inventory/migrations/0008_alter_dealer_options_remove_dealer_dealer_type_and_more.py b/inventory/migrations/0008_alter_dealer_options_remove_dealer_dealer_type_and_more.py deleted file mode 100644 index ce4e5e58..00000000 --- a/inventory/migrations/0008_alter_dealer_options_remove_dealer_dealer_type_and_more.py +++ /dev/null @@ -1,55 +0,0 @@ -# Generated by Django 5.1.4 on 2024-12-29 15:46 - -import django.db.models.deletion -import inventory.mixins -import phonenumber_field.modelfields -from django.conf import settings -from django.db import migrations, models - - -class Migration(migrations.Migration): - - dependencies = [ - ('inventory', '0007_vendor_created_at'), - migrations.swappable_dependency(settings.AUTH_USER_MODEL), - ] - - operations = [ - migrations.AlterModelOptions( - name='dealer', - options={'verbose_name': 'Dealer', 'verbose_name_plural': 'Dealers'}, - ), - migrations.RemoveField( - model_name='dealer', - name='dealer_type', - ), - migrations.RemoveField( - model_name='dealer', - name='parent_dealer', - ), - migrations.AddField( - model_name='dealer', - name='updated_at', - field=models.DateTimeField(auto_now=True, verbose_name='Updated At'), - ), - migrations.CreateModel( - name='Staff', - 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')), - ('phone_number', phonenumber_field.modelfields.PhoneNumberField(max_length=128, region='SA', verbose_name='Phone Number')), - ('staff_type', models.CharField(choices=[('manager', 'Manager'), ('inventory', 'Inventory'), ('accountant', 'Accountant'), ('sales', 'Sales'), ('receptionist', 'Receptionist'), ('technician', 'Technician'), ('driver', 'Driver')], max_length=255, verbose_name='Staff Type')), - ('created_at', models.DateTimeField(auto_now_add=True, verbose_name='Created At')), - ('updated_at', models.DateTimeField(auto_now=True, verbose_name='Updated At')), - ('dealer', models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.SET_NULL, to='inventory.dealer')), - ('user', models.OneToOneField(on_delete=django.db.models.deletion.CASCADE, related_name='staff', to=settings.AUTH_USER_MODEL)), - ], - options={ - 'verbose_name': 'Staff', - 'verbose_name_plural': 'Staff', - 'permissions': [], - }, - bases=(models.Model, inventory.mixins.LocalizedNameMixin), - ), - ] diff --git a/inventory/migrations/0008_vatrate.py b/inventory/migrations/0008_vatrate.py deleted file mode 100644 index 754bc92d..00000000 --- a/inventory/migrations/0008_vatrate.py +++ /dev/null @@ -1,23 +0,0 @@ -# Generated by Django 4.2.17 on 2024-12-30 09:45 - -from decimal import Decimal -from django.db import migrations, models - - -class Migration(migrations.Migration): - - dependencies = [ - ('inventory', '0007_vendor_created_at'), - ] - - operations = [ - 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)), - ], - ), - ] diff --git a/inventory/migrations/0009_alter_additionalservices_uom_alter_staff_dealer.py b/inventory/migrations/0009_alter_additionalservices_uom_alter_staff_dealer.py deleted file mode 100644 index fe0bce61..00000000 --- a/inventory/migrations/0009_alter_additionalservices_uom_alter_staff_dealer.py +++ /dev/null @@ -1,25 +0,0 @@ -# Generated by Django 5.1.4 on 2024-12-30 01:50 - -import django.db.models.deletion -from django.db import migrations, models - - -class Migration(migrations.Migration): - - dependencies = [ - ('inventory', '0008_alter_dealer_options_remove_dealer_dealer_type_and_more'), - ] - - operations = [ - migrations.AlterField( - model_name='additionalservices', - name='uom', - field=models.CharField(choices=[('Unit', 'Unit'), ('Kg', 'Kg'), ('L', 'L'), ('m', 'm'), ('cm', 'cm'), ('m2', 'm2'), ('m3', 'm3'), ('m3', 'm3')], max_length=10, verbose_name='Unit of Measurement'), - ), - migrations.AlterField( - model_name='staff', - name='dealer', - field=models.ForeignKey(default=8, on_delete=django.db.models.deletion.CASCADE, related_name='staff', to='inventory.dealer'), - preserve_default=False, - ), - ] diff --git a/inventory/migrations/0010_useractivitylog.py b/inventory/migrations/0010_useractivitylog.py deleted file mode 100644 index 6d9fab8f..00000000 --- a/inventory/migrations/0010_useractivitylog.py +++ /dev/null @@ -1,30 +0,0 @@ -# Generated by Django 5.1.4 on 2024-12-30 02:50 - -import django.db.models.deletion -from django.conf import settings -from django.db import migrations, models - - -class Migration(migrations.Migration): - - dependencies = [ - ('inventory', '0009_alter_additionalservices_uom_alter_staff_dealer'), - migrations.swappable_dependency(settings.AUTH_USER_MODEL), - ] - - operations = [ - migrations.CreateModel( - name='UserActivityLog', - 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)), - ], - options={ - 'verbose_name': 'User Activity Log', - 'verbose_name_plural': 'User Activity Logs', - 'ordering': ['-timestamp'], - }, - ), - ] diff --git a/inventory/migrations/0011_delete_useractivitylog.py b/inventory/migrations/0011_delete_useractivitylog.py deleted file mode 100644 index a7302bbd..00000000 --- a/inventory/migrations/0011_delete_useractivitylog.py +++ /dev/null @@ -1,16 +0,0 @@ -# Generated by Django 5.1.4 on 2024-12-30 03:11 - -from django.db import migrations - - -class Migration(migrations.Migration): - - dependencies = [ - ('inventory', '0010_useractivitylog'), - ] - - operations = [ - migrations.DeleteModel( - name='UserActivityLog', - ), - ] diff --git a/inventory/migrations/0012_representative_email.py b/inventory/migrations/0012_representative_email.py deleted file mode 100644 index 4e4ab042..00000000 --- a/inventory/migrations/0012_representative_email.py +++ /dev/null @@ -1,19 +0,0 @@ -# Generated by Django 5.1.4 on 2024-12-30 03:42 - -from django.db import migrations, models - - -class Migration(migrations.Migration): - - dependencies = [ - ('inventory', '0011_delete_useractivitylog'), - ] - - operations = [ - migrations.AddField( - model_name='representative', - name='email', - field=models.EmailField(default='mail@mail.com', max_length=255, verbose_name='Email Address'), - preserve_default=False, - ), - ] diff --git a/inventory/migrations/0013_organization_created_at.py b/inventory/migrations/0013_organization_created_at.py deleted file mode 100644 index c8138396..00000000 --- a/inventory/migrations/0013_organization_created_at.py +++ /dev/null @@ -1,20 +0,0 @@ -# Generated by Django 5.1.4 on 2024-12-30 03:59 - -import django.utils.timezone -from django.db import migrations, models - - -class Migration(migrations.Migration): - - dependencies = [ - ('inventory', '0012_representative_email'), - ] - - operations = [ - migrations.AddField( - model_name='organization', - name='created_at', - field=models.DateTimeField(auto_now_add=True, default=django.utils.timezone.now, verbose_name='Created At'), - preserve_default=False, - ), - ] diff --git a/inventory/migrations/0014_useractivitylog.py b/inventory/migrations/0014_useractivitylog.py deleted file mode 100644 index d4a1bf1f..00000000 --- a/inventory/migrations/0014_useractivitylog.py +++ /dev/null @@ -1,30 +0,0 @@ -# Generated by Django 5.1.4 on 2024-12-30 10:49 - -import django.db.models.deletion -from django.conf import settings -from django.db import migrations, models - - -class Migration(migrations.Migration): - - dependencies = [ - ('inventory', '0013_organization_created_at'), - migrations.swappable_dependency(settings.AUTH_USER_MODEL), - ] - - operations = [ - migrations.CreateModel( - name='UserActivityLog', - 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)), - ], - options={ - 'verbose_name': 'User Activity Log', - 'verbose_name_plural': 'User Activity Logs', - 'ordering': ['-timestamp'], - }, - ), - ] diff --git a/inventory/migrations/0015_merge_0008_vatrate_0014_useractivitylog.py b/inventory/migrations/0015_merge_0008_vatrate_0014_useractivitylog.py deleted file mode 100644 index 99663c5c..00000000 --- a/inventory/migrations/0015_merge_0008_vatrate_0014_useractivitylog.py +++ /dev/null @@ -1,14 +0,0 @@ -# Generated by Django 4.2.17 on 2024-12-30 11:11 - -from django.db import migrations - - -class Migration(migrations.Migration): - - dependencies = [ - ('inventory', '0008_vatrate'), - ('inventory', '0014_useractivitylog'), - ] - - operations = [ - ] diff --git a/inventory/migrations/0016_alter_staff_staff_type.py b/inventory/migrations/0016_alter_staff_staff_type.py deleted file mode 100644 index 229113a1..00000000 --- a/inventory/migrations/0016_alter_staff_staff_type.py +++ /dev/null @@ -1,18 +0,0 @@ -# Generated by Django 5.1.4 on 2024-12-30 15:13 - -from django.db import migrations, models - - -class Migration(migrations.Migration): - - dependencies = [ - ('inventory', '0015_merge_0008_vatrate_0014_useractivitylog'), - ] - - operations = [ - migrations.AlterField( - model_name='staff', - name='staff_type', - field=models.CharField(choices=[('manager', 'Manager'), ('inventory', 'Inventory'), ('accountant', 'Accountant'), ('sales', 'Sales')], max_length=255, verbose_name='Staff Type'), - ), - ] diff --git a/inventory/migrations/0017_customer_is_lead_notification_opportunity.py b/inventory/migrations/0017_customer_is_lead_notification_opportunity.py deleted file mode 100644 index 36ddd4f1..00000000 --- a/inventory/migrations/0017_customer_is_lead_notification_opportunity.py +++ /dev/null @@ -1,54 +0,0 @@ -# Generated by Django 5.1.4 on 2024-12-30 22:58 - -import django.db.models.deletion -from django.db import migrations, models - - -class Migration(migrations.Migration): - - dependencies = [ - ('inventory', '0016_alter_staff_staff_type'), - ] - - operations = [ - migrations.AddField( - model_name='customer', - name='is_lead', - field=models.BooleanField(default=True, verbose_name='Is Lead'), - ), - 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_at', models.DateTimeField(auto_now_add=True, verbose_name='Created At')), - ('staff', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, related_name='notifications', to='inventory.staff')), - ], - options={ - 'verbose_name': 'Notification', - 'verbose_name_plural': 'Notifications', - 'ordering': ['-created_at'], - }, - ), - migrations.CreateModel( - name='Opportunity', - fields=[ - ('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')), - ('deal_name', models.CharField(max_length=255, verbose_name='Deal Name')), - ('deal_value', models.DecimalField(decimal_places=2, max_digits=10, verbose_name='Deal Value')), - ('deal_status', models.CharField(choices=[('new', 'New'), ('in_progress', 'In Progress'), ('pending', 'Pending'), ('canceled', 'Canceled'), ('completed', 'Completed')], default='new', max_length=50, verbose_name='Deal Status')), - ('priority', models.CharField(choices=[('low', 'Low'), ('medium', 'Medium'), ('high', 'High'), ('urgent', 'Urgent')], default='low', max_length=50, verbose_name='Priority')), - ('source', models.CharField(choices=[('referrals', 'Referrals'), ('walk_in', 'Walk In'), ('toll_free', 'Toll Free'), ('whatsapp', 'Whatsapp'), ('showroom', 'Showroom'), ('website', 'Website')], default='showroom', max_length=255, verbose_name='Source')), - ('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(blank=True, null=True, on_delete=django.db.models.deletion.SET_NULL, to='inventory.car', verbose_name='Car')), - ('created_by', models.ForeignKey(null=True, on_delete=django.db.models.deletion.SET_NULL, related_name='deals_created', to='inventory.staff')), - ('customer', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, related_name='opportunities', to='inventory.customer')), - ], - options={ - 'verbose_name': 'Opportunity', - 'verbose_name_plural': 'Opportunities', - }, - ), - ] diff --git a/inventory/migrations/0018_remove_notification_staff_notification_user.py b/inventory/migrations/0018_remove_notification_staff_notification_user.py deleted file mode 100644 index d7714e0d..00000000 --- a/inventory/migrations/0018_remove_notification_staff_notification_user.py +++ /dev/null @@ -1,26 +0,0 @@ -# Generated by Django 5.1.4 on 2024-12-31 03:24 - -import django.db.models.deletion -from django.conf import settings -from django.db import migrations, models - - -class Migration(migrations.Migration): - - dependencies = [ - ('inventory', '0017_customer_is_lead_notification_opportunity'), - migrations.swappable_dependency(settings.AUTH_USER_MODEL), - ] - - operations = [ - migrations.RemoveField( - model_name='notification', - name='staff', - ), - migrations.AddField( - model_name='notification', - name='user', - field=models.ForeignKey(default=1, on_delete=django.db.models.deletion.CASCADE, related_name='notifications', to=settings.AUTH_USER_MODEL), - preserve_default=False, - ), - ] diff --git a/inventory/migrations/0019_alter_opportunity_deal_status_and_more.py b/inventory/migrations/0019_alter_opportunity_deal_status_and_more.py deleted file mode 100644 index e6e8c676..00000000 --- a/inventory/migrations/0019_alter_opportunity_deal_status_and_more.py +++ /dev/null @@ -1,28 +0,0 @@ -# Generated by Django 5.1.4 on 2024-12-31 14:30 - -from django.db import migrations, models - - -class Migration(migrations.Migration): - - dependencies = [ - ('inventory', '0018_remove_notification_staff_notification_user'), - ] - - operations = [ - migrations.AlterField( - model_name='opportunity', - name='deal_status', - field=models.CharField(choices=[('new', 'New'), ('pending', 'Pending'), ('canceled', 'Canceled'), ('completed', 'Completed')], default='new', max_length=50, verbose_name='Deal Status'), - ), - migrations.AlterField( - model_name='opportunity', - name='priority', - field=models.CharField(choices=[('low', 'Low'), ('medium', 'Medium'), ('high', 'High')], default='low', max_length=50, verbose_name='Priority'), - ), - migrations.AlterField( - model_name='opportunity', - name='source', - field=models.CharField(choices=[('referrals', 'Referrals'), ('walk_in', 'Walk In'), ('toll_free', 'Toll Free'), ('whatsapp', 'Whatsapp'), ('showrooms', 'Showrooms'), ('website', 'Website'), ('other', 'Other')], default='showrooms', max_length=255, verbose_name='Source'), - ), - ] diff --git a/inventory/migrations/0020_notes.py b/inventory/migrations/0020_notes.py deleted file mode 100644 index a36509c5..00000000 --- a/inventory/migrations/0020_notes.py +++ /dev/null @@ -1,31 +0,0 @@ -# Generated by Django 5.1.4 on 2024-12-31 15:13 - -import django.db.models.deletion -from django.conf import settings -from django.db import migrations, models - - -class Migration(migrations.Migration): - - dependencies = [ - ('inventory', '0019_alter_opportunity_deal_status_and_more'), - migrations.swappable_dependency(settings.AUTH_USER_MODEL), - ] - - operations = [ - migrations.CreateModel( - name='Notes', - fields=[ - ('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')), - ('note', models.TextField(verbose_name='Note')), - ('created_at', models.DateTimeField(auto_now_add=True, verbose_name='Created At')), - ('updated_at', models.DateTimeField(auto_now=True, verbose_name='Updated At')), - ('created_by', models.ForeignKey(null=True, on_delete=django.db.models.deletion.SET_NULL, related_name='notes_created', to=settings.AUTH_USER_MODEL)), - ('opportunity', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, related_name='notes', to='inventory.opportunity')), - ], - options={ - 'verbose_name': 'Notes', - 'verbose_name_plural': 'Notes', - }, - ), - ] diff --git a/inventory/migrations/0021_opportunitylog.py b/inventory/migrations/0021_opportunitylog.py deleted file mode 100644 index 9a30de75..00000000 --- a/inventory/migrations/0021_opportunitylog.py +++ /dev/null @@ -1,34 +0,0 @@ -# Generated by Django 5.1.4 on 2024-12-31 16:13 - -import django.db.models.deletion -from django.conf import settings -from django.db import migrations, models - - -class Migration(migrations.Migration): - - dependencies = [ - ('inventory', '0020_notes'), - migrations.swappable_dependency(settings.AUTH_USER_MODEL), - ] - - operations = [ - migrations.CreateModel( - name='OpportunityLog', - fields=[ - ('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')), - ('action', models.CharField(choices=[('create', 'Create'), ('update', 'Update'), ('delete', 'Delete'), ('status_change', 'Status Change')], max_length=50, verbose_name='Action')), - ('old_status', models.CharField(blank=True, choices=[('new', 'New'), ('pending', 'Pending'), ('canceled', 'Canceled'), ('completed', 'Completed')], max_length=50, null=True, verbose_name='Old Status')), - ('new_status', models.CharField(blank=True, choices=[('new', 'New'), ('pending', 'Pending'), ('canceled', 'Canceled'), ('completed', 'Completed')], max_length=50, null=True, verbose_name='New Status')), - ('details', models.TextField(blank=True, null=True, verbose_name='Details')), - ('created_at', models.DateTimeField(auto_now_add=True, verbose_name='Created At')), - ('opportunity', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, related_name='logs', to='inventory.opportunity')), - ('user', models.ForeignKey(null=True, on_delete=django.db.models.deletion.SET_NULL, to=settings.AUTH_USER_MODEL, verbose_name='User')), - ], - options={ - 'verbose_name': 'Log', - 'verbose_name_plural': 'Logs', - 'ordering': ['-created_at'], - }, - ), - ] diff --git a/inventory/migrations/0023_remove_carfinance_additional_services_and_more.py b/inventory/migrations/0023_remove_carfinance_additional_services_and_more.py deleted file mode 100644 index f844bf25..00000000 --- a/inventory/migrations/0023_remove_carfinance_additional_services_and_more.py +++ /dev/null @@ -1,25 +0,0 @@ -# Generated by Django 5.1.4 on 2025-01-01 16:40 - -import django.db.models.deletion -from django.db import migrations, models - - -class Migration(migrations.Migration): - - dependencies = [ - ('django_ledger', '0017_alter_accountmodel_unique_together_and_more'), - ('inventory', '0022_remove_opportunitylog_user_opportunitylog_staff_and_more'), - ] - - operations = [ - migrations.RemoveField( - model_name='carfinance', - name='additional_services', - ), - migrations.AddField( - model_name='carfinance', - name='additional_services', - field=models.ForeignKey(blank=True, default=1, on_delete=django.db.models.deletion.CASCADE, related_name='additional_finances', to='django_ledger.itemmodel'), - preserve_default=False, - ), - ] diff --git a/inventory/migrations/0024_remove_carfinance_additional_services_and_more.py b/inventory/migrations/0024_remove_carfinance_additional_services_and_more.py deleted file mode 100644 index 85bfa2f4..00000000 --- a/inventory/migrations/0024_remove_carfinance_additional_services_and_more.py +++ /dev/null @@ -1,23 +0,0 @@ -# Generated by Django 5.1.4 on 2025-01-01 16:51 - -from django.db import migrations, models - - -class Migration(migrations.Migration): - - dependencies = [ - ('django_ledger', '0017_alter_accountmodel_unique_together_and_more'), - ('inventory', '0023_remove_carfinance_additional_services_and_more'), - ] - - operations = [ - migrations.RemoveField( - model_name='carfinance', - name='additional_services', - ), - migrations.AddField( - model_name='carfinance', - name='additional_services', - field=models.ManyToManyField(blank=True, related_name='additional_finances', to='django_ledger.itemmodel'), - ), - ] diff --git a/inventory/migrations/__pycache__/__init__.cpython-311.pyc b/inventory/migrations/__pycache__/__init__.cpython-311.pyc deleted file mode 100644 index a006aab7..00000000 Binary files a/inventory/migrations/__pycache__/__init__.cpython-311.pyc and /dev/null differ diff --git a/inventory/models.py b/inventory/models.py index abd9dc10..899d21fa 100644 --- a/inventory/models.py +++ b/inventory/models.py @@ -552,16 +552,7 @@ class Dealer(models.Model, LocalizedNameMixin): 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")) - # parent_dealer = models.ForeignKey( "self", - # on_delete=models.SET_NULL, - # blank=True, - # null=True, - # verbose_name=_("Parent Dealer"), - # related_name="sub_dealers",) - # dealer_type = models.CharField(max_length=255, - # choices=DEALER_TYPES.choices, - # verbose_name=_("Dealer Type"), - # default=DEALER_TYPES.OWNER,) + objects = DealerUserManager() @property diff --git a/inventory/urls.py b/inventory/urls.py index 23978c25..39917767 100644 --- a/inventory/urls.py +++ b/inventory/urls.py @@ -134,7 +134,8 @@ urlpatterns = [ path('sales/estimates/create/', views.create_estimate, name='estimate_create'), path('sales/estimates//estimate_mark_as/', views.estimate_mark_as, name='estimate_mark_as'), path('sales/estimates//preview/', views.EstimatePreviewView.as_view(), name='estimate_preview'), - path('send_email//', views.send_email_view, name='send_email'), + path('sales/estimates//payment_request/', views.PaymentRequest.as_view(), name='payment_request'), + path('sales/estimates//send_email', views.send_email_view, name='send_email'), # Invoice path('sales/invoices/', views.InvoiceListView.as_view(), name='invoice_list'), path('sales/invoices//create/', views.invoice_create, name='invoice_create'), diff --git a/inventory/views.py b/inventory/views.py index 109bd018..5e25f3ea 100644 --- a/inventory/views.py +++ b/inventory/views.py @@ -182,7 +182,6 @@ class AccountingDashboard(LoginRequiredMixin, TemplateView): return redirect("welcome") return super().dispatch(request, *args, **kwargs) - def get_context_data(self, **kwargs): context = super().get_context_data(**kwargs) @@ -198,7 +197,7 @@ class AccountingDashboard(LoginRequiredMixin, TemplateView): total_selling_price = stats["total_selling_price"] or 0 total_profit = total_selling_price - total_cost_price - context['dealer'] = self.request.user.dealer + context["dealer"] = self.request.user.dealer context["total_cars"] = total_cars context["total_reservations"] = total_reservations context["total_cost_price"] = total_cost_price @@ -453,41 +452,49 @@ def inventory_stats_view(request): "total_cars": 0, "trims": {}, } - inventory[make.id_car_make]["models"][model.id_car_model]["total_cars"] += 1 + try: + inventory[make.id_car_make]["models"][model.id_car_model]["total_cars"] += 1 - trim = car.id_car_trim - if ( - trim - and trim.id_car_trim - not in inventory[make.id_car_make]["models"][model.id_car_model]["trims"] - ): + trim = car.id_car_trim + if ( + trim + and trim.id_car_trim + not in inventory[make.id_car_make]["models"][model.id_car_model][ + "trims" + ] + ): + inventory[make.id_car_make]["models"][model.id_car_model]["trims"][ + trim.id_car_trim + ] = { + "trim_id": trim.id_car_trim, + "trim_name": trim.name, + "total_cars": 0, + } inventory[make.id_car_make]["models"][model.id_car_model]["trims"][ trim.id_car_trim - ] = {"trim_id": trim.id_car_trim, "trim_name": trim.name, "total_cars": 0} - inventory[make.id_car_make]["models"][model.id_car_model]["trims"][ - trim.id_car_trim - ]["total_cars"] += 1 - - result = { - "total_cars": cars.count(), - "makes": [ - { - "make_id": make_data["make_id"], - "make_name": make_data["make_name"], - "total_cars": make_data["total_cars"], - "models": [ - { - "model_id": model_data["model_id"], - "model_name": model_data["model_name"], - "total_cars": model_data["total_cars"], - "trims": list(model_data["trims"].values()), - } - for model_data in make_data["models"].values() - ], - } - for make_data in inventory.values() - ], - } + ]["total_cars"] += 1 + except Exception as e: + print(e) + result = { + "total_cars": cars.count(), + "makes": [ + { + "make_id": make_data["make_id"], + "make_name": make_data["make_name"], + "total_cars": make_data["total_cars"], + "models": [ + { + "model_id": model_data["model_id"], + "model_name": model_data["model_name"], + "total_cars": model_data["total_cars"], + "trims": list(model_data["trims"].values()), + } + for model_data in make_data["models"].values() + ], + } + for make_data in inventory.values() + ], + } return render(request, "inventory/inventory_stats.html", {"inventory": result}) @@ -666,10 +673,11 @@ class DealerDetailView(LoginRequiredMixin, DetailView): context_object_name = "dealer" def get_queryset(self): - total_count = models.Dealer.objects.annotate( - staff_count=Coalesce(Count('staff'), Value(0)), - total_count=F('staff_count') + Value(1)) - return total_count + total_count = models.Dealer.objects.annotate( + staff_count=Coalesce(Count("staff"), Value(0)), + total_count=F("staff_count") + Value(1), + ) + return total_count class DealerUpdateView(LoginRequiredMixin, SuccessMessageMixin, UpdateView): @@ -695,12 +703,12 @@ class DealerUpdateView(LoginRequiredMixin, SuccessMessageMixin, UpdateView): # return forms.UserForm -class CustomerListView(LoginRequiredMixin, PermissionRequiredMixin, ListView): +class CustomerListView(LoginRequiredMixin, ListView): model = models.Customer + home_label = _("customers") context_object_name = "customers" paginate_by = 10 template_name = "customers/customer_list.html" - permission_required = ("inventory.view_customer",) def get_queryset(self): query = self.request.GET.get("q") @@ -722,16 +730,14 @@ class CustomerListView(LoginRequiredMixin, PermissionRequiredMixin, ListView): return context -class CustomerDetailView(LoginRequiredMixin, PermissionRequiredMixin, DetailView): +class CustomerDetailView(LoginRequiredMixin, DetailView): model = models.Customer template_name = "customers/view_customer.html" context_object_name = "customer" - permission_required = ("inventory.view_customer",) class CustomerCreateView( LoginRequiredMixin, - PermissionRequiredMixin, SuccessMessageMixin, CreateView, ): @@ -739,7 +745,6 @@ class CustomerCreateView( form_class = forms.CustomerForm template_name = "customers/customer_form.html" success_url = reverse_lazy("customer_list") - permission_required = ("inventory.add_customer",) success_message = _("Customer created successfully.") def form_valid(self, form): @@ -747,10 +752,8 @@ class CustomerCreateView( return super().form_valid(form) - class CustomerUpdateView( LoginRequiredMixin, - PermissionRequiredMixin, SuccessMessageMixin, UpdateView, ): @@ -758,7 +761,6 @@ class CustomerUpdateView( form_class = forms.CustomerForm template_name = "customers/customer_form.html" success_url = reverse_lazy("customer_list") - permission_required = ("inventory.change_customer",) success_message = _("Customer updated successfully.") @@ -770,23 +772,20 @@ def delete_customer(request, pk): return redirect("customer_list") -class VendorListView(LoginRequiredMixin, PermissionRequiredMixin, ListView): +class VendorListView(LoginRequiredMixin, ListView): model = models.Vendor context_object_name = "vendors" paginate_by = 10 template_name = "vendors/vendors_list.html" - permission_required = ("inventory.view_vendor",) -class VendorDetailView(LoginRequiredMixin, PermissionRequiredMixin, DetailView): +class VendorDetailView(LoginRequiredMixin, DetailView): model = models.Vendor template_name = "vendors/view_vendor.html" - permission_required = ("inventory.view_vendor",) class VendorCreateView( LoginRequiredMixin, - PermissionRequiredMixin, SuccessMessageMixin, CreateView, ): @@ -794,7 +793,6 @@ class VendorCreateView( form_class = forms.VendorForm template_name = "vendors/vendor_form.html" success_url = reverse_lazy("vendor_list") - permission_required = ("inventory.add_vendor",) success_message = _("Vendor created successfully.") def form_valid(self, form): @@ -804,7 +802,6 @@ class VendorCreateView( class VendorUpdateView( LoginRequiredMixin, - PermissionRequiredMixin, SuccessMessageMixin, UpdateView, ): @@ -812,7 +809,6 @@ class VendorUpdateView( form_class = forms.VendorForm template_name = "vendors/vendor_form.html" success_url = reverse_lazy("vendor_list") - permission_required = ("inventory.change_vendor",) success_message = _("Vendor updated successfully.") @@ -824,11 +820,10 @@ def delete_vendor(request, pk): return redirect("vendor_list") -class QuotationCreateView(LoginRequiredMixin, PermissionRequiredMixin, CreateView): +class QuotationCreateView(LoginRequiredMixin, CreateView): model = models.SaleQuotation form_class = forms.QuotationForm template_name = "sales/quotation_form.html" - permission_required = ("inventory.add_salequotation",) def form_valid(self, form): dealer = self.request.user.dealer @@ -847,12 +842,11 @@ class QuotationCreateView(LoginRequiredMixin, PermissionRequiredMixin, CreateVie return redirect("quotation_list") -class QuotationListView(LoginRequiredMixin, PermissionRequiredMixin, ListView): +class QuotationListView(LoginRequiredMixin, ListView): model = models.SaleQuotation template_name = "sales/quotation_list.html" context_object_name = "quotations" paginate_by = 10 - permission_required = ("inventory.view_salequotation",) def get_queryset(self): status = self.request.GET.get("status") @@ -862,11 +856,10 @@ class QuotationListView(LoginRequiredMixin, PermissionRequiredMixin, ListView): return queryset -class QuotationDetailView(LoginRequiredMixin, PermissionRequiredMixin, DetailView): +class QuotationDetailView(LoginRequiredMixin, DetailView): model = models.SaleQuotation template_name = "sales/quotation_detail.html" context_object_name = "quotation" - permission_required = ("inventory.view_salequotation",) def get_context_data(self, **kwargs): context = super().get_context_data(**kwargs) @@ -1196,34 +1189,30 @@ def confirm_quotation(request, pk): return redirect("quotation_detail", pk=pk) -class SalesOrderDetailView(LoginRequiredMixin, PermissionRequiredMixin, DetailView): +class SalesOrderDetailView(LoginRequiredMixin, DetailView): model = models.SalesOrder template_name = "sales/sales_order_detail.html" context_object_name = "sales_order" - permission_required = ("inventory.view_salequotation",) slug_field = "order_id" slug_url_kwarg = "order_id" # Users -class UserListView(LoginRequiredMixin, PermissionRequiredMixin, ListView): +class UserListView(LoginRequiredMixin, ListView): model = models.Staff context_object_name = "users" paginate_by = 10 template_name = "users/user_list.html" - permission_required = ("inventory.view_dealer",) -class UserDetailView(LoginRequiredMixin, PermissionRequiredMixin, DetailView): +class UserDetailView(LoginRequiredMixin, DetailView): model = models.Staff template_name = "users/user_detail.html" context_object_name = "user_" - permission_required = ("inventory.view_dealer",) class UserCreateView( LoginRequiredMixin, - PermissionRequiredMixin, SuccessMessageMixin, CreateView, ): @@ -1231,7 +1220,6 @@ class UserCreateView( form_class = forms.StaffForm template_name = "users/user_form.html" success_url = reverse_lazy("user_list") - permission_required = ("inventory.add_dealer",) success_message = _("User created successfully.") def form_valid(self, form): @@ -1249,7 +1237,6 @@ class UserCreateView( class UserUpdateView( LoginRequiredMixin, - PermissionRequiredMixin, SuccessMessageMixin, UpdateView, ): @@ -1257,7 +1244,6 @@ class UserUpdateView( form_class = forms.StaffForm template_name = "users/user_form.html" success_url = reverse_lazy("user_list") - permission_required = ("inventory.change_dealer",) success_message = _("User updated successfully.") def get_form_kwargs(self): @@ -1738,55 +1724,36 @@ def create_estimate(request): if request.method == "POST": try: data = json.loads(request.body) - title = data["title"] - customer_id = data["customer"] - terms = data["terms"] + title = data.get("title") + customer_id = data.get("customer") + terms = data.get("terms") customer = entity.get_customers().filter(pk=customer_id).first() estimate = entity.create_estimate( - estimate_title=title, - customer_model=customer, - contract_terms=terms) + estimate_title=title, customer_model=customer, contract_terms=terms + ) - items = data.get("item[]", []) - quantities = data.get("quantity[]", []) - items_list = [ - {"item_id": items[i], "quantity": quantities[i]} - for i in range(len(items)) - ] + items = data.get("item", []) + quantities = data.get("quantity", []) - if items_list: + if items and quantities: if isinstance(items, list): - items = [ - { - "item_number": entity.get_items_all() - .filter(pk=item.get("item_id")) - .first() - .item_number, - "quantity": item.get("quantity"), - "unit_cost": models.Car.objects.get( - vin=entity.get_items_all() - .filter(pk=item.get("item_id")) - .first() - .cost_price - ), - "unit_revenue": models.Car.objects.get( - vin=entity.get_items_all() - .filter(pk=item.get("item_id")) - .first() - .selling_price - ), - "total_amount": models.Car.objects.get( - vin=entity.get_items_all() - .filter(pk=item.get("item_id")) - .first() - .cost_price - * item.get("quantity") - ), - } - for item in items_list + items_list = [ + {"item_id": items[i], "quantity": quantities[i]} + for i in range(len(items)) ] - # items = [entity.get_items_all().filter(pk=item).first() for item in items] + items_txs = [] + for item in items_list: + item_instance = ItemModel.objects.get(pk=item.get("item_id")) + car_instace = models.Car.objects.get(vin=item_instance.name) + items_txs.append({ + "item_number": item_instance.item_number, + "quantity": float(item.get("quantity")), + "unit_cost": car_instace.finances.cost_price, + "unit_revenue": car_instace.finances.selling_price, + "total_amount": car_instace.finances.cost_price * int(item.get("quantity")), + }) + estimate_itemtxs = { item.get("item_number"): { "unit_cost": item.get("unit_cost"), @@ -1794,17 +1761,18 @@ def create_estimate(request): "quantity": item.get("quantity"), "total_amount": item.get("total_amount"), } - for item in items + for item in items_txs } else: item = entity.get_items_all().filter(pk=items).first() - instance = models.Car.objects.get(vin=item) + + instance = models.Car.objects.get(vin=item.name) estimate_itemtxs = { item.item_number: { "unit_cost": instance.finances.cost_price, "unit_revenue": instance.finances.selling_price, "quantity": float(quantities), - "total_amount": instance.finances.total, + "total_amount": instance.finances.total * int(quantities), } } estimate.migrate_itemtxs( @@ -1812,13 +1780,25 @@ def create_estimate(request): commit=True, operation=EstimateModel.ITEMIZE_APPEND, ) - url = reverse_lazy("estimate_detail", kwargs={"pk": estimate.pk}) + + if isinstance(items, list): + for item in items: + instance = models.Car.objects.get(vin=item) + instance.status = models.CarStatusChoices.RESERVED + instance.save() + else: + instance = models.Car.objects.get(vin=items) + instance.status = models.CarStatusChoices.RESERVED + instance.save() + url = reverse("estimate_detail", kwargs={"pk": estimate.pk}) + return JsonResponse( { "status": "success", "message": "Estimate created successfully!", "url": url, - } + }, + status=200, ) except Exception as e: return JsonResponse( @@ -1859,6 +1839,20 @@ class EstimateDetailView(LoginRequiredMixin, DetailView): return super().get_context_data(**kwargs) +class PaymentRequest(LoginRequiredMixin, DetailView): + model = EstimateModel + template_name = "sales/estimates/payment_request_detail.html" + context_object_name = "estimate" + + def get_context_data(self, **kwargs): + context = super().get_context_data(**kwargs) + context["cars"] = [ + models.Car.objects.get(vin=car.item_model.name) + for car in context["estimate"].get_itemtxs_data()[0].all() + ] + return context + + class EstimatePreviewView(LoginRequiredMixin, DetailView): model = EstimateModel context_object_name = "estimate" @@ -1888,11 +1882,16 @@ def estimate_mark_as(request, pk): messages.error(request, "Estimate is not ready for review") return redirect("estimate_detail", pk=estimate.pk) estimate.mark_as_review() - elif mark == "accepted": + elif mark == "approved": if not estimate.can_approve(): messages.error(request, "Estimate is not ready for approval") return redirect("estimate_detail", pk=estimate.pk) estimate.mark_as_approved() + elif mark == "rejected": + if not estimate.can_cancel(): + messages.error(request, "Estimate is not ready for rejection") + return redirect("estimate_detail", pk=estimate.pk) + estimate.mark_as_canceled() elif mark == "completed": if not estimate.can_complete(): messages.error(request, "Estimate is not ready for completion") @@ -2170,54 +2169,65 @@ class UserActivityLogListView(ListView): # email def send_email_view(request, pk): estimate = get_object_or_404(EstimateModel, pk=pk) - if not estimate.can_review(): - messages.error(request, "Estimate is not ready for review") + if request.method == "POST": + # if not estimate.can_review(): + # messages.error(request, "Estimate is not ready for review") + # return redirect("estimate_detail", pk=estimate.pk) + if not estimate.get_itemtxs_data()[0]: + messages.error(request, "Estimate has no items") + return redirect("estimate_detail", pk=estimate.pk) + + send_email( + "manager@tenhal.com", + request.POST.get("to"), + request.POST.get("subject"), + request.POST.get("message"), + ) + # estimate.mark_as_review() + messages.success(request, "Email sent successfully!") return redirect("estimate_detail", pk=estimate.pk) + link = reverse_lazy("estimate_preview", kwargs={"pk": estimate.pk}) msg = f""" - السلام عليكم - Dear {estimate.customer.customer_name}, + السلام عليكم + Dear {estimate.customer.customer_name}, - أود أن أشارككم تقدير المشروع الذي ناقشناه. يرجى العثور على الوثيقة التفصيلية للمقترح المرفقة. + أود أن أشارككم تقدير المشروع الذي ناقشناه. يرجى العثور على الوثيقة التفصيلية للمقترح المرفقة. - I hope this email finds you well. I wanted to share with you the estimate for the project we discussed. Please find the detailed estimate document attached. + I hope this email finds you well. I wanted to share with you the estimate for the project we discussed. Please find the detailed estimate document attached. - يرجى مراجعة المقترح وإعلامي إذا كانت لديك أي أسئلة أو مخاوف. إذا كانت كل شيء يبدو جيدًا، يمكننا المضي قدمًا في المشروع. + يرجى مراجعة المقترح وإعلامي إذا كانت لديك أي أسئلة أو مخاوف. إذا كانت كل شيء يبدو جيدًا، يمكننا المضي قدمًا في المشروع. - Please review the estimate and let me know if you have any questions or concerns. If everything looks good, we can proceed with the project. + Please review the estimate and let me know if you have any questions or concerns. If everything looks good, we can proceed with the project. - شكراً لاهتمامكم بهذا الأمر. - Thank you for your attention to this matter. + Estimate Link: + {link} - Estimate Link: - View Estimate + شكراً لاهتمامكم بهذا الأمر. + Thank you for your attention to this matter. - تحياتي, - Best regards, - [Your Name] - [Your Position] - [Your Company Name] - [Your Contact Information] - """ - send_email( - "manager@tenhal.sa", - "user@tenhal.sa", - f"Estimate-{estimate.estimate_number}", - msg, + تحياتي, + Best regards, + [Your Name] + [Your Position] + [Your Company] + [Your Contact Information] + """ + return render( + request, + "sales/estimates/estimate_send.html", + {"estimate": estimate, "message": msg}, ) - estimate.mark_as_review() - messages.success(request, "Email sent successfully!") - return redirect("estimate_detail", pk=estimate.pk) def create_lead(request, pk): customer = get_object_or_404(models.Customer, pk=pk) if customer.is_lead: - messages.warning(request, _('Customer is already a lead.')) + messages.warning(request, _("Customer is already a lead.")) else: customer.is_lead = True customer.save() - messages.success(request, _('Customer successfully marked as a lead.')) - return redirect(reverse('customer_detail', kwargs={'pk': customer.pk})) + messages.success(request, _("Customer successfully marked as a lead.")) + return redirect(reverse("customer_detail", kwargs={"pk": customer.pk})) class OpportunityCreateView(CreateView): @@ -2232,33 +2242,35 @@ class OpportunityCreateView(CreateView): return context def form_valid(self, form): - form.instance.customer = models.Customer.objects.get(pk=self.kwargs['customer_id']) + form.instance.customer = models.Customer.objects.get( + pk=self.kwargs["customer_id"] + ) form.instance.created_by = self.request.user.staff return super().form_valid(form) def get_success_url(self): - return reverse_lazy('opportunity_detail', kwargs={'pk': self.object.pk}) + return reverse_lazy("opportunity_detail", kwargs={"pk": self.object.pk}) class OpportunityUpdateView(UpdateView): model = models.Opportunity form_class = forms.OpportunityForm - template_name = 'crm/opportunity_form.html' + template_name = "crm/opportunity_form.html" def get_success_url(self): - return reverse_lazy('opportunity_detail', kwargs={'pk': self.object.pk}) + return reverse_lazy("opportunity_detail", kwargs={"pk": self.object.pk}) class OpportunityDetailView(DetailView): model = models.Opportunity - template_name = 'crm/opportunity_detail.html' - context_object_name = 'opportunity' + template_name = "crm/opportunity_detail.html" + context_object_name = "opportunity" class OpportunityListView(ListView): model = models.Opportunity - template_name = 'crm/opportunity_list.html' - context_object_name = 'opportunities' + template_name = "crm/opportunity_list.html" + context_object_name = "opportunities" @login_required @@ -2271,27 +2283,31 @@ def delete_opportunity(request, pk): class OpportunityLogsView(LoginRequiredMixin, ListView): model = models.OpportunityLog - template_name = 'crm/opportunity_logs.html' - context_object_name = 'logs' + template_name = "crm/opportunity_logs.html" + context_object_name = "logs" def get_queryset(self): - opportunity_id = self.kwargs['pk'] - return models.OpportunityLog.objects.filter(opportunity_id=opportunity_id).order_by('-created_at') + opportunity_id = self.kwargs["pk"] + return models.OpportunityLog.objects.filter( + opportunity_id=opportunity_id + ).order_by("-created_at") def get_context_data(self, **kwargs): context = super().get_context_data(**kwargs) - context['opportunity'] = models.Opportunity.objects.get(pk=self.kwargs['pk']) + context["opportunity"] = models.Opportunity.objects.get(pk=self.kwargs["pk"]) return context class NotificationListView(LoginRequiredMixin, ListView): model = models.Notification - template_name = 'notifications_history.html' - context_object_name = 'notifications' + template_name = "notifications_history.html" + context_object_name = "notifications" paginate_by = 10 def get_queryset(self): - return models.Notification.objects.filter(user=self.request.user).order_by('-created_at') + return models.Notification.objects.filter(user=self.request.user).order_by( + "-created_at" + ) @login_required @@ -2300,23 +2316,23 @@ def mark_notification_as_read(request, pk): notification.is_read = True notification.save() messages.success(request, _("Notification marked as read.")) - return redirect('notifications_history') - - + return redirect("notifications_history") @login_required def fetch_notifications(request): - notifications = models.Notification.objects.filter(user=request.user, is_read=False).order_by('-created_at') + notifications = models.Notification.objects.filter( + user=request.user, is_read=False + ).order_by("-created_at") notifications_data = [ { - 'id': notification.id, - 'message': notification.message, - 'created_at': notification.created_at.strftime('%Y-%m-%d %H:%M:%S'), + "id": notification.id, + "message": notification.message, + "created_at": notification.created_at.strftime("%Y-%m-%d %H:%M:%S"), } for notification in notifications ] - return JsonResponse({'notifications': notifications_data}) + return JsonResponse({"notifications": notifications_data}) class ItemServiceCreateView(CreateView): diff --git a/templates/base.html b/templates/base.html index 2670e5e4..9867aaec 100644 --- a/templates/base.html +++ b/templates/base.html @@ -100,12 +100,21 @@ }); Toast.fire({ icon: "{{ message.tags }}", - titleText: "{{ message| safe }}" -}); + titleText: "{{ message| safe }}"}); {% endfor %} {% endif %} - +const Toast = Swal.mixin({ + toast: true, + position: "top-end", + showConfirmButton: false, + timer: 2000, + timerProgressBar: false, + didOpen: (toast) => { + toast.onmouseenter = Swal.stopTimer; + toast.onmouseleave = Swal.resumeTimer; + } + }); function notify(tag,msg){ Toast.fire({ icon: tag, diff --git a/templates/sales/estimates/estimate_detail.html b/templates/sales/estimates/estimate_detail.html index 2ef84201..068ca883 100644 --- a/templates/sales/estimates/estimate_detail.html +++ b/templates/sales/estimates/estimate_detail.html @@ -19,7 +19,7 @@ data-bs-dismiss="modal"> {% trans 'No' %} -
+ {% csrf_token %}
@@ -30,6 +30,8 @@ + +
@@ -38,16 +40,18 @@

{% trans 'Estimate' %}

{% if estimate.status == 'draft' %} - {% trans 'Send Estimate' %} - + {% trans 'Send Estimate' %} + {% endif %} {% if estimate.status == 'in_review' %} - + {% endif %} {% if estimate.status == 'approved' %} {% trans 'Create Invoice' %} {% endif %} - {% trans 'Preview' %} + {% if estimate.status == 'in_review' %} + {% trans 'Preview' %} + {% endif %}
@@ -99,8 +103,8 @@ {% trans "In Review" %} {% elif estimate.status == 'approved' %} {% trans "Approved" %} - {% elif estimate.status == 'declined' %} - {% trans "Declined" %} + {% elif estimate.status == 'canceled' %} + {% trans "canceled" %} {% endif %}
diff --git a/templates/sales/estimates/estimate_form.html b/templates/sales/estimates/estimate_form.html index e23dcb26..084a5b2c 100644 --- a/templates/sales/estimates/estimate_form.html +++ b/templates/sales/estimates/estimate_form.html @@ -7,7 +7,7 @@ {% block content %}

{% trans "Create Estimate" %}

-
+ {% csrf_token %}
{{ form|crispy }} @@ -25,24 +25,9 @@
-
@@ -58,119 +43,105 @@
+{% endblock content %} - - - -{% endblock %} \ No newline at end of file + +{% endblock extra_js %} \ No newline at end of file diff --git a/templates/sales/estimates/estimate_preview.html b/templates/sales/estimates/estimate_preview.html index 448d0fbe..60be3c76 100644 --- a/templates/sales/estimates/estimate_preview.html +++ b/templates/sales/estimates/estimate_preview.html @@ -10,13 +10,12 @@ - {% if LANGUAGE_CODE == 'en' %} @@ -120,66 +120,138 @@ {% endif %} + -
- +{% if estimate.status != "in_review" %} +
+
+
+
+
+
+
+

Page Missing!

+

But no worries! Our ostrich is looking everywhere
+

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

{% trans "Estimate" %}

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

@@ -233,10 +305,8 @@

{% trans "If you have any questions, feel free to contact us at" %} support@example.com.

{% trans "Thank you for your business" %}

-
- - + {% endif %} @@ -263,6 +333,18 @@ // Generate and download the PDF html2pdf().from(element).set(options).save(); }); + + document.getElementById('confirmAccept').addEventListener('click', function () { + // Handle the accept action here + alert('Estimate Accepted'); + $('#acceptModal').modal('hide'); + }); + + document.getElementById('confirmReject').addEventListener('click', function () { + // Handle the reject action here + alert('Estimate Rejected'); + $('#rejectModal').modal('hide'); + }); \ No newline at end of file diff --git a/templates/sales/estimates/estimate_send.html b/templates/sales/estimates/estimate_send.html new file mode 100644 index 00000000..7091686a --- /dev/null +++ b/templates/sales/estimates/estimate_send.html @@ -0,0 +1,36 @@ +{% extends "base.html" %} +{% load crispy_forms_filters %} +{% load i18n static %} + +{% block title %}{{ _("Quotations") }}{% endblock title %} + +{% block content %} + + + + +{% endblock content %} \ No newline at end of file diff --git a/templates/sales/estimates/payment_request_detail.html b/templates/sales/estimates/payment_request_detail.html new file mode 100644 index 00000000..6d7ca734 --- /dev/null +++ b/templates/sales/estimates/payment_request_detail.html @@ -0,0 +1,235 @@ + + + + + + عرض سعر السيارة - Tenhall + + + + +
+
+

عرض سعر السيارة - Tenhal

+
+ +
+

المكرمين/

+ +
+ +

السلام عليكم و رحمة الله و بركاته،

+ +

بناء على طلبكم، نورد لكم عرض سعر للسيارة وهو يعد إيجابا منا بالبيع:

+ + + + + + + + + {% for car in cars %} + + + + {% endfor %} + +
نوع السيارةاللون الخارجياللون الداخلي
+ +
+

حمولة المركبة (

+ +

) سنة الصنع (

+ +

) (جديد / مستعملة) كلم/ميل

+
+ +
+

مستوى اقتصاد الوقود (

+ +

) رقم الشاسيه "في حال كانت السيارة مستعملة فقط" (

+ +

)

+
+ +
+

مواصفات أخرى:

+ +
+ + + + + + + + + + + + +
سعر السيارة الأساسيمبلغ ضريبة القيمة المضافة (15% VAT)إجمالي سعر السيارة مع الضريبة
+ +

إجمالي سعر السيارة مع الضريبة كتابة: ريالا سعوديا فقط لا غير

+ +
+

مدة الضمان:

+ +

شهرا، أو

+ +

كيلومترا /ميل (أيهما يأتي أولا)

+
+ +
+

ملاحظات:

+ +
+ +
+

اسم الشركة/ الوكالة:

+ +
+ +
+

العنوان: المدينة شارع

+ +
+ +
+

ص.ب رمز بريدي الهاتف:

+ +
+ +
+
+

الموظف المسؤول التوقيع:

+ +
+
+

التاريخ:

+ +

/

+ +

/

+ +

م الختم

+
+
+ + +
+ + + \ No newline at end of file