This commit is contained in:
Faheedkhan 2025-06-01 15:17:05 +03:00
parent 5b4d6bf2b2
commit 7b290e84ba
26 changed files with 1488 additions and 555 deletions

View File

@ -1,10 +1,12 @@
#!/bin/sh
echo "Delete Old Migrations"
find ./inventory -type f -iname "00*.py" -delete
find ./haikalbot -type f -iname "00*.py" -delete
echo "Delete Old Cache"
find ./car_inventory -type d -iname "__pycache__"|xargs rm -rf
find ./inventory -type d -iname "__pycache__"|xargs rm -rf
find ./haikalbot -type d -iname "__pycache__"|xargs rm -rf
echo "Apply Base Migrate"

View File

@ -1,5 +1,8 @@
# Generated by Django 5.2.1 on 2025-05-25 23:01
# Generated by Django 5.1.7 on 2025-06-01 11:25
import django.db.models.deletion
import django.utils.timezone
from django.conf import settings
from django.db import migrations, models
@ -8,6 +11,7 @@ class Migration(migrations.Migration):
initial = True
dependencies = [
migrations.swappable_dependency(settings.AUTH_USER_MODEL),
]
operations = [
@ -17,7 +21,26 @@ class Migration(migrations.Migration):
('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
('user_message', models.TextField()),
('chatbot_response', models.TextField()),
('timestamp', models.DateTimeField(auto_now_add=True)),
('timestamp', models.DateTimeField(auto_now_add=True, db_index=True)),
],
options={
'ordering': ['-timestamp'],
},
),
migrations.CreateModel(
name='AnalysisCache',
fields=[
('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
('prompt_hash', models.CharField(db_index=True, max_length=64)),
('dealer_id', models.IntegerField(blank=True, db_index=True, null=True)),
('created_at', models.DateTimeField(default=django.utils.timezone.now)),
('updated_at', models.DateTimeField(auto_now=True)),
('expires_at', models.DateTimeField(db_index=True)),
('result', models.JSONField()),
('user', models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.CASCADE, to=settings.AUTH_USER_MODEL)),
],
options={
'verbose_name_plural': 'Analysis caches',
},
),
]

View File

@ -1,4 +1,4 @@
# Generated by Django 5.2.1 on 2025-05-25 23:01
# Generated by Django 5.1.7 on 2025-06-01 11:25
import django.db.models.deletion
from django.db import migrations, models
@ -19,4 +19,16 @@ class Migration(migrations.Migration):
name='dealer',
field=models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, related_name='chatlogs', to='inventory.dealer'),
),
migrations.AddIndex(
model_name='analysiscache',
index=models.Index(fields=['prompt_hash', 'dealer_id'], name='haikalbot_a_prompt__b98e1e_idx'),
),
migrations.AddIndex(
model_name='analysiscache',
index=models.Index(fields=['expires_at'], name='haikalbot_a_expires_e790cd_idx'),
),
migrations.AddIndex(
model_name='chatlog',
index=models.Index(fields=['dealer', 'timestamp'], name='haikalbot_c_dealer__6f8d63_idx'),
),
]

View File

@ -1,33 +0,0 @@
# Generated by Django 5.2.1 on 2025-05-26 00:28
import django.db.models.deletion
import django.utils.timezone
from django.conf import settings
from django.db import migrations, models
class Migration(migrations.Migration):
dependencies = [
('haikalbot', '0002_initial'),
migrations.swappable_dependency(settings.AUTH_USER_MODEL),
]
operations = [
migrations.CreateModel(
name='AnalysisCache',
fields=[
('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
('prompt_hash', models.CharField(db_index=True, max_length=64)),
('dealer_id', models.IntegerField(blank=True, db_index=True, null=True)),
('created_at', models.DateTimeField(default=django.utils.timezone.now)),
('updated_at', models.DateTimeField(auto_now=True)),
('expires_at', models.DateTimeField()),
('result', models.JSONField()),
('user', models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.CASCADE, to=settings.AUTH_USER_MODEL)),
],
options={
'indexes': [models.Index(fields=['prompt_hash', 'dealer_id'], name='haikalbot_a_prompt__b98e1e_idx'), models.Index(fields=['expires_at'], name='haikalbot_a_expires_e790cd_idx')],
},
),
]

View File

@ -1,36 +0,0 @@
# Generated by Django 5.2.1 on 2025-05-26 08:17
from django.db import migrations, models
class Migration(migrations.Migration):
dependencies = [
('haikalbot', '0003_analysiscache'),
('inventory', '0001_initial'),
]
operations = [
migrations.AlterModelOptions(
name='analysiscache',
options={'verbose_name_plural': 'Analysis caches'},
),
migrations.AlterModelOptions(
name='chatlog',
options={'ordering': ['-timestamp']},
),
migrations.AlterField(
model_name='analysiscache',
name='expires_at',
field=models.DateTimeField(db_index=True),
),
migrations.AlterField(
model_name='chatlog',
name='timestamp',
field=models.DateTimeField(auto_now_add=True, db_index=True),
),
migrations.AddIndex(
model_name='chatlog',
index=models.Index(fields=['dealer', 'timestamp'], name='haikalbot_c_dealer__6f8d63_idx'),
),
]

View File

@ -1,4 +1,4 @@
# Generated by Django 5.2.1 on 2025-05-25 23:01
# Generated by Django 5.1.7 on 2025-06-01 11:25
import datetime
import django.core.validators
@ -21,7 +21,7 @@ class Migration(migrations.Migration):
('appointment', '0001_initial'),
('auth', '0012_alter_user_first_name_max_length'),
('contenttypes', '0002_remove_content_type_name'),
('django_ledger', '0001_initial'),
('django_ledger', '0021_alter_bankaccountmodel_account_model_and_more'),
migrations.swappable_dependency(settings.AUTH_USER_MODEL),
]
@ -155,6 +155,7 @@ class Migration(migrations.Migration):
('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')),
('is_sold', models.BooleanField(default=False)),
('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')),
],
@ -475,6 +476,36 @@ class Migration(migrations.Migration):
'verbose_name_plural': 'Emails',
},
),
migrations.CreateModel(
name='Lead',
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')),
('last_name', models.CharField(max_length=50, verbose_name='Last Name')),
('email', models.EmailField(max_length=254, verbose_name='Email')),
('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')),
('lead_type', models.CharField(choices=[('customer', 'Customer'), ('organization', 'Organization')], default='customer', max_length=50, verbose_name='Lead Type')),
('source', models.CharField(choices=[('referrals', 'Referrals'), ('whatsapp', 'WhatsApp'), ('showroom', 'Showroom'), ('tiktok', 'TikTok'), ('instagram', 'Instagram'), ('x', 'X'), ('facebook', 'Facebook'), ('motory', 'Motory'), ('influencers', 'Influencers'), ('youtube', 'Youtube'), ('campaign', 'Campaign')], max_length=50, verbose_name='Source')),
('channel', models.CharField(choices=[('walk_in', 'Walk In'), ('toll_free', 'Toll Free'), ('website', 'Website'), ('email', 'Email'), ('form', 'Form')], max_length=50, verbose_name='Channel')),
('status', models.CharField(choices=[('new', 'New'), ('contacted', 'Contacted'), ('qualified', 'Qualified'), ('unqualified', 'Unqualified'), ('converted', 'Converted')], db_index=True, default='new', max_length=50, verbose_name='Status')),
('next_action', models.CharField(blank=True, max_length=255, null=True, verbose_name='Next Action')),
('next_action_date', models.DateTimeField(blank=True, null=True, verbose_name='Next Action Date')),
('is_converted', models.BooleanField(default=False)),
('converted_at', models.DateTimeField(blank=True, null=True)),
('created', models.DateTimeField(auto_now_add=True, db_index=True, verbose_name='Created')),
('updated', models.DateTimeField(auto_now=True, verbose_name='Updated')),
('slug', models.SlugField(blank=True, null=True, unique=True)),
('customer', models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.CASCADE, related_name='customer_leads', to='inventory.customer')),
('dealer', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, related_name='leads', to='inventory.dealer')),
('id_car_make', models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.DO_NOTHING, to='inventory.carmake', verbose_name='Make')),
('id_car_model', models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.DO_NOTHING, to='inventory.carmodel', verbose_name='Model')),
],
options={
'verbose_name': 'Lead',
'verbose_name_plural': 'Leads',
},
),
migrations.CreateModel(
name='Notes',
fields=[
@ -534,41 +565,41 @@ class Migration(migrations.Migration):
bases=(models.Model, inventory.mixins.LocalizedNameMixin),
),
migrations.CreateModel(
name='Lead',
name='Opportunity',
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')),
('last_name', models.CharField(max_length=50, verbose_name='Last Name')),
('email', models.EmailField(max_length=254, verbose_name='Email')),
('phone_number', phonenumber_field.modelfields.PhoneNumberField(max_length=128, region='SA', verbose_name='Phone Number')),
('lead_type', models.CharField(choices=[('customer', 'Customer'), ('organization', 'Organization')], default='customer', max_length=50, verbose_name='Lead Type')),
('year', models.PositiveSmallIntegerField(blank=True, null=True, verbose_name='Year')),
('source', models.CharField(choices=[('referrals', 'Referrals'), ('whatsapp', 'WhatsApp'), ('showroom', 'Showroom'), ('tiktok', 'TikTok'), ('instagram', 'Instagram'), ('x', 'X'), ('facebook', 'Facebook'), ('motory', 'Motory'), ('influencers', 'Influencers'), ('youtube', 'Youtube'), ('campaign', 'Campaign')], max_length=50, verbose_name='Source')),
('channel', models.CharField(choices=[('walk_in', 'Walk In'), ('toll_free', 'Toll Free'), ('website', 'Website'), ('email', 'Email'), ('form', 'Form')], max_length=50, verbose_name='Channel')),
('crn', models.CharField(blank=True, max_length=10, null=True, unique=True, verbose_name='Commercial Registration Number')),
('vrn', models.CharField(blank=True, max_length=15, null=True, unique=True, verbose_name='VAT Registration Number')),
('address', models.CharField(max_length=50, verbose_name='address')),
('priority', models.CharField(choices=[('low', 'Low'), ('medium', 'Medium'), ('high', 'High')], default='medium', max_length=10, verbose_name='Priority')),
('status', models.CharField(choices=[('new', 'New'), ('follow_up', 'Follow-up'), ('negotiation', 'Negotiation'), ('won', 'Won'), ('lost', 'Lost'), ('closed', 'Closed')], db_index=True, default='new', max_length=50, verbose_name='Status')),
('next_action', models.CharField(blank=True, max_length=255, null=True, verbose_name='Next Action')),
('next_action_date', models.DateTimeField(blank=True, null=True, verbose_name='Next Action Date')),
('is_converted', models.BooleanField(default=False)),
('converted_at', models.DateTimeField(blank=True, null=True)),
('salary', models.PositiveIntegerField(blank=True, null=True, verbose_name='Salary')),
('created', models.DateTimeField(auto_now_add=True, db_index=True, verbose_name='Created')),
('crn', models.CharField(blank=True, max_length=20, null=True, verbose_name='CRN')),
('vrn', models.CharField(blank=True, max_length=20, null=True, verbose_name='VRN')),
('salary', models.DecimalField(blank=True, decimal_places=2, max_digits=10, null=True, verbose_name='Salary')),
('priority', models.CharField(choices=[('high', 'High'), ('medium', 'Medium'), ('low', 'Low')], default='medium', max_length=20, verbose_name='Priority')),
('stage', models.CharField(choices=[('qualification', 'Qualification'), ('test_drive', 'Test Drive'), ('quotation', 'Quotation'), ('negotiation', 'Negotiation'), ('financing', 'Financing'), ('closed_won', 'Closed Won'), ('closed_lost', 'Closed Lost'), ('on_hold', 'On Hold')], max_length=20, verbose_name='Stage')),
('probability', models.PositiveIntegerField(validators=[inventory.models.validate_probability])),
('amount', models.DecimalField(decimal_places=2, max_digits=10, verbose_name='Amount')),
('expected_revenue', models.DecimalField(blank=True, decimal_places=2, max_digits=10, null=True, verbose_name='Expected Revenue')),
('vehicle_of_interest_make', models.CharField(blank=True, max_length=50, null=True)),
('vehicle_of_interest_model', models.CharField(blank=True, max_length=100, null=True)),
('expected_close_date', models.DateField(blank=True, null=True)),
('created', models.DateTimeField(auto_now_add=True, verbose_name='Created')),
('updated', models.DateTimeField(auto_now=True, verbose_name='Updated')),
('slug', models.SlugField(blank=True, null=True, unique=True)),
('customer', models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.CASCADE, related_name='customer_leads', to='inventory.customer')),
('dealer', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, related_name='leads', to='inventory.dealer')),
('id_car_make', models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.DO_NOTHING, to='inventory.carmake', verbose_name='Make')),
('id_car_model', models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.DO_NOTHING, to='inventory.carmodel', verbose_name='Model')),
('organization', models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.CASCADE, related_name='organization_leads', to='inventory.organization')),
('slug', models.SlugField(blank=True, help_text='Unique slug for the opportunity.', null=True, unique=True, verbose_name='Slug')),
('loss_reason', models.CharField(blank=True, max_length=255, null=True)),
('car', models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.SET_NULL, to='inventory.car', verbose_name='Car')),
('customer', models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.CASCADE, related_name='opportunities', to='inventory.customer')),
('dealer', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, related_name='opportunities', to='inventory.dealer')),
('estimate', models.OneToOneField(blank=True, null=True, on_delete=django.db.models.deletion.SET_NULL, related_name='opportunity', to='django_ledger.estimatemodel')),
('lead', models.OneToOneField(blank=True, null=True, on_delete=django.db.models.deletion.CASCADE, related_name='opportunity', to='inventory.lead')),
('organization', models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.SET_NULL, to='inventory.organization', verbose_name='Organization')),
],
options={
'verbose_name': 'Lead',
'verbose_name_plural': 'Leads',
'verbose_name': 'Opportunity',
'verbose_name_plural': 'Opportunities',
},
),
migrations.AddField(
model_name='lead',
name='organization',
field=models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.CASCADE, related_name='organization_leads', to='inventory.organization'),
),
migrations.CreateModel(
name='Refund',
fields=[
@ -609,12 +640,33 @@ class Migration(migrations.Migration):
('payment_method', models.CharField(choices=[('cash', 'Cash'), ('finance', 'Finance'), ('lease', 'Lease'), ('credit_card', 'Credit Card'), ('bank_transfer', 'Bank Transfer'), ('sadad', 'SADAD')], max_length=20)),
('comments', models.TextField(blank=True, null=True)),
('formatted_order_id', models.CharField(editable=False, max_length=10, unique=True)),
('created', models.DateTimeField(auto_now_add=True)),
('agreed_price', models.DecimalField(decimal_places=2, help_text='The final agreed-upon selling price of the vehicle.', max_digits=12)),
('down_payment_amount', models.DecimalField(decimal_places=2, default=0.0, help_text='The initial payment made by the customer.', max_digits=12)),
('trade_in_value', models.DecimalField(decimal_places=2, default=0.0, help_text='The value of any vehicle traded in by the customer.', max_digits=12)),
('loan_amount', models.DecimalField(decimal_places=2, default=0.0, help_text='The amount financed by a bank or third-party lender.', max_digits=12)),
('total_paid_amount', models.DecimalField(decimal_places=2, default=0.0, help_text='Sum of down payment, trade-in value, and loan amount received so far.', max_digits=12)),
('remaining_balance', models.DecimalField(decimal_places=2, default=0.0, help_text='The remaining amount due from the customer or financing.', max_digits=12)),
('status', models.CharField(choices=[('PENDING_APPROVAL', 'Pending Approval'), ('APPROVED', 'Approved'), ('IN_FINANCING', 'In Financing'), ('PARTIALLY_PAID', 'Partially Paid'), ('FULLY_PAID', 'Fully Paid'), ('PENDING_DELIVERY', 'Pending Delivery'), ('DELIVERED', 'Delivered'), ('CANCELLED', 'Cancelled')], default='PENDING_APPROVAL', help_text='Current status of the sales order.', max_length=20)),
('order_date', models.DateTimeField(default=django.utils.timezone.now, help_text='The date and time the sales order was created.')),
('expected_delivery_date', models.DateField(blank=True, help_text='The planned date for vehicle delivery.', null=True)),
('actual_delivery_date', models.DateTimeField(blank=True, help_text='The actual date and time the vehicle was delivered.', null=True)),
('cancelled_date', models.DateTimeField(blank=True, help_text='The date and time the order was cancelled, if applicable.', null=True)),
('cancellation_reason', models.TextField(blank=True, help_text='Reason for cancellation, if applicable.', null=True)),
('created_at', models.DateTimeField(auto_now_add=True)),
('updated_at', models.DateTimeField(auto_now=True)),
('car', models.ForeignKey(blank=True, help_text='The specific vehicle (VIN) being sold.', null=True, on_delete=django.db.models.deletion.PROTECT, related_name='sales_orders', to='inventory.car')),
('created_by', models.ForeignKey(help_text='The user who created this sales order.', null=True, on_delete=django.db.models.deletion.SET_NULL, related_name='created_sales_orders', to=settings.AUTH_USER_MODEL)),
('customer', models.ForeignKey(help_text='The customer making the purchase.', on_delete=django.db.models.deletion.PROTECT, related_name='sales_orders', to='inventory.customer')),
('estimate', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, related_name='sale_orders', to='django_ledger.estimatemodel', verbose_name='Estimate')),
('invoice', models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.CASCADE, related_name='sale_orders', to='django_ledger.invoicemodel', verbose_name='Invoice')),
('last_modified_by', models.ForeignKey(help_text='The user who last modified this sales order.', null=True, on_delete=django.db.models.deletion.SET_NULL, related_name='modified_sales_orders', to=settings.AUTH_USER_MODEL)),
('opportunity', models.OneToOneField(help_text='The associated sales opportunity for this order.', on_delete=django.db.models.deletion.CASCADE, related_name='sales_order', to='inventory.opportunity')),
('trade_in_vehicle', models.ForeignKey(blank=True, help_text='The vehicle traded in by the customer, if any.', null=True, on_delete=django.db.models.deletion.SET_NULL, related_name='traded_in_on_orders', to='inventory.car')),
],
options={
'ordering': ['-created'],
'verbose_name': 'Sales Order',
'verbose_name_plural': 'Sales Orders',
'ordering': ['-order_date'],
},
),
migrations.CreateModel(
@ -662,35 +714,17 @@ class Migration(migrations.Migration):
('objects', inventory.models.StaffUserManager()),
],
),
migrations.CreateModel(
name='Opportunity',
fields=[
('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
('stage', models.CharField(choices=[('discovery', 'Discovery'), ('proposal', 'Proposal'), ('negotiation', 'Negotiation'), ('closed_won', 'Closed Won'), ('closed_lost', 'Closed Lost')], max_length=20, verbose_name='Stage')),
('probability', models.PositiveIntegerField(validators=[inventory.models.validate_probability])),
('expected_revenue', models.DecimalField(decimal_places=2, max_digits=10, verbose_name='Expected Revenue')),
('closing_date', models.DateField(blank=True, null=True, verbose_name='Closing Date')),
('created', models.DateTimeField(auto_now_add=True, verbose_name='Created')),
('updated', models.DateTimeField(auto_now=True, verbose_name='Updated')),
('slug', models.SlugField(blank=True, help_text='Unique slug for the opportunity.', null=True, unique=True, verbose_name='Slug')),
('car', models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.SET_NULL, to='inventory.car', verbose_name='Car')),
('customer', models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.CASCADE, related_name='opportunities', to='inventory.customer')),
('dealer', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, related_name='opportunities', to='inventory.dealer')),
('estimate', models.OneToOneField(blank=True, null=True, on_delete=django.db.models.deletion.SET_NULL, related_name='opportunity', to='django_ledger.estimatemodel')),
('lead', models.OneToOneField(blank=True, null=True, on_delete=django.db.models.deletion.CASCADE, related_name='opportunity', to='inventory.lead')),
('staff', models.ForeignKey(null=True, on_delete=django.db.models.deletion.SET_NULL, related_name='owner', to='inventory.staff', verbose_name='Owner')),
],
options={
'verbose_name': 'Opportunity',
'verbose_name_plural': 'Opportunities',
},
migrations.AddField(
model_name='opportunity',
name='staff',
field=models.ForeignKey(null=True, on_delete=django.db.models.deletion.SET_NULL, related_name='owner', to='inventory.staff', verbose_name='Owner'),
),
migrations.CreateModel(
name='LeadStatusHistory',
fields=[
('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
('old_status', models.CharField(choices=[('new', 'New'), ('follow_up', 'Follow-up'), ('negotiation', 'Negotiation'), ('won', 'Won'), ('lost', 'Lost'), ('closed', 'Closed')], max_length=50, verbose_name='Old Status')),
('new_status', models.CharField(choices=[('new', 'New'), ('follow_up', 'Follow-up'), ('negotiation', 'Negotiation'), ('won', 'Won'), ('lost', 'Lost'), ('closed', 'Closed')], max_length=50, verbose_name='New Status')),
('old_status', models.CharField(choices=[('new', 'New'), ('contacted', 'Contacted'), ('qualified', 'Qualified'), ('unqualified', 'Unqualified'), ('converted', 'Converted')], max_length=50, verbose_name='Old Status')),
('new_status', models.CharField(choices=[('new', 'New'), ('contacted', 'Contacted'), ('qualified', 'Qualified'), ('unqualified', 'Unqualified'), ('converted', 'Converted')], max_length=50, verbose_name='New Status')),
('changed_at', models.DateTimeField(auto_now_add=True, verbose_name='Changed At')),
('lead', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, related_name='status_history', to='inventory.lead')),
('changed_by', models.ForeignKey(on_delete=django.db.models.deletion.DO_NOTHING, related_name='status_changes', to='inventory.staff')),

View File

@ -1,18 +0,0 @@
# Generated by Django 5.1.7 on 2025-05-25 14:19
from django.db import migrations, models
class Migration(migrations.Migration):
dependencies = [
('inventory', '0001_initial'),
]
operations = [
migrations.AddField(
model_name='carfinance',
name='is_sold',
field=models.BooleanField(default=False),
),
]

View File

@ -1,125 +0,0 @@
# Generated by Django 5.1.7 on 2025-05-27 14:41
import django.db.models.deletion
import django.utils.timezone
from django.conf import settings
from django.db import migrations, models
class Migration(migrations.Migration):
dependencies = [
('inventory', '0002_carfinance_is_sold'),
migrations.swappable_dependency(settings.AUTH_USER_MODEL),
]
operations = [
migrations.AlterModelOptions(
name='saleorder',
options={'ordering': ['-order_date'], 'verbose_name': 'Sales Order', 'verbose_name_plural': 'Sales Orders'},
),
migrations.RenameField(
model_name='saleorder',
old_name='created',
new_name='created_at',
),
migrations.AddField(
model_name='saleorder',
name='actual_delivery_date',
field=models.DateTimeField(blank=True, help_text='The actual date and time the vehicle was delivered.', null=True),
),
migrations.AddField(
model_name='saleorder',
name='agreed_price',
field=models.DecimalField(decimal_places=2, default=0, help_text='The final agreed-upon selling price of the vehicle.', max_digits=12),
preserve_default=False,
),
migrations.AddField(
model_name='saleorder',
name='cancellation_reason',
field=models.TextField(blank=True, help_text='Reason for cancellation, if applicable.', null=True),
),
migrations.AddField(
model_name='saleorder',
name='cancelled_date',
field=models.DateTimeField(blank=True, help_text='The date and time the order was cancelled, if applicable.', null=True),
),
migrations.AddField(
model_name='saleorder',
name='car',
field=models.ForeignKey(default=1, help_text='The specific vehicle (VIN) being sold.', on_delete=django.db.models.deletion.PROTECT, related_name='sales_orders', to='inventory.car'),
preserve_default=False,
),
migrations.AddField(
model_name='saleorder',
name='created_by',
field=models.ForeignKey(help_text='The user who created this sales order.', null=True, on_delete=django.db.models.deletion.SET_NULL, related_name='created_sales_orders', to=settings.AUTH_USER_MODEL),
),
migrations.AddField(
model_name='saleorder',
name='customer',
field=models.ForeignKey(default=1, help_text='The customer making the purchase.', on_delete=django.db.models.deletion.PROTECT, related_name='sales_orders', to='inventory.customer'),
preserve_default=False,
),
migrations.AddField(
model_name='saleorder',
name='down_payment_amount',
field=models.DecimalField(decimal_places=2, default=0.0, help_text='The initial payment made by the customer.', max_digits=12),
),
migrations.AddField(
model_name='saleorder',
name='expected_delivery_date',
field=models.DateField(blank=True, help_text='The planned date for vehicle delivery.', null=True),
),
migrations.AddField(
model_name='saleorder',
name='last_modified_by',
field=models.ForeignKey(help_text='The user who last modified this sales order.', null=True, on_delete=django.db.models.deletion.SET_NULL, related_name='modified_sales_orders', to=settings.AUTH_USER_MODEL),
),
migrations.AddField(
model_name='saleorder',
name='loan_amount',
field=models.DecimalField(decimal_places=2, default=0.0, help_text='The amount financed by a bank or third-party lender.', max_digits=12),
),
migrations.AddField(
model_name='saleorder',
name='opportunity',
field=models.OneToOneField(default=1, help_text='The associated sales opportunity for this order.', on_delete=django.db.models.deletion.CASCADE, related_name='sales_order', to='inventory.opportunity'),
preserve_default=False,
),
migrations.AddField(
model_name='saleorder',
name='order_date',
field=models.DateTimeField(default=django.utils.timezone.now, help_text='The date and time the sales order was created.'),
),
migrations.AddField(
model_name='saleorder',
name='remaining_balance',
field=models.DecimalField(decimal_places=2, default=0.0, help_text='The remaining amount due from the customer or financing.', max_digits=12),
),
migrations.AddField(
model_name='saleorder',
name='status',
field=models.CharField(choices=[('PENDING_APPROVAL', 'Pending Approval'), ('APPROVED', 'Approved'), ('IN_FINANCING', 'In Financing'), ('PARTIALLY_PAID', 'Partially Paid'), ('FULLY_PAID', 'Fully Paid'), ('PENDING_DELIVERY', 'Pending Delivery'), ('DELIVERED', 'Delivered'), ('CANCELLED', 'Cancelled')], default='PENDING_APPROVAL', help_text='Current status of the sales order.', max_length=20),
),
migrations.AddField(
model_name='saleorder',
name='total_paid_amount',
field=models.DecimalField(decimal_places=2, default=0.0, help_text='Sum of down payment, trade-in value, and loan amount received so far.', max_digits=12),
),
migrations.AddField(
model_name='saleorder',
name='trade_in_value',
field=models.DecimalField(decimal_places=2, default=0.0, help_text='The value of any vehicle traded in by the customer.', max_digits=12),
),
migrations.AddField(
model_name='saleorder',
name='trade_in_vehicle',
field=models.ForeignKey(blank=True, help_text='The vehicle traded in by the customer, if any.', null=True, on_delete=django.db.models.deletion.SET_NULL, related_name='traded_in_on_orders', to='inventory.car'),
),
migrations.AddField(
model_name='saleorder',
name='updated_at',
field=models.DateTimeField(auto_now=True),
),
]

View File

@ -1,19 +0,0 @@
# Generated by Django 5.1.7 on 2025-05-27 14:43
import django.db.models.deletion
from django.db import migrations, models
class Migration(migrations.Migration):
dependencies = [
('inventory', '0003_alter_saleorder_options_and_more'),
]
operations = [
migrations.AlterField(
model_name='saleorder',
name='car',
field=models.ForeignKey(blank=True, help_text='The specific vehicle (VIN) being sold.', null=True, on_delete=django.db.models.deletion.PROTECT, related_name='sales_orders', to='inventory.car'),
),
]

View File

@ -1,18 +0,0 @@
# Generated by Django 5.1.7 on 2025-05-28 13:14
from django.db import migrations, models
class Migration(migrations.Migration):
dependencies = [
('inventory', '0004_alter_saleorder_car'),
]
operations = [
migrations.AlterField(
model_name='opportunity',
name='stage',
field=models.CharField(choices=[('qualification', 'Qualification'), ('test_drive', 'Test Drive'), ('quotation', 'Quotation'), ('negotiation', 'Negotiation'), ('financing', 'Financing'), ('closed_won', 'Closed Won'), ('closed_lost', 'Closed Lost'), ('on_hold', 'On Hold')], max_length=20, verbose_name='Stage'),
),
]

View File

@ -1,45 +0,0 @@
# Generated by Django 5.1.7 on 2025-05-28 13:16
import django.db.models.deletion
from django.conf import settings
from django.db import migrations, models
class Migration(migrations.Migration):
dependencies = [
('inventory', '0005_alter_opportunity_stage'),
migrations.swappable_dependency(settings.AUTH_USER_MODEL),
]
operations = [
migrations.RemoveField(
model_name='opportunity',
name='closing_date',
),
migrations.AddField(
model_name='opportunity',
name='assigned_to',
field=models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.SET_NULL, related_name='assigned_opportunities', to=settings.AUTH_USER_MODEL),
),
migrations.AddField(
model_name='opportunity',
name='expected_close_date',
field=models.DateField(blank=True, null=True),
),
migrations.AddField(
model_name='opportunity',
name='loss_reason',
field=models.CharField(blank=True, max_length=255, null=True),
),
migrations.AddField(
model_name='opportunity',
name='vehicle_of_interest_make',
field=models.CharField(blank=True, max_length=50, null=True),
),
migrations.AddField(
model_name='opportunity',
name='vehicle_of_interest_model',
field=models.CharField(blank=True, max_length=100, null=True),
),
]

View File

@ -1,28 +0,0 @@
# Generated by Django 5.1.7 on 2025-05-28 13:36
from django.db import migrations, models
class Migration(migrations.Migration):
dependencies = [
('inventory', '0006_remove_opportunity_closing_date_and_more'),
]
operations = [
migrations.AlterField(
model_name='lead',
name='status',
field=models.CharField(choices=[('new', 'New'), ('contacted', 'Contacted'), ('qualified', 'Qualified'), ('unqualified', 'Unqualified'), ('converted', 'Converted')], db_index=True, default='new', max_length=50, verbose_name='Status'),
),
migrations.AlterField(
model_name='leadstatushistory',
name='new_status',
field=models.CharField(choices=[('new', 'New'), ('contacted', 'Contacted'), ('qualified', 'Qualified'), ('unqualified', 'Unqualified'), ('converted', 'Converted')], max_length=50, verbose_name='New Status'),
),
migrations.AlterField(
model_name='leadstatushistory',
name='old_status',
field=models.CharField(choices=[('new', 'New'), ('contacted', 'Contacted'), ('qualified', 'Qualified'), ('unqualified', 'Unqualified'), ('converted', 'Converted')], max_length=50, verbose_name='Old Status'),
),
]

View File

@ -1,37 +0,0 @@
# Generated by Django 5.1.7 on 2025-05-28 13:44
from django.db import migrations
class Migration(migrations.Migration):
dependencies = [
('inventory', '0007_alter_lead_status_alter_leadstatushistory_new_status_and_more'),
]
operations = [
migrations.RemoveField(
model_name='lead',
name='address',
),
migrations.RemoveField(
model_name='lead',
name='crn',
),
migrations.RemoveField(
model_name='lead',
name='priority',
),
migrations.RemoveField(
model_name='lead',
name='salary',
),
migrations.RemoveField(
model_name='lead',
name='vrn',
),
migrations.RemoveField(
model_name='lead',
name='year',
),
]

View File

@ -1,38 +0,0 @@
# Generated by Django 5.1.7 on 2025-05-28 13:46
from django.db import migrations, models
class Migration(migrations.Migration):
dependencies = [
('inventory', '0008_remove_lead_address_remove_lead_crn_and_more'),
]
operations = [
migrations.AddField(
model_name='lead',
name='address',
field=models.CharField(blank=True, max_length=200, null=True, verbose_name='Address'),
),
migrations.AddField(
model_name='opportunity',
name='crn',
field=models.CharField(blank=True, max_length=20, null=True, verbose_name='CRN'),
),
migrations.AddField(
model_name='opportunity',
name='priority',
field=models.CharField(choices=[('high', 'High'), ('medium', 'Medium'), ('low', 'Low')], default='medium', max_length=20, verbose_name='Priority'),
),
migrations.AddField(
model_name='opportunity',
name='salary',
field=models.DecimalField(blank=True, decimal_places=2, max_digits=10, null=True, verbose_name='Salary'),
),
migrations.AddField(
model_name='opportunity',
name='vrn',
field=models.CharField(blank=True, max_length=20, null=True, verbose_name='VRN'),
),
]

View File

@ -1,19 +0,0 @@
# Generated by Django 5.1.7 on 2025-05-29 15:22
import django.db.models.deletion
from django.db import migrations, models
class Migration(migrations.Migration):
dependencies = [
('inventory', '0009_lead_address_opportunity_crn_opportunity_priority_and_more'),
]
operations = [
migrations.AlterField(
model_name='opportunity',
name='assigned_to',
field=models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.SET_NULL, related_name='assigned_opportunities', to='inventory.staff'),
),
]

View File

@ -1,17 +0,0 @@
# Generated by Django 5.1.7 on 2025-05-29 15:23
from django.db import migrations
class Migration(migrations.Migration):
dependencies = [
('inventory', '0010_alter_opportunity_assigned_to'),
]
operations = [
migrations.RemoveField(
model_name='opportunity',
name='assigned_to',
),
]

View File

@ -1,19 +0,0 @@
# Generated by Django 5.1.7 on 2025-05-29 16:12
import django.db.models.deletion
from django.db import migrations, models
class Migration(migrations.Migration):
dependencies = [
('inventory', '0011_remove_opportunity_assigned_to'),
]
operations = [
migrations.AddField(
model_name='opportunity',
name='organization',
field=models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.SET_NULL, to='inventory.organization', verbose_name='Organization'),
),
]

View File

@ -1,23 +0,0 @@
# Generated by Django 5.1.7 on 2025-05-29 23:00
from django.db import migrations, models
class Migration(migrations.Migration):
dependencies = [
('inventory', '0012_opportunity_organization'),
]
operations = [
migrations.AddField(
model_name='opportunity',
name='amount',
field=models.DecimalField(blank=True, decimal_places=2, max_digits=10, null=True, verbose_name='Amount'),
),
migrations.AlterField(
model_name='opportunity',
name='expected_revenue',
field=models.DecimalField(blank=True, decimal_places=2, max_digits=10, null=True, verbose_name='Expected Revenue'),
),
]

View File

@ -1,19 +0,0 @@
# Generated by Django 5.1.7 on 2025-05-29 23:02
from django.db import migrations, models
class Migration(migrations.Migration):
dependencies = [
('inventory', '0013_opportunity_amount_and_more'),
]
operations = [
migrations.AlterField(
model_name='opportunity',
name='amount',
field=models.DecimalField(decimal_places=2, default=0, max_digits=10, verbose_name='Amount'),
preserve_default=False,
),
]

View File

@ -578,7 +578,7 @@ path(
name="estimate_detail",
),
path("sales/estimates/create/", views.create_estimate, name="estimate_create"),
path("sales/estimates/create/<slug:slug>/", views.create_estimate, name="estimate_create_from_opportunity"),
path("sales/estimates/create/<int:pk>/", views.create_estimate, name="estimate_create_from_opportunity"),
path(
"sales/estimates/<uuid:pk>/estimate_mark_as/",
views.estimate_mark_as,

File diff suppressed because one or more lines are too long

Binary file not shown.

After

Width:  |  Height:  |  Size: 34 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 65 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.0 MiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.9 MiB