This commit is contained in:
Marwan Alwali 2025-01-13 21:38:18 +03:00
parent 00f87799d0
commit 1c668d3f0a
43 changed files with 748 additions and 1770 deletions

View File

@ -1,4 +1,4 @@
# Generated by Django 5.1.4 on 2025-01-07 22:27 # Generated by Django 5.1.4 on 2025-01-12 17:20
from django.db import migrations, models from django.db import migrations, models

View File

@ -1,4 +1,4 @@
# Generated by Django 5.1.4 on 2025-01-07 22:27 # Generated by Django 5.1.4 on 2025-01-12 17:20
from django.db import migrations, models from django.db import migrations, models

View File

@ -1,4 +1,4 @@
# Generated by Django 5.1.4 on 2025-01-07 22:27 # Generated by Django 5.1.4 on 2025-01-12 17:20
import django.db.models.deletion import django.db.models.deletion
from django.db import migrations, models from django.db import migrations, models

View File

@ -567,36 +567,7 @@ class EmailForm(forms.Form):
from_email = forms.EmailField() from_email = forms.EmailField()
to_email = forms.EmailField(label="To") to_email = forms.EmailField(label="To")
class LeadCreateForm(forms.ModelForm): class LeadForm(forms.ModelForm):
class Meta:
model = Lead
fields = ['title',
'first_name',
'last_name',
'email',
'phone_number',
'city',
'salary',
'obligations',
'id_car_make',
'id_car_model',
'year',
'source',
'channel',
'assigned',
'priority',
]
def __init__(self, *args, **kwargs):
super().__init__(*args, **kwargs)
if "id_car_make" in self.fields:
queryset = self.fields["id_car_make"].queryset.filter(is_sa_import=True)
self.fields["id_car_make"].choices = [
(obj.id_car_make, obj.get_local_name()) for obj in queryset
]
class LeadUpdateForm(forms.ModelForm):
class Meta: class Meta:
model = Lead model = Lead
fields = ['title', fields = ['title',

View File

@ -1,4 +1,4 @@
# Generated by Django 5.1.4 on 2025-01-07 22:27 # Generated by Django 5.1.4 on 2025-01-12 17:20
import django.db.models.deletion import django.db.models.deletion
import inventory.mixins import inventory.mixins
@ -14,6 +14,7 @@ class Migration(migrations.Migration):
initial = True initial = True
dependencies = [ dependencies = [
('contenttypes', '0002_remove_content_type_name'),
('django_ledger', '0017_alter_accountmodel_unique_together_and_more'), ('django_ledger', '0017_alter_accountmodel_unique_together_and_more'),
migrations.swappable_dependency(settings.AUTH_USER_MODEL), migrations.swappable_dependency(settings.AUTH_USER_MODEL),
] ]
@ -57,7 +58,7 @@ class Migration(migrations.Migration):
('arabic_name', models.CharField(blank=True, max_length=255, null=True)), ('arabic_name', models.CharField(blank=True, max_length=255, null=True)),
('logo', models.ImageField(blank=True, null=True, upload_to='car_make', verbose_name='logo')), ('logo', models.ImageField(blank=True, null=True, upload_to='car_make', verbose_name='logo')),
('is_sa_import', models.BooleanField(default=False)), ('is_sa_import', models.BooleanField(default=False)),
('car_type', models.SmallIntegerField(choices=[(1, 'Car'), (2, 'light commercial'), (3, 'truck trailer'), (4, 'trailer'), (5, 'truck'), (6, 'bus')])), ('car_type', models.SmallIntegerField(choices=[(1, 'Car'), (2, 'Light Commercial'), (3, 'Heavy-Duty Tractors'), (4, 'Trailers'), (5, 'Medium Trucks'), (6, 'Buses'), (20, 'Motorcycles'), (21, 'Buggy'), (22, 'Moto ATV'), (23, 'Scooters'), (24, 'Karting'), (25, 'ATV'), (26, 'Snowmobiles')])),
], ],
options={ options={
'verbose_name': 'Make', 'verbose_name': 'Make',
@ -134,6 +135,41 @@ class Migration(migrations.Migration):
('created_at', models.DateTimeField(auto_now_add=True)), ('created_at', models.DateTimeField(auto_now_add=True)),
], ],
), ),
migrations.CreateModel(
name='Activity',
fields=[
('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
('object_id', models.PositiveIntegerField()),
('activity_type', models.CharField(choices=[('call', 'Call'), ('sms', 'SMS'), ('email', 'Email'), ('whatsapp', 'WhatsApp'), ('visit', 'Visit'), ('add_car', 'Add Car'), ('reserve_car', 'Reserve Car'), ('remove_car', 'Remove Car'), ('create_quotation', 'Create Quotation'), ('cancel_quotation', 'Cancel Quotation'), ('create_order', 'Create Order'), ('cancel_order', 'Cancel Order'), ('create_invoice', 'Create Invoice'), ('cancel_invoice', 'Cancel Invoice')], max_length=50, verbose_name='Activity Type')),
('notes', models.TextField(blank=True, null=True, verbose_name='Notes')),
('created', models.DateTimeField(auto_now_add=True, verbose_name='Created')),
('updated', models.DateTimeField(auto_now=True, verbose_name='Updated')),
('content_type', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to='contenttypes.contenttype')),
('created_by', models.ForeignKey(on_delete=django.db.models.deletion.DO_NOTHING, related_name='activities_created', to=settings.AUTH_USER_MODEL)),
],
options={
'verbose_name': 'Activity',
'verbose_name_plural': 'Activities',
},
),
migrations.CreateModel(
name='AdditionalServices',
fields=[
('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
('name', models.CharField(max_length=255, verbose_name='Name')),
('arabic_name', models.CharField(max_length=255, verbose_name='Arabic Name')),
('description', models.TextField(verbose_name='Description')),
('price', models.DecimalField(decimal_places=2, max_digits=14, verbose_name='Price')),
('taxable', models.BooleanField(default=False, verbose_name='taxable')),
('uom', models.CharField(choices=[('EA', 'Each'), ('PR', 'Pair'), ('SET', 'Set'), ('GAL', 'Gallon'), ('L', 'Liter'), ('M', 'Meter'), ('KG', 'Kilogram'), ('HR', 'Hour'), ('BX', 'Box'), ('RL', 'Roll'), ('PKG', 'Package'), ('DZ', 'Dozen'), ('SQ_M', 'Square Meter'), ('PC', 'Piece'), ('BDL', 'Bundle')], max_length=10, verbose_name='Unit of Measurement')),
('item', models.OneToOneField(blank=True, null=True, on_delete=django.db.models.deletion.CASCADE, to='django_ledger.itemmodel', verbose_name='Item')),
],
options={
'verbose_name': 'Additional Services',
'verbose_name_plural': 'Additional Services',
},
bases=(models.Model, inventory.mixins.LocalizedNameMixin),
),
migrations.CreateModel( migrations.CreateModel(
name='CarFinance', name='CarFinance',
fields=[ fields=[
@ -141,7 +177,7 @@ class Migration(migrations.Migration):
('cost_price', models.DecimalField(decimal_places=2, max_digits=14, verbose_name='Cost Price')), ('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')), ('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')), ('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')), ('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')), ('car', models.OneToOneField(on_delete=django.db.models.deletion.CASCADE, related_name='finances', to='inventory.car')),
], ],
options={ options={
@ -325,26 +361,6 @@ class Migration(migrations.Migration):
('objects', inventory.models.DealerUserManager()), ('objects', inventory.models.DealerUserManager()),
], ],
), ),
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')),
('dealer', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, related_name='customers', to='inventory.dealer')),
],
options={
'verbose_name': 'Customer',
'verbose_name_plural': 'Customers',
},
),
migrations.CreateModel( migrations.CreateModel(
name='CarLocation', name='CarLocation',
fields=[ fields=[
@ -366,23 +382,80 @@ class Migration(migrations.Migration):
name='dealer', name='dealer',
field=models.ForeignKey(on_delete=django.db.models.deletion.DO_NOTHING, related_name='cars', to='inventory.dealer', verbose_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( migrations.CreateModel(
name='AdditionalServices', name='Lead',
fields=[ fields=[
('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')), ('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
('name', models.CharField(max_length=255, verbose_name='Name')), ('title', models.CharField(choices=[('mr', 'Mr'), ('mrs', 'Mrs'), ('ms', 'Ms'), ('miss', 'Miss'), ('dr', 'Dr'), ('prof', 'Prof'), ('prince', 'Prince'), ('princess', 'Princess'), ('company', 'Company'), ('na', 'N/A')], max_length=20, verbose_name='Title')),
('arabic_name', models.CharField(max_length=255, verbose_name='Arabic Name')), ('first_name', models.CharField(max_length=50, verbose_name='First Name')),
('description', models.TextField(verbose_name='Description')), ('last_name', models.CharField(max_length=50, verbose_name='Last Name')),
('price', models.DecimalField(decimal_places=2, max_digits=14, verbose_name='Price')), ('email', models.EmailField(db_index=True, max_length=254, unique=True, verbose_name='Email')),
('taxable', models.BooleanField(default=False, verbose_name='taxable')), ('phone_number', phonenumber_field.modelfields.PhoneNumberField(max_length=128, region='SA', verbose_name='Phone Number')),
('uom', models.CharField(choices=[('EA', 'Each'), ('PR', 'Pair'), ('SET', 'Set'), ('GAL', 'Gallon'), ('L', 'Liter'), ('M', 'Meter'), ('KG', 'Kilogram'), ('HR', 'Hour'), ('BX', 'Box'), ('RL', 'Roll'), ('PKG', 'Package'), ('DZ', 'Dozen'), ('SQ_M', 'Square Meter'), ('PC', 'Piece'), ('BDL', 'Bundle')], max_length=10, verbose_name='Unit of Measurement')), ('salary', models.PositiveIntegerField(blank=True, null=True, verbose_name='Salary')),
('dealer', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to='inventory.dealer', verbose_name='Dealer')), ('obligations', models.PositiveIntegerField(blank=True, null=True, verbose_name='Obligations')),
('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')),
('address', models.CharField(blank=True, max_length=200, null=True, verbose_name='Address')),
('city', models.CharField(max_length=50, verbose_name='City')),
('priority', models.CharField(choices=[('low', 'Low'), ('medium', 'Medium'), ('high', 'High')], default='medium', max_length=10, verbose_name='Priority')),
('status', models.CharField(choices=[('new', 'New'), ('pending', 'Pending'), ('in_progress', 'In Progress'), ('qualified', 'Qualified'), ('canceled', 'Canceled')], db_index=True, default='new', max_length=50, verbose_name='Status')),
('created', models.DateTimeField(auto_now_add=True, db_index=True, verbose_name='Created')),
('updated', models.DateTimeField(auto_now=True, verbose_name='Updated')),
('dealer', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, related_name='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={ options={
'verbose_name': 'Additional Services', 'verbose_name': 'Lead',
'verbose_name_plural': 'Additional Services', 'verbose_name_plural': 'Leads',
},
),
migrations.CreateModel(
name='Customer',
fields=[
('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
('title', models.CharField(choices=[('mr', 'Mr'), ('mrs', 'Mrs'), ('ms', 'Ms'), ('miss', 'Miss'), ('dr', 'Dr'), ('prof', 'Prof'), ('prince', 'Prince'), ('princess', 'Princess'), ('company', 'Company'), ('na', 'N/A')], default='na', max_length=10, verbose_name='Title')),
('first_name', models.CharField(max_length=50, verbose_name='First Name')),
('middle_name', models.CharField(blank=True, max_length=50, null=True, verbose_name='Middle Name')),
('last_name', models.CharField(max_length=50, verbose_name='Last Name')),
('gender', models.CharField(choices=[('m', 'Male'), ('f', 'Female')], max_length=1, verbose_name='Gender')),
('dob', models.DateField(verbose_name='Date of Birth')),
('email', models.EmailField(max_length=254, unique=True, verbose_name='Email')),
('national_id', models.CharField(max_length=10, unique=True, verbose_name='National ID')),
('phone_number', phonenumber_field.modelfields.PhoneNumberField(max_length=128, region='SA', unique=True, verbose_name='Phone Number')),
('city', models.CharField(blank=True, max_length=255, verbose_name='City')),
('address', models.CharField(blank=True, max_length=200, null=True, verbose_name='Address')),
('created', models.DateTimeField(auto_now_add=True, verbose_name='Created')),
('updated', models.DateTimeField(auto_now=True, verbose_name='Updated')),
('dealer', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, related_name='customers', to='inventory.dealer')),
('lead', models.OneToOneField(blank=True, null=True, on_delete=django.db.models.deletion.SET_NULL, related_name='converted', to='inventory.lead', verbose_name='Lead')),
],
options={
'verbose_name': 'Customer',
'verbose_name_plural': 'Customers',
},
),
migrations.CreateModel(
name='Notes',
fields=[
('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
('object_id', models.PositiveIntegerField()),
('note', models.TextField(verbose_name='Note')),
('created', models.DateTimeField(auto_now_add=True, verbose_name='Created')),
('updated', models.DateTimeField(auto_now=True, verbose_name='Updated')),
('content_type', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to='contenttypes.contenttype')),
('created_by', models.ForeignKey(on_delete=django.db.models.deletion.DO_NOTHING, related_name='notes_created', to=settings.AUTH_USER_MODEL)),
],
options={
'verbose_name': 'Note',
'verbose_name_plural': 'Notes',
}, },
bases=(models.Model, inventory.mixins.LocalizedNameMixin),
), ),
migrations.CreateModel( migrations.CreateModel(
name='Notification', name='Notification',
@ -390,46 +463,13 @@ class Migration(migrations.Migration):
('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')), ('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
('message', models.CharField(max_length=255, verbose_name='Message')), ('message', models.CharField(max_length=255, verbose_name='Message')),
('is_read', models.BooleanField(default=False, verbose_name='Is Read')), ('is_read', models.BooleanField(default=False, verbose_name='Is Read')),
('created_at', models.DateTimeField(auto_now_add=True, verbose_name='Created At')), ('created', models.DateTimeField(auto_now_add=True, verbose_name='Created')),
('user', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, related_name='notifications', to=settings.AUTH_USER_MODEL)), ('user', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, related_name='notifications', to=settings.AUTH_USER_MODEL)),
], ],
options={ options={
'verbose_name': 'Notification', 'verbose_name': 'Notification',
'verbose_name_plural': 'Notifications', 'verbose_name_plural': 'Notifications',
'ordering': ['-created_at'], 'ordering': ['-created'],
},
),
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=20, verbose_name='Deal Status')),
('priority', models.CharField(choices=[('low', 'Low'), ('medium', 'Medium'), ('high', 'High')], default='low', max_length=10, verbose_name='Priority')),
('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')),
('customer', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, related_name='opportunities', to='inventory.customer')),
],
options={
'verbose_name': 'Opportunity',
'verbose_name_plural': 'Opportunities',
},
),
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(on_delete=django.db.models.deletion.DO_NOTHING, 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.CreateModel( migrations.CreateModel(
@ -540,8 +580,8 @@ class Migration(migrations.Migration):
('arabic_name', models.CharField(max_length=255, verbose_name='Arabic 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')), ('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'), ('coordinator', 'Coordinator'), ('receptionist', 'Receptionist'), ('agent', 'Agent')], max_length=255, verbose_name='Staff Type')), ('staff_type', models.CharField(choices=[('manager', 'Manager'), ('inventory', 'Inventory'), ('accountant', 'Accountant'), ('sales', 'Sales'), ('coordinator', 'Coordinator'), ('receptionist', 'Receptionist'), ('agent', 'Agent')], max_length=255, verbose_name='Staff Type')),
('created_at', models.DateTimeField(auto_now_add=True, verbose_name='Created At')), ('created', models.DateTimeField(auto_now_add=True, verbose_name='Created')),
('updated_at', models.DateTimeField(auto_now=True, verbose_name='Updated At')), ('updated', models.DateTimeField(auto_now=True, verbose_name='Updated')),
('dealer', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, related_name='staff', to='inventory.dealer')), ('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)), ('user', models.OneToOneField(on_delete=django.db.models.deletion.CASCADE, related_name='staff', to=settings.AUTH_USER_MODEL)),
], ],
@ -551,29 +591,55 @@ class Migration(migrations.Migration):
'permissions': [], 'permissions': [],
}, },
bases=(models.Model, inventory.mixins.LocalizedNameMixin), bases=(models.Model, inventory.mixins.LocalizedNameMixin),
managers=[
('objects', inventory.models.StaffUserManager()),
],
), ),
migrations.CreateModel( migrations.CreateModel(
name='OpportunityLog', name='Opportunity',
fields=[ fields=[
('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')), ('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')), ('stage', models.CharField(choices=[('prospect', 'Prospect'), ('proposal', 'Proposal'), ('negotiation', 'Negotiation'), ('closed_won', 'Closed Won'), ('closed_lost', 'Closed Lost')], max_length=20, verbose_name='Stage')),
('old_status', models.CharField(blank=True, choices=[('new', 'New'), ('pending', 'Pending'), ('canceled', 'Canceled'), ('completed', 'Completed')], max_length=50, null=True, verbose_name='Old Status')), ('status', models.CharField(choices=[('new', 'New'), ('pending', 'Pending'), ('in_progress', 'In Progress'), ('qualified', 'Qualified'), ('canceled', 'Canceled')], default='new', max_length=20, verbose_name='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')), ('probability', models.PositiveIntegerField(validators=[inventory.models.validate_probability])),
('details', models.TextField(blank=True, null=True, verbose_name='Details')), ('closing_date', models.DateField(verbose_name='Closing Date')),
('created_at', models.DateTimeField(auto_now_add=True, verbose_name='Created At')), ('created', models.DateTimeField(auto_now_add=True, verbose_name='Created')),
('opportunity', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, related_name='logs', to='inventory.opportunity')), ('updated', models.DateTimeField(auto_now=True, verbose_name='Updated')),
('staff', models.ForeignKey(null=True, on_delete=django.db.models.deletion.SET_NULL, to='inventory.staff', verbose_name='Staff')), ('closed', models.BooleanField(default=False, verbose_name='Closed')),
('car', models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.SET_NULL, to='inventory.car', verbose_name='Car')),
('customer', models.ForeignKey(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')),
('staff', models.ForeignKey(null=True, on_delete=django.db.models.deletion.SET_NULL, related_name='owner', to='inventory.staff', verbose_name='Owner')),
], ],
options={ options={
'verbose_name': 'Log', 'verbose_name': 'Opportunity',
'verbose_name_plural': 'Logs', 'verbose_name_plural': 'Opportunities',
'ordering': ['-created_at'], },
),
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'), ('pending', 'Pending'), ('in_progress', 'In Progress'), ('qualified', 'Qualified'), ('canceled', 'Canceled')], max_length=50, verbose_name='Old Status')),
('new_status', models.CharField(choices=[('new', 'New'), ('pending', 'Pending'), ('in_progress', 'In Progress'), ('qualified', 'Qualified'), ('canceled', 'Canceled')], max_length=50, verbose_name='New Status')),
('changed_at', models.DateTimeField(auto_now_add=True, verbose_name='Changed At')),
('lead', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, related_name='status_history', to='inventory.lead')),
('changed_by', models.ForeignKey(on_delete=django.db.models.deletion.DO_NOTHING, related_name='status_changes', to='inventory.staff')),
],
options={
'verbose_name': 'Lead Status History',
'verbose_name_plural': 'Lead Status Histories',
}, },
), ),
migrations.AddField( migrations.AddField(
model_name='opportunity', model_name='lead',
name='created_by', name='assigned',
field=models.ForeignKey(null=True, on_delete=django.db.models.deletion.SET_NULL, related_name='deals_created', to='inventory.staff'), field=models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.SET_NULL, related_name='assigned', to='inventory.staff', verbose_name='Assigned'),
),
migrations.AddField(
model_name='customer',
name='staff',
field=models.ForeignKey(null=True, on_delete=django.db.models.deletion.SET_NULL, related_name='customer_staff', to='inventory.staff', verbose_name='Staff'),
), ),
migrations.CreateModel( migrations.CreateModel(
name='Subscription', name='Subscription',

View File

@ -1,26 +0,0 @@
# Generated by Django 4.2.17 on 2025-01-08 08:42
from django.db import migrations, models
import django.db.models.deletion
class Migration(migrations.Migration):
dependencies = [
('django_ledger', '0017_alter_accountmodel_unique_together_and_more'),
('inventory', '0003_alter_caroptionvalue_is_base'),
]
operations = [
migrations.AddField(
model_name='additionalservices',
name='item',
field=models.OneToOneField(default='', on_delete=django.db.models.deletion.CASCADE, to='django_ledger.itemmodel', verbose_name='Item'),
preserve_default=False,
),
migrations.AlterField(
model_name='carfinance',
name='additional_services',
field=models.ManyToManyField(blank=True, related_name='additional_finances', to='inventory.additionalservices'),
),
]

View File

@ -1,20 +0,0 @@
# Generated by Django 4.2.17 on 2025-01-08 08:43
from django.db import migrations, models
import django.db.models.deletion
class Migration(migrations.Migration):
dependencies = [
('django_ledger', '0017_alter_accountmodel_unique_together_and_more'),
('inventory', '0004_additionalservices_item_and_more'),
]
operations = [
migrations.AlterField(
model_name='additionalservices',
name='item',
field=models.OneToOneField(blank=True, null=True, on_delete=django.db.models.deletion.CASCADE, to='django_ledger.itemmodel', verbose_name='Item'),
),
]

View File

@ -1,59 +0,0 @@
# Generated by Django 5.1.4 on 2025-01-08 19:03
import django.utils.timezone
import django_countries.fields
from django.db import migrations, models
class Migration(migrations.Migration):
dependencies = [
('inventory', '0005_alter_additionalservices_item'),
]
operations = [
migrations.RemoveField(
model_name='customer',
name='is_lead',
),
migrations.AddField(
model_name='customer',
name='dob',
field=models.DateField(default=django.utils.timezone.now, verbose_name='Date of Birth'),
preserve_default=False,
),
migrations.AddField(
model_name='customer',
name='gender',
field=models.CharField(choices=[('m', 'Male'), ('f', 'Female')], default='m', max_length=1, verbose_name='Gender'),
preserve_default=False,
),
migrations.AddField(
model_name='customer',
name='nationality',
field=django_countries.fields.CountryField(blank=True, max_length=2, verbose_name='Nationality'),
),
migrations.AddField(
model_name='customer',
name='obligations',
field=models.PositiveIntegerField(default=1000, verbose_name='Obligations'),
preserve_default=False,
),
migrations.AddField(
model_name='customer',
name='salary',
field=models.PositiveIntegerField(default=10000, verbose_name='Salary'),
preserve_default=False,
),
migrations.AddField(
model_name='customer',
name='title',
field=models.CharField(choices=[('mr', 'Mr'), ('mrs', 'Mrs'), ('ms', 'Ms'), ('miss', 'Miss'), ('dr', 'Dr'), ('prof', 'Prof'), ('prince', 'Prince'), ('princess', 'Princess'), ('company', 'Company')], default='mr', max_length=10, verbose_name='Title'),
preserve_default=False,
),
migrations.AddField(
model_name='customer',
name='updated',
field=models.DateTimeField(auto_now=True, verbose_name='Updated'),
),
]

View File

@ -1,43 +0,0 @@
# Generated by Django 5.1.4 on 2025-01-09 05:46
import django_countries.fields
from django.db import migrations, models
class Migration(migrations.Migration):
dependencies = [
('inventory', '0006_remove_customer_is_lead_customer_dob_customer_gender_and_more'),
]
operations = [
migrations.RemoveField(
model_name='customer',
name='nationality',
),
migrations.AddField(
model_name='customer',
name='country',
field=django_countries.fields.CountryField(blank=True, max_length=2, verbose_name='Country'),
),
migrations.AlterField(
model_name='customer',
name='title',
field=models.CharField(choices=[('mr', 'Mr'), ('mrs', 'Mrs'), ('ms', 'Ms'), ('miss', 'Miss'), ('dr', 'Dr'), ('prof', 'Prof'), ('prince', 'Prince'), ('princess', 'Princess'), ('company', 'Company'), ('na', 'N/A')], default='na', max_length=10, verbose_name='Title'),
),
migrations.AlterField(
model_name='opportunity',
name='deal_status',
field=models.CharField(choices=[('new', 'New'), ('pending', 'Pending'), ('in_progress', 'In Progress'), ('canceled', 'Canceled'), ('lost', 'Lost'), ('won', 'Won')], default='new', max_length=20, verbose_name='Deal Status'),
),
migrations.AlterField(
model_name='opportunitylog',
name='new_status',
field=models.CharField(blank=True, choices=[('new', 'New'), ('pending', 'Pending'), ('in_progress', 'In Progress'), ('canceled', 'Canceled'), ('lost', 'Lost'), ('won', 'Won')], max_length=50, null=True, verbose_name='New Status'),
),
migrations.AlterField(
model_name='opportunitylog',
name='old_status',
field=models.CharField(blank=True, choices=[('new', 'New'), ('pending', 'Pending'), ('in_progress', 'In Progress'), ('canceled', 'Canceled'), ('lost', 'Lost'), ('won', 'Won')], max_length=50, null=True, verbose_name='Old Status'),
),
]

View File

@ -1,224 +0,0 @@
# Generated by Django 5.1.4 on 2025-01-09 09:19
import django.db.models.deletion
import django.utils.timezone
from django.db import migrations, models
class Migration(migrations.Migration):
dependencies = [
('contenttypes', '0002_remove_content_type_name'),
('inventory', '0007_remove_customer_nationality_customer_country_and_more'),
]
operations = [
migrations.AlterModelOptions(
name='notes',
options={'verbose_name': 'Note', 'verbose_name_plural': 'Notes'},
),
migrations.AlterModelOptions(
name='notification',
options={'ordering': ['-created'], 'verbose_name': 'Notification', 'verbose_name_plural': 'Notifications'},
),
migrations.RemoveField(
model_name='customer',
name='obligations',
),
migrations.RemoveField(
model_name='customer',
name='salary',
),
migrations.RemoveField(
model_name='notes',
name='created_at',
),
migrations.RemoveField(
model_name='notes',
name='opportunity',
),
migrations.RemoveField(
model_name='notes',
name='updated_at',
),
migrations.RemoveField(
model_name='notification',
name='created_at',
),
migrations.RemoveField(
model_name='opportunity',
name='created_at',
),
migrations.RemoveField(
model_name='opportunity',
name='created_by',
),
migrations.RemoveField(
model_name='opportunity',
name='deal_name',
),
migrations.RemoveField(
model_name='opportunity',
name='deal_status',
),
migrations.RemoveField(
model_name='opportunity',
name='deal_value',
),
migrations.RemoveField(
model_name='opportunity',
name='priority',
),
migrations.RemoveField(
model_name='opportunity',
name='updated_at',
),
migrations.RemoveField(
model_name='staff',
name='created_at',
),
migrations.RemoveField(
model_name='staff',
name='updated_at',
),
migrations.AddField(
model_name='notes',
name='content_type',
field=models.ForeignKey(default=1, on_delete=django.db.models.deletion.CASCADE, to='contenttypes.contenttype'),
preserve_default=False,
),
migrations.AddField(
model_name='notes',
name='created',
field=models.DateTimeField(auto_now_add=True, default=django.utils.timezone.now, verbose_name='Created'),
preserve_default=False,
),
migrations.AddField(
model_name='notes',
name='object_id',
field=models.PositiveIntegerField(default=1),
preserve_default=False,
),
migrations.AddField(
model_name='notes',
name='updated',
field=models.DateTimeField(auto_now=True, verbose_name='Updated'),
),
migrations.AddField(
model_name='notification',
name='created',
field=models.DateTimeField(auto_now_add=True, default=django.utils.timezone.now, verbose_name='Created'),
preserve_default=False,
),
migrations.AddField(
model_name='opportunity',
name='created',
field=models.DateTimeField(auto_now_add=True, default=django.utils.timezone.now, verbose_name='Created'),
preserve_default=False,
),
migrations.AddField(
model_name='opportunity',
name='dealer',
field=models.ForeignKey(default=1, on_delete=django.db.models.deletion.CASCADE, related_name='opportunities', to='inventory.dealer'),
preserve_default=False,
),
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.AddField(
model_name='opportunity',
name='stage',
field=models.CharField(choices=[('prospect', 'Prospect'), ('proposal', 'Proposal'), ('negotiation', 'Negotiation'), ('closed_won', 'Closed Won'), ('closed_lost', 'Closed Lost')], default='prospect', max_length=20, verbose_name='Stage'),
preserve_default=False,
),
migrations.AddField(
model_name='opportunity',
name='updated',
field=models.DateTimeField(auto_now=True, verbose_name='Updated'),
),
migrations.AddField(
model_name='staff',
name='created',
field=models.DateTimeField(auto_now_add=True, default=django.utils.timezone.now, verbose_name='Created'),
preserve_default=False,
),
migrations.AddField(
model_name='staff',
name='updated',
field=models.DateTimeField(auto_now=True, verbose_name='Updated'),
),
migrations.AlterField(
model_name='notes',
name='created_by',
field=models.ForeignKey(on_delete=django.db.models.deletion.DO_NOTHING, related_name='notes_created', to='inventory.staff'),
),
migrations.CreateModel(
name='Activity',
fields=[
('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
('object_id', models.PositiveIntegerField()),
('activity_type', models.CharField(choices=[('call', 'Call'), ('sms', 'SMS'), ('email', 'Email'), ('whatsapp', 'WhatsApp'), ('visit', 'Visit'), ('add_car', 'Add Car'), ('reserve_car', 'Reserve Car'), ('remove_car', 'Remove Car'), ('create_quotation', 'Create Quotation'), ('cancel_quotation', 'Cancel Quotation'), ('create_order', 'Create Order'), ('cancel_order', 'Cancel Order'), ('create_invoice', 'Create Invoice'), ('cancel_invoice', 'Cancel Invoice')], max_length=50, verbose_name='Activity Type')),
('notes', models.TextField(blank=True, null=True, verbose_name='Notes')),
('created', models.DateTimeField(auto_now_add=True, verbose_name='Created')),
('updated', models.DateTimeField(auto_now=True, verbose_name='Updated')),
('content_type', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to='contenttypes.contenttype')),
('created_by', models.ForeignKey(on_delete=django.db.models.deletion.DO_NOTHING, related_name='activities_created', to='inventory.staff')),
],
options={
'verbose_name': 'Activity',
'verbose_name_plural': 'Activities',
},
),
migrations.CreateModel(
name='Lead',
fields=[
('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
('title', models.CharField(choices=[('mr', 'Mr'), ('mrs', 'Mrs'), ('ms', 'Ms'), ('miss', 'Miss'), ('dr', 'Dr'), ('prof', 'Prof'), ('prince', 'Prince'), ('princess', 'Princess'), ('company', 'Company'), ('na', 'N/A')], max_length=20, verbose_name='Title')),
('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(db_index=True, max_length=254, unique=True, verbose_name='Email')),
('salary', models.PositiveIntegerField(blank=True, null=True, verbose_name='Salary')),
('obligations', models.PositiveIntegerField(blank=True, null=True, verbose_name='Obligations')),
('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')),
('priority', models.CharField(choices=[('low', 'Low'), ('medium', 'Medium'), ('high', 'High')], default='low', max_length=10, verbose_name='Priority')),
('status', models.CharField(choices=[('new', 'New'), ('pending', 'Pending'), ('assigned', 'Assigned'), ('in_progress', 'In Progress'), ('contacted', 'Contacted'), ('qualified', 'Qualified'), ('canceled', 'Canceled')], db_index=True, max_length=50, verbose_name='Status')),
('created', models.DateTimeField(auto_now_add=True, db_index=True, verbose_name='Created')),
('updated', models.DateTimeField(auto_now=True, verbose_name='Updated')),
('assigned', models.ForeignKey(null=True, on_delete=django.db.models.deletion.SET_NULL, related_name='assigned', to='inventory.staff', verbose_name='Assigned')),
('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.AddField(
model_name='customer',
name='lead',
field=models.OneToOneField(blank=True, null=True, on_delete=django.db.models.deletion.SET_NULL, related_name='converted', to='inventory.lead', verbose_name='Lead'),
),
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'), ('pending', 'Pending'), ('assigned', 'Assigned'), ('in_progress', 'In Progress'), ('contacted', 'Contacted'), ('qualified', 'Qualified'), ('canceled', 'Canceled')], max_length=50, verbose_name='Old Status')),
('new_status', models.CharField(choices=[('new', 'New'), ('pending', 'Pending'), ('assigned', 'Assigned'), ('in_progress', 'In Progress'), ('contacted', 'Contacted'), ('qualified', 'Qualified'), ('canceled', 'Canceled')], max_length=50, verbose_name='New Status')),
('changed_at', models.DateTimeField(auto_now_add=True, verbose_name='Changed At')),
('changed_by', models.ForeignKey(on_delete=django.db.models.deletion.DO_NOTHING, related_name='status_changes', to='inventory.staff')),
('lead', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, related_name='status_history', to='inventory.lead')),
],
options={
'verbose_name': 'Lead Status History',
'verbose_name_plural': 'Lead Status Histories',
},
),
migrations.DeleteModel(
name='OpportunityLog',
),
]

View File

@ -1,20 +0,0 @@
# Generated by Django 5.1.4 on 2025-01-09 09:57
import inventory.models
from django.db import migrations
class Migration(migrations.Migration):
dependencies = [
('inventory', '0008_alter_notes_options_alter_notification_options_and_more'),
]
operations = [
migrations.AlterModelManagers(
name='staff',
managers=[
('objects', inventory.models.StaffUserManager()),
],
),
]

View File

@ -1,19 +0,0 @@
# Generated by Django 5.1.4 on 2025-01-09 11:36
import django.db.models.deletion
from django.db import migrations, models
class Migration(migrations.Migration):
dependencies = [
('inventory', '0009_alter_staff_managers'),
]
operations = [
migrations.AddField(
model_name='customer',
name='staff',
field=models.ForeignKey(null=True, on_delete=django.db.models.deletion.SET_NULL, related_name='customer_staff', to='inventory.staff', verbose_name='Staff'),
),
]

View File

@ -1,22 +0,0 @@
# Generated by Django 5.1.4 on 2025-01-09 20:54
from django.db import migrations, models
class Migration(migrations.Migration):
dependencies = [
('inventory', '0010_customer_staff'),
]
operations = [
migrations.RemoveField(
model_name='customer',
name='country',
),
migrations.AddField(
model_name='customer',
name='city',
field=models.CharField(blank=True, max_length=255, verbose_name='City'),
),
]

View File

@ -1,20 +0,0 @@
# Generated by Django 5.1.4 on 2025-01-11 10:32
import inventory.models
from django.db import migrations, models
class Migration(migrations.Migration):
dependencies = [
('inventory', '0011_remove_customer_country_customer_city'),
]
operations = [
migrations.AddField(
model_name='opportunity',
name='probability',
field=models.PositiveIntegerField(default=70, validators=[inventory.models.validate_probability]),
preserve_default=False,
),
]

View File

@ -1,20 +0,0 @@
# Generated by Django 5.1.4 on 2025-01-11 11:09
import phonenumber_field.modelfields
from django.db import migrations
class Migration(migrations.Migration):
dependencies = [
('inventory', '0012_opportunity_probability'),
]
operations = [
migrations.AddField(
model_name='lead',
name='phone_number',
field=phonenumber_field.modelfields.PhoneNumberField(default='0535521547', max_length=128, region='SA', verbose_name='Phone Number'),
preserve_default=False,
),
]

View File

@ -1,26 +0,0 @@
# Generated by Django 5.1.4 on 2025-01-11 12:12
import django.db.models.deletion
from django.conf import settings
from django.db import migrations, models
class Migration(migrations.Migration):
dependencies = [
('inventory', '0013_lead_phone_number'),
migrations.swappable_dependency(settings.AUTH_USER_MODEL),
]
operations = [
migrations.AlterField(
model_name='activity',
name='created_by',
field=models.ForeignKey(on_delete=django.db.models.deletion.DO_NOTHING, related_name='activities_created', to=settings.AUTH_USER_MODEL),
),
migrations.AlterField(
model_name='notes',
name='created_by',
field=models.ForeignKey(on_delete=django.db.models.deletion.DO_NOTHING, related_name='notes_created', to=settings.AUTH_USER_MODEL),
),
]

View File

@ -1,19 +0,0 @@
# Generated by Django 5.1.4 on 2025-01-11 12:28
from django.db import migrations, models
class Migration(migrations.Migration):
dependencies = [
('inventory', '0014_alter_activity_created_by_alter_notes_created_by'),
]
operations = [
migrations.AddField(
model_name='lead',
name='city',
field=models.CharField(default='Riyadh', max_length=50, verbose_name='City'),
preserve_default=False,
),
]

View File

@ -1,18 +0,0 @@
# Generated by Django 5.1.4 on 2025-01-11 12:31
from django.db import migrations, models
class Migration(migrations.Migration):
dependencies = [
('inventory', '0015_lead_city'),
]
operations = [
migrations.AddField(
model_name='lead',
name='address',
field=models.CharField(blank=True, max_length=200, null=True, verbose_name='Address'),
),
]

View File

@ -1,19 +0,0 @@
# Generated by Django 5.1.4 on 2025-01-11 19:20
import django.db.models.deletion
from django.db import migrations, models
class Migration(migrations.Migration):
dependencies = [
('inventory', '0016_lead_address'),
]
operations = [
migrations.AlterField(
model_name='lead',
name='assigned',
field=models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.SET_NULL, related_name='assigned', to='inventory.staff', verbose_name='Assigned'),
),
]

View File

@ -1,18 +0,0 @@
# Generated by Django 5.1.4 on 2025-01-11 23:36
from django.db import migrations, models
class Migration(migrations.Migration):
dependencies = [
('inventory', '0017_alter_lead_assigned'),
]
operations = [
migrations.AlterField(
model_name='lead',
name='priority',
field=models.CharField(choices=[('low', 'Low'), ('medium', 'Medium'), ('high', 'High')], default='medium', max_length=10, verbose_name='Priority'),
),
]

View File

@ -1,45 +0,0 @@
# Generated by Django 5.1.4 on 2025-01-12 01:43
import django.utils.timezone
from django.db import migrations, models
class Migration(migrations.Migration):
dependencies = [
('inventory', '0018_alter_lead_priority'),
]
operations = [
migrations.AddField(
model_name='opportunity',
name='closed',
field=models.BooleanField(default=False, verbose_name='Closed'),
),
migrations.AddField(
model_name='opportunity',
name='closing_date',
field=models.DateField(default=django.utils.timezone.now, verbose_name='Closing Date'),
preserve_default=False,
),
migrations.AddField(
model_name='opportunity',
name='status',
field=models.CharField(choices=[('new', 'New'), ('pending', 'Pending'), ('in_progress', 'In Progress'), ('qualified', 'Qualified'), ('canceled', 'Canceled')], default='new', max_length=20, verbose_name='Status'),
),
migrations.AlterField(
model_name='lead',
name='status',
field=models.CharField(choices=[('new', 'New'), ('pending', 'Pending'), ('in_progress', 'In Progress'), ('qualified', 'Qualified'), ('canceled', 'Canceled')], 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'), ('pending', 'Pending'), ('in_progress', 'In Progress'), ('qualified', 'Qualified'), ('canceled', 'Canceled')], max_length=50, verbose_name='New Status'),
),
migrations.AlterField(
model_name='leadstatushistory',
name='old_status',
field=models.CharField(choices=[('new', 'New'), ('pending', 'Pending'), ('in_progress', 'In Progress'), ('qualified', 'Qualified'), ('canceled', 'Canceled')], max_length=50, verbose_name='Old Status'),
),
]

View File

@ -75,6 +75,22 @@ class VatRate(models.Model):
def __str__(self): def __str__(self):
return f"Rate: {self.rate}%" return f"Rate: {self.rate}%"
class CarType(models.IntegerChoices):
CAR = 1, _('Car')
LIGHT_COMMERCIAL = 2, _('Light Commercial')
HEAVY_DUTY_TRACTORS = 3, _('Heavy-Duty Tractors')
TRAILERS = 4, _('Trailers')
MEDIUM_TRUCKS = 5, _('Medium Trucks')
BUSES = 6, _('Buses')
MOTORCYCLES = 20, _('Motorcycles')
BUGGY = 21, _('Buggy')
MOTO_ATV = 22, _('Moto ATV')
SCOOTERS = 23, _('Scooters')
KARTING = 24, _('Karting')
ATV = 25, _('ATV')
SNOWMOBILES = 26, _('Snowmobiles')
class CarMake(models.Model, LocalizedNameMixin): class CarMake(models.Model, LocalizedNameMixin):
id_car_make = models.AutoField(primary_key=True) id_car_make = models.AutoField(primary_key=True)

View File

@ -78,9 +78,11 @@ urlpatterns = [
path('cars/finance/<int:pk>/update/', views.CarFinanceUpdateView.as_view(), name='car_finance_update'), path('cars/finance/<int:pk>/update/', views.CarFinanceUpdateView.as_view(), name='car_finance_update'),
path('cars/add/', views.CarCreateView.as_view(), name='car_add'), path('cars/add/', views.CarCreateView.as_view(), name='car_add'),
path('ajax/', views.AjaxHandlerView.as_view(), name='ajax_handler'), path('ajax/', views.AjaxHandlerView.as_view(), name='ajax_handler'),
path('cars/get-car-models/', views.get_car_models, name='get_car_models'),
path('cars/<int:car_pk>/add-color/', views.CarColorCreate.as_view(), name='add_color'), path('cars/<int:car_pk>/add-color/', views.CarColorCreate.as_view(), name='add_color'),
path('cars/<int:car_pk>/location/add/', views.CarLocationCreateView.as_view(), name='add_car_location'), path('cars/<int:car_pk>/location/add/', views.CarLocationCreateView.as_view(), name='add_car_location'),
path('cars/<int:pk>/location/update/', views.CarLocationUpdateView.as_view(), name='transfer'), path('cars/<int:pk>/location/update/', views.CarLocationUpdateView.as_view(), name='transfer'),
path('cars/inventory/search/', views.SearchCodeView.as_view(), name='car_search'),
# path('cars/<int:car_pk>/colors/<int:pk>/update/',views.CarColorUpdateView.as_view(),name='color_update'), # path('cars/<int:car_pk>/colors/<int:pk>/update/',views.CarColorUpdateView.as_view(),name='color_update'),
path('cars/reserve/<int:car_id>/', views.reserve_car_view, name='reserve_car'), path('cars/reserve/<int:car_id>/', views.reserve_car_view, name='reserve_car'),
@ -183,6 +185,6 @@ handler500 = 'inventory.views.custom_error_view'
handler403 = 'inventory.views.custom_permission_denied_view' handler403 = 'inventory.views.custom_permission_denied_view'
handler400 = 'inventory.views.custom_bad_request_view' handler400 = 'inventory.views.custom_bad_request_view'

View File

@ -1,5 +1,7 @@
from decimal import Decimal from decimal import Decimal
from django.core.paginator import Paginator from django.core.paginator import Paginator
from django.utils.decorators import method_decorator
from django.views.decorators.csrf import csrf_exempt
from django_ledger.models import ( from django_ledger.models import (
EntityModel, EntityModel,
InvoiceModel, InvoiceModel,
@ -81,6 +83,10 @@ from django.contrib.auth.models import User
from allauth.account import views from allauth.account import views
from django.db.models import Count, F, Value from django.db.models import Count, F, Value
from django.contrib.auth import authenticate from django.contrib.auth import authenticate
import cv2
import numpy as np
from pyzbar.pyzbar import decode
logger = logging.getLogger(__name__) logger = logging.getLogger(__name__)
@ -427,6 +433,38 @@ class AjaxHandlerView(LoginRequiredMixin, View):
return JsonResponse(serialized_options, safe=False) return JsonResponse(serialized_options, safe=False)
@method_decorator(csrf_exempt, name='dispatch')
class SearchCodeView(View):
template_name = "inventory/scan_vin.html"
def get(self, request, *args, **kwargs):
"""Render the form page."""
return render(request, self.template_name)
def post(self, request, *args, **kwargs):
image_file = request.FILES.get('image')
if image_file:
print("image received!")
image = cv2.imdecode(np.frombuffer(image_file.read(), np.uint8), cv2.IMREAD_COLOR)
decoded_objects = decode(image)
if decoded_objects:
print("image decoded!")
print(decoded_objects[0])
code = decoded_objects[0].data.decode('utf-8')
print("code received!")
print(code)
car = get_object_or_404(models.Car, vin=code)
name = car.id_car_make.get_local_name
print(name)
return redirect('car_detail', pk=car.pk)
else:
print("back to else statement")
return JsonResponse({'success': False, 'error': 'No code detected'})
else:
return JsonResponse({'success': False, 'error': 'No image provided'})
class CarInventory(LoginRequiredMixin, ListView): class CarInventory(LoginRequiredMixin, ListView):
model = models.Car model = models.Car
home_label = _("inventory") home_label = _("inventory")
@ -594,9 +632,7 @@ class CarFinanceCreateView(LoginRequiredMixin, CreateView):
def get_form(self, form_class=None): def get_form(self, form_class=None):
form = super().get_form(form_class) form = super().get_form(form_class)
dealer = get_user_type(self.request) dealer = get_user_type(self.request)
form.fields[ form.fields["additional_finances"].queryset = models.AdditionalServices.objects.filter(dealer=dealer)
"additional_finances"
].queryset = models.AdditionalServices.objects.filter(dealer=dealer)
return form return form
# def get_initial(self): # def get_initial(self):
@ -1702,17 +1738,16 @@ class BankAccountCreateView(LoginRequiredMixin, SuccessMessageMixin, CreateView)
success_message = "Bank account created successfully." success_message = "Bank account created successfully."
def form_valid(self, form): def form_valid(self, form):
form.instance.entity_model = self.request.user.dealer.entity dealer = get_user_type(self.request)
form.instance.entity_model = dealer.entity
return super().form_valid(form) return super().form_valid(form)
def get_form_kwargs(self): def get_form_kwargs(self):
""" dealer = get_user_type(self.request)
Override this method to pass additional keyword arguments to the form. entity = dealer.entity
"""
entity = self.request.user.dealer.entity
kwargs = super().get_form_kwargs() kwargs = super().get_form_kwargs()
kwargs["entity_slug"] = entity.slug # Get entity_slug from URL kwargs["entity_slug"] = entity.slug
kwargs["user_model"] = entity.admin # Get user_model from the request kwargs["user_model"] = entity.admin
return kwargs return kwargs
@ -1730,10 +1765,8 @@ class BankAccountUpdateView(LoginRequiredMixin, SuccessMessageMixin, UpdateView)
success_message = "Bank account updated successfully." success_message = "Bank account updated successfully."
def get_form_kwargs(self): def get_form_kwargs(self):
""" dealer = get_user_type(self.request)
Override this method to pass additional keyword arguments to the form. entity = dealer.entity
"""
entity = self.request.user.dealer.entity
kwargs = super().get_form_kwargs() kwargs = super().get_form_kwargs()
kwargs["entity_slug"] = entity.slug # Get entity_slug from URL kwargs["entity_slug"] = entity.slug # Get entity_slug from URL
kwargs["user_model"] = entity.admin # Get user_model from the request kwargs["user_model"] = entity.admin # Get user_model from the request
@ -1764,7 +1797,8 @@ class AccountListView(LoginRequiredMixin, ListView):
paginate_by = 10 paginate_by = 10
def get_queryset(self): def get_queryset(self):
entity = self.request.user.dealer.entity dealer = get_user_type(self.request)
entity = dealer.entity
qs = entity.get_all_accounts() qs = entity.get_all_accounts()
paginator = Paginator(qs, 20) paginator = Paginator(qs, 20)
page_number = self.request.GET.get("page", 1) # Default to page 1 page_number = self.request.GET.get("page", 1) # Default to page 1
@ -2417,12 +2451,11 @@ class LeadDetailView(DetailView):
class LeadCreateView(CreateView, SuccessMessageMixin, LoginRequiredMixin): class LeadCreateView(CreateView, SuccessMessageMixin, LoginRequiredMixin):
model = models.Lead model = models.Lead
form_class = forms.LeadCreateForm form_class = forms.LeadForm
template_name = 'crm/leads/lead_create_form.html' template_name = 'crm/leads/lead_form.html'
success_message = "Lead created successfully!" # success_message = "Lead created successfully!"
success_url = reverse_lazy('lead_list') success_url = reverse_lazy('lead_list')
def form_valid(self, form): def form_valid(self, form):
print("Form data:", form.cleaned_data) # Debug form data print("Form data:", form.cleaned_data) # Debug form data
dealer = get_user_type(self.request) dealer = get_user_type(self.request)
@ -2430,10 +2463,18 @@ class LeadCreateView(CreateView, SuccessMessageMixin, LoginRequiredMixin):
return super().form_valid(form) return super().form_valid(form)
def get_car_models(request):
make_id = request.GET.get('id_car_make')
if make_id:
car_models = models.CarModel.objects.filter(id_car_make=make_id).values('id_car_model', 'name', 'arabic_name')
return JsonResponse(list(car_models), safe=False)
return JsonResponse([], safe=False)
class LeadUpdateView(UpdateView): class LeadUpdateView(UpdateView):
model = models.Lead model = models.Lead
form_class = forms.LeadUpdateForm form_class = forms.LeadForm
template_name = 'crm/leads/lead_update_form.html' template_name = 'crm/leads/lead_form.html'
success_url = reverse_lazy('lead_list') success_url = reverse_lazy('lead_list')
class LeadDeleteView(DeleteView): class LeadDeleteView(DeleteView):

View File

@ -1,26 +1,43 @@
aiohappyeyeballs==2.4.4
aiohttp==3.11.11
aiohttp-retry==2.8.3
aiosignal==1.3.2
alabaster==1.0.0 alabaster==1.0.0
albucore==0.0.13
albumentations==1.4.10
annotated-types==0.7.0 annotated-types==0.7.0
anyio==4.6.2.post1 anyio==4.6.2.post1
arabic-reshaper==3.0.0
asgiref==3.8.1 asgiref==3.8.1
astor==0.8.1
astroid==3.3.5 astroid==3.3.5
attrs==23.2.0 attrs==23.2.0
autopep8==2.3.1 autopep8==2.3.1
babel==2.16.0 babel==2.16.0
beautifulsoup4==4.12.3 beautifulsoup4==4.12.3
bleach==6.2.0
blinker==1.9.0
Brotli==1.1.0 Brotli==1.1.0
certifi==2024.8.30 certifi==2024.8.30
cffi==1.17.1 cffi==1.17.1
chardet==5.2.0 chardet==5.2.0
charset-normalizer==3.4.0 charset-normalizer==3.4.0
click==8.1.7 click==8.1.8
colorama==0.4.6
commonmark==0.9.1
contourpy==1.3.1
crispy-bootstrap5==2024.10 crispy-bootstrap5==2024.10
cryptography==44.0.0 cryptography==44.0.0
cssselect2==0.7.0 cssselect2==0.7.0
cycler==0.12.1
Cython==3.0.11
decorator==5.1.1
desert==2020.11.18
dill==0.3.9 dill==0.3.9
distro==1.9.0 distro==1.9.0
dj-rest-auth==7.0.0 dj-rest-auth==7.0.0
dj-shop-cart==7.1.1 dj-shop-cart==7.1.1
Django==4.2.17 Django==5.1.4
django-allauth==65.3.0 django-allauth==65.3.0
django-autoslug==1.9.9 django-autoslug==1.9.9
django-bootstrap5==24.3 django-bootstrap5==24.3
@ -34,10 +51,13 @@ django-filter==24.3
django-formtools==2.5.1 django-formtools==2.5.1
django-ledger==0.7.0 django-ledger==0.7.0
django-money==3.5.3 django-money==3.5.3
django-nine==0.2.7
django-nonefield==0.4
django-phonenumber-field==8.0.0 django-phonenumber-field==8.0.0
django-prometheus==2.3.1 django-prometheus==2.3.1
django-sekizai==4.1.0 django-sekizai==4.1.0
django-silk==5.3.1 django-silk==5.3.1
django-sms==0.7.0
django-sslserver==0.22 django-sslserver==0.22
django-tables2==2.7.0 django-tables2==2.7.0
django-treebeard==4.7.1 django-treebeard==4.7.1
@ -47,9 +67,13 @@ djangorestframework==3.15.2
djangorestframework-simplejwt==5.3.1 djangorestframework-simplejwt==5.3.1
djangoviz==0.1.1 djangoviz==0.1.1
docutils==0.21.2 docutils==0.21.2
easy-thumbnails==2.10
et_xmlfile==2.0.0 et_xmlfile==2.0.0
Faker==33.1.0 Faker==33.1.0
fire==0.7.0
Flask==3.1.0
fonttools==4.55.3 fonttools==4.55.3
frozenlist==1.5.0
gprof2dot==2024.6.6 gprof2dot==2024.6.6
graphqlclient==0.2.4 graphqlclient==0.2.4
greenlet==3.1.1 greenlet==3.1.1
@ -57,80 +81,143 @@ h11==0.14.0
httpcore==1.0.7 httpcore==1.0.7
httpx==0.28.0 httpx==0.28.0
idna==3.10 idna==3.10
imageio==2.36.1
imagesize==1.4.1 imagesize==1.4.1
imgaug==0.4.0
iso4217==1.12.20240625 iso4217==1.12.20240625
isodate==0.7.2 isodate==0.7.2
isort==5.13.2 isort==5.13.2
itsdangerous==2.2.0
Jinja2==3.1.4 Jinja2==3.1.4
jiter==0.8.0 jiter==0.8.0
joblib==1.4.2 joblib==1.4.2
kiwisolver==1.4.8
lazy_loader==0.4
ledger==1.0.1 ledger==1.0.1
lmdb==1.6.2
lxml==5.3.0 lxml==5.3.0
Markdown==3.7 Markdown==3.7
markdown-it-py==3.0.0 markdown-it-py==3.0.0
MarkupSafe==3.0.2 MarkupSafe==3.0.2
marshmallow==3.23.2
matplotlib==3.10.0
mccabe==0.7.0 mccabe==0.7.0
mdurl==0.1.2 mdurl==0.1.2
MouseInfo==0.1.3
multidict==6.1.0
mypy-extensions==1.0.0
networkx==3.4.2
newrelic==10.3.1 newrelic==10.3.1
nltk==3.9.1 nltk==3.9.1
numpy==1.26.4
oauthlib==3.2.2 oauthlib==3.2.2
ofxtools==0.9.5 ofxtools==0.9.5
openai==1.56.2 openai==1.56.2
opencv-contrib-python==4.10.0.84
opencv-python==4.10.0.84
opencv-python-headless==4.10.0.84
openpyxl==3.1.5 openpyxl==3.1.5
opt-einsum==3.3.0
outcome==1.3.0.post0
packaging==24.2 packaging==24.2
pandas==2.2.3
pango==0.0.1 pango==0.0.1
pdfkit==1.0.0 pdfkit==1.0.0
phonenumbers==8.13.51 phonenumbers==8.13.51
pillow==11.0.0 pillow==11.0.0
platformdirs==4.3.6 platformdirs==4.3.6
prometheus_client==0.21.1 prometheus_client==0.21.1
propcache==0.2.1
protobuf==5.29.3
psycopg==3.2.3 psycopg==3.2.3
psycopg-binary==3.2.3 psycopg-binary==3.2.3
psycopg-c==3.2.3
py-moneyed==3.0 py-moneyed==3.0
PyAutoGUI==0.9.54
pyclipper==1.3.0.post6
pycodestyle==2.12.1 pycodestyle==2.12.1
pycparser==2.22 pycparser==2.22
pydantic==2.10.3 pydantic==2.10.3
pydantic_core==2.27.1 pydantic_core==2.27.1
pydotplus==2.0.2 pydotplus==2.0.2
pydyf==0.11.0 pydyf==0.11.0
PyGetWindow==0.0.9
Pygments==2.18.0 Pygments==2.18.0
PyJWT==2.10.1 PyJWT==2.10.1
pylint==3.3.2 pylint==3.3.2
PyMsgBox==1.0.9
PyMySQL==1.1.1 PyMySQL==1.1.1
pyobjc-core==10.3.2
pyobjc-framework-Cocoa==10.3.2
pyobjc-framework-Quartz==10.3.2
pyparsing==3.2.0 pyparsing==3.2.0
pyperclip==1.9.0
pyphen==0.17.0 pyphen==0.17.0
pypng==0.20220715.0 pypng==0.20220715.0
PyRect==0.2.0
PyScreeze==1.0.1
pyserial==3.5 pyserial==3.5
PySocks==1.7.1
python-bidi==0.6.3
python-dateutil==2.9.0.post0 python-dateutil==2.9.0.post0
python-docx==1.1.2
python-openid==2.2.5 python-openid==2.2.5
python3-saml==1.16.0 python3-saml==1.16.0
pytweening==1.2.0
pytz==2024.2 pytz==2024.2
pyvin==0.0.2 pyvin==0.0.2
pywa==2.4.0
pywhat==5.1.0
pywhatkit==5.4
PyYAML==6.0.2
qrcode==8.0 qrcode==8.0
RapidFuzz==3.11.0
regex==2024.11.6 regex==2024.11.6
reportlab==4.2.5 reportlab==4.2.5
requests==2.32.3 requests==2.32.3
requests-oauthlib==2.0.0 requests-oauthlib==2.0.0
rich==13.9.4 rich==13.9.4
setuptools==75.6.0 rubicon-objc==0.4.9
scikit-image==0.25.0
scikit-learn==1.6.0
scipy==1.14.1
selenium==4.27.1
shapely==2.0.6
six==1.16.0 six==1.16.0
sniffio==1.3.1 sniffio==1.3.1
snowballstemmer==2.2.0 snowballstemmer==2.2.0
sortedcontainers==2.4.0
soupsieve==2.6 soupsieve==2.6
SQLAlchemy==2.0.36 SQLAlchemy==2.0.36
sqlparse==0.5.2 sqlparse==0.5.2
tablib==3.7.0 tablib==3.7.0
termcolor==2.5.0
threadpoolctl==3.5.0
tifffile==2025.1.10
tinycss2==1.4.0 tinycss2==1.4.0
tinyhtml5==2.0.0 tinyhtml5==2.0.0
tomli==2.2.1
tomlkit==0.13.2 tomlkit==0.13.2
tqdm==4.67.1 tqdm==4.67.1
trio==0.28.0
trio-websocket==0.11.1
twilio==9.4.1
typing-inspect==0.9.0
typing_extensions==4.12.2 typing_extensions==4.12.2
tzdata==2024.2
Unidecode==1.3.8
upgrade-requirements==1.7.0 upgrade-requirements==1.7.0
urllib3==2.2.3 urllib3==2.2.3
vin==0.6.2 vin==0.6.2
vininfo==1.8.0 vininfo==1.8.0
vishap==0.1.5
vpic-api==0.7.4
weasyprint==63.1 weasyprint==63.1
webencodings==0.5.1 webencodings==0.5.1
websocket-client==1.8.0
Werkzeug==3.1.3 Werkzeug==3.1.3
wikipedia==1.4.0
wsproto==1.2.0
xmlsec==1.3.14 xmlsec==1.3.14
yarl==1.18.3
zopfli==0.2.3.post1 zopfli==0.2.3.post1

View File

@ -1,388 +0,0 @@
{% extends "base.html" %}
{% load i18n static %}
{% block content %}
<div class="row g-3">
<div class="col-sm-12 col-md-8">
<h2>
{% if form.instance.pk %}
{{ _("Edit Lead") }}
{% else %}
{{ _("Add New Lead") }}
{% endif %}
</h2>
</div>
<div class="col-sm-12 col-md-8">
<!-- The 'novalidate' attribute is removed to allow HTML5 validation.
If you prefer manual validation, you can add 'novalidate'. -->
<form class="row g-3 form needs-validation" method="post" action="{% url 'lead_create' %}">
{% csrf_token %}
<!-- Title -->
<div class="col-sm-6 col-md-4">
<div class="form-floating">
<select
class="form-control"
id="{{ form.title.id_for_label }}"
name="{{ form.title.name }}"
aria-label="{{ _('Title') }}"
required
>
<option value="">{{ _('Select Title') }}</option>
{% for value, label in form.title.field.choices %}
<option value="{{ value }}" {% if form.title.value == value %}selected{% endif %}>
{{ label }}
</option>
{% endfor %}
</select>
<label for="{{ form.title.id_for_label }}">{{ _("Title") }}</label>
</div>
<span class="invalid-feedback">{{ form.title.errors }}</span>
</div>
<!-- First Name -->
<div class="col-sm-6 col-md-4">
<div class="form-floating">
<input
type="text"
class="form-control"
id="{{ form.first_name.id_for_label }}"
name="{{ form.first_name.name }}"
value="{{ form.first_name.value|default:'' }}"
placeholder="{{ _('Enter first name') }}"
aria-label="{{ _('First Name') }}"
required
>
<label for="{{ form.first_name.id_for_label }}">{{ _("First Name") }}</label>
</div>
<span class="invalid-feedback">{{ form.first_name.errors }}</span>
</div>
<!-- Last Name -->
<div class="col-sm-6 col-md-4">
<div class="form-floating">
<input
type="text"
class="form-control"
id="{{ form.last_name.id_for_label }}"
name="{{ form.last_name.name }}"
value="{{ form.last_name.value|default:'' }}"
placeholder="{{ _('Enter last name') }}"
aria-label="{{ _('Last Name') }}"
required
>
<label for="{{ form.last_name.id_for_label }}">{{ _("Last Name") }}</label>
</div>
<span class="invalid-feedback">{{ form.last_name.errors }}</span>
</div>
<!-- Email -->
<div class="col-sm-6 col-md-4">
<div class="form-floating">
<input
type="email"
class="form-control"
id="{{ form.email.id_for_label }}"
name="{{ form.email.name }}"
value="{{ form.email.value|default:'' }}"
placeholder="{{ _('Enter email') }}"
aria-label="{{ _('Email') }}"
required
>
<label for="{{ form.email.id_for_label }}">{{ _("Email") }}</label>
</div>
<span class="invalid-feedback">{{ form.email.errors }}</span>
</div>
<!-- Phone Number -->
<div class="col-sm-6 col-md-4">
<div class="form-floating">
<input
type="text"
class="form-control"
id="{{ form.phone_number.id_for_label }}"
name="{{ form.phone_number.name }}"
value="{{ form.phone_number.value|default:'' }}"
placeholder="{{ _('Enter phone number') }}"
aria-label="{{ _('Phone Number') }}"
pattern="^[+]?[\d\s-]{5,}$"
required>
<label for="{{ form.phone_number.id_for_label }}">{{ _("Phone Number") }}</label>
</div>
<span class="invalid-feedback">{{ form.phone_number.errors }}</span>
</div>
<!-- Salary -->
<div class="col-sm-6 col-md-4">
<div class="form-floating">
<input
type="number"
class="form-control"
id="{{ form.salary.id_for_label }}"
name="{{ form.salary.name }}"
value="{{ form.salary.value|default:'' }}"
placeholder="{{ _('Enter salary') }}"
aria-label="{{ _('Salary') }}"
required
>
<label for="{{ form.salary.id_for_label }}">{{ _("Salary") }}</label>
</div>
<span class="invalid-feedback">{{ form.salary.errors }}</span>
</div>
<!-- Obligations -->
<div class="col-sm-6 col-md-4">
<div class="form-floating">
<input
type="number"
class="form-control"
id="{{ form.obligations.id_for_label }}"
name="{{ form.obligations.name }}"
value="{{ form.obligations.value|default:'' }}"
placeholder="{{ _('Enter obligations') }}"
aria-label="{{ _('Obligations') }}"
required
>
<label for="{{ form.obligations.id_for_label }}">{{ _("Obligations") }}</label>
</div>
<span class="invalid-feedback">{{ form.obligations.errors }}</span>
</div>
<!-- Car Make -->
<div class="col-sm-6 col-md-4">
<div class="form-floating">
<select
class="form-select"
id="{{ form.id_car_make.id_for_label }}"
name="{{ form.id_car_make.name }}"
aria-label="{{ _('Car Make') }}">
<option value="">{{ _('Select Make') }}</option>
{% for value, label in form.id_car_make.field.choices %}
<option value="{{ value }}" {% if form.id_car_make.value == value %}selected{% endif %}>
{{ label }}
</option>
{% endfor %}
</select>
<label for="{{ form.id_car_make.id_for_label }}">{{ _("Car Make") }}</label>
</div>
</div>
<!-- Car Model -->
<div class="col-sm-6 col-md-4">
<div class="form-floating">
<select
class="form-select"
id="{{ form.id_car_model.id_for_label }}"
name="{{ form.id_car_model.name }}"
aria-label="{{ _('Car Model') }}">
<option value="">{{ _('Select Model') }}</option>
{% for value, label in form.id_car_model.field.choices %}
<option value="{{ value }}" {% if form.id_car_model.value == value %}selected{% endif %}>
{{ label }}
</option>
{% endfor %}
</select>
<label for="{{ form.id_car_model.id_for_label }}">{{ _("Car Model") }}</label>
</div>
</div>
<!-- Year -->
<div class="col-sm-6 col-md-4">
<div class="form-floating">
<input
type="number"
class="form-control"
id="{{ form.year.id_for_label }}"
name="{{ form.year.name }}"
value="{{ form.year.value|default:'' }}"
placeholder="{{ _('Enter year') }}"
aria-label="{{ _('Year') }}"
required
>
<label for="{{ form.year.id_for_label }}">{{ _("Year") }}</label>
</div>
<span class="invalid-feedback">{{ form.year.errors }}</span>
</div>
<!-- Source -->
<div class="col-sm-6 col-md-4">
<div class="form-floating">
<select
class="form-control"
id="{{ form.source.id_for_label }}"
name="{{ form.source.name }}"
aria-label="{{ _('Source') }}"
required
>
<option value="">{{ _('Select Source') }}</option>
{% for value, label in form.source.field.choices %}
<option value="{{ value }}" {% if form.source.value == value %}selected{% endif %}>
{{ label }}
</option>
{% endfor %}
</select>
<label for="{{ form.source.id_for_label }}">{{ _("Source") }}</label>
</div>
<span class="invalid-feedback">{{ form.source.errors }}</span>
</div>
<!-- Channel -->
<div class="col-sm-6 col-md-4">
<div class="form-floating">
<select
class="form-control"
id="{{ form.channel.id_for_label }}"
name="{{ form.channel.name }}"
aria-label="{{ _('Channel') }}"
required
>
<option value="">{{ _('Select Channel') }}</option>
{% for value, label in form.channel.field.choices %}
<option value="{{ value }}" {% if form.channel.value == value %}selected{% endif %}>
{{ label }}
</option>
{% endfor %}
</select>
<label for="{{ form.channel.id_for_label }}">{{ _("Channel") }}</label>
</div>
<span class="invalid-feedback">{{ form.channel.errors }}</span>
</div>
<!-- Assigned -->
<div class="col-sm-6 col-md-4">
<div class="form-floating">
<select
class="form-control"
id="{{ form.assigned.id_for_label }}"
name="{{ form.assigned.name }}"
aria-label="{{ _('Assigned To') }}"
required
>
<option value="">{{ _('Select Assignee') }}</option>
{% for value, label in form.assigned.field.choices %}
<option value="{{ value }}" {% if form.assigned.value == value %}selected{% endif %}>
{{ label }}
</option>
{% endfor %}
</select>
<label for="{{ form.assigned.id_for_label }}">{{ _("Assigned To") }}</label>
</div>
<span class="invalid-feedback">{{ form.assigned.errors }}</span>
</div>
<!-- Priority -->
<div class="col-sm-6 col-md-4">
<div class="form-floating">
<select
class="form-control"
id="{{ form.priority.id_for_label }}"
name="{{ form.priority.name }}"
aria-label="{{ _('Priority') }}"
required
>
<option value="">{{ _('Select Priority') }}</option>
{% for value, label in form.priority.field.choices %}
<option value="{{ value }}" {% if form.priority.value == value %}selected{% endif %}>
{{ label }}
</option>
{% endfor %}
</select>
<label for="{{ form.priority.id_for_label }}">{{ _("Priority") }}</label>
</div>
<span class="invalid-feedback">{{ form.priority.errors }}</span>
</div>
<!-- Form Actions -->
<div class="col-sm-12">
<button
type="submit"
name="add_lead"
id="lead-save"
class="btn btn-phoenix-primary"
>
{{ _("Save") }}
</button>
<a href="{% url 'lead_list' %}" class="btn btn-phoenix-secondary">
{{ _("Cancel") }}
</a>
</div>
</form>
</div>
</div>
<script>
// Reusable function to get the CSRF cookie
function getCookie(name) {
let cookieValue = null;
if (document.cookie && document.cookie !== "") {
const cookies = document.cookie.split(";");
for (let cookie of cookies) {
cookie = cookie.trim();
if (cookie.substring(0, name.length + 1) === name + "=") {
cookieValue = decodeURIComponent(cookie.substring(name.length + 1));
break;
}
}
}
return cookieValue;
}
document.addEventListener("DOMContentLoaded", function () {
const ajaxUrl = "{% url 'ajax_handler' %}";
const makeSelect = document.getElementById("{{ form.id_car_make.id_for_label }}");
const modelSelect = document.getElementById("{{ form.id_car_model.id_for_label }}");
const csrfToken = getCookie("csrftoken");
// Helper function to reset the dropdown
function resetDropdown(dropdown, placeholder) {
dropdown.innerHTML = `<option value="">${placeholder}</option>`;
}
// Load car models based on make
async function loadModels(makeId) {
// Show a temporary loading message
resetDropdown(modelSelect, "{{ _('Loading...') }}");
try {
const response = await fetch(`${ajaxUrl}?action=get_models&make_id=${makeId}`, {
headers: {
"X-CSRFToken": csrfToken,
"X-Requested-With": "XMLHttpRequest",
},
});
const data = await response.json();
// Reset dropdown before populating
resetDropdown(modelSelect, "{{ _('Select Model') }}");
data.forEach((model) => {
const option = document.createElement("option");
option.value = model.id_car_model;
// Display name based on language
option.textContent = document.documentElement.lang === "en" ? model.name : model.arabic_name;
modelSelect.appendChild(option);
});
} catch (error) {
console.error("Error loading models:", error);
}
}
// When car make changes, load corresponding models
makeSelect.addEventListener("change", () => {
const make_id = makeSelect.value;
if (make_id) {
loadModels(make_id);
} else {
resetDropdown(modelSelect, "{{ _('Select Model') }}");
}
});
// Debug normal form submission
document.querySelector("form").addEventListener("submit", (event) => {
// If you'd like to handle the form manually, uncomment the next line:
// event.preventDefault();
console.log("Form submitted normally");
});
});
</script>
{% endblock %}

View File

@ -0,0 +1,20 @@
{% extends 'base.html' %}
{% load i18n static crispy_forms_filters %}
{% block content %}
<h1>{% if object %}Update{% else %}Create{% endif %}</h1>
<form method="post">
{% csrf_token %}
{{ form|crispy }}
<button
type="submit"
name="add_lead"
id="lead-save"
class="btn btn-phoenix-primary"
>
{{ _("Save") }}
</button>
<a href="{% url 'lead_list' %}" class="btn btn-phoenix-secondary">
{{ _("Cancel") }}
</a>
</form>
{% endblock %}

View File

@ -1,232 +0,0 @@
{% extends "base.html" %}
{% load i18n static%}
{% block content %}
<div class="row g-3">
<div class="col-sm-6 col-md-8">
<h2>{% if form.instance.pk %}{{ _("Edit Lead") }}{% else %}{{ _("Add New Lead") }}{% endif %}</h2>
</div>
<div class="col-sm-6 col-md-8">
<form class="row g-3 form" method="post" action="{% url 'lead_create' %}">
{% csrf_token %}
<!-- Title -->
<div class="col-sm-6 col-md-4">
<div class="form-floating">
<select class="form-control" id="{{ form.title.id_for_label }}" name="{{ form.title.name }}">
{% for value, label in form.title.field.choices %}
<option value="{{ value }}" {% if form.title.value == value %}selected{% endif %}>{{ label }}</option>
{% endfor %}
</select>
<label for="{{ form.title.id_for_label }}">{{ _("Title") }}</label>
</div>
{{ form.title.errors }}
</div>
<!-- First Name -->
<div class="col-sm-6 col-md-4">
<div class="form-floating">
<input type="text" class="form-control" id="{{ form.first_name.id_for_label }}" name="{{ form.first_name.name }}" value="{{ form.first_name.value|default:'' }}" placeholder="{{ _('Enter first name') }}">
<label for="{{ form.first_name.id_for_label }}">{{ _("First Name") }}</label>
</div>
{{ form.first_name.errors }}
</div>
<!-- Last Name -->
<div class="col-sm-6 col-md-4">
<div class="form-floating">
<input type="text" class="form-control" id="{{ form.last_name.id_for_label }}" name="{{ form.last_name.name }}" value="{{ form.last_name.value|default:'' }}" placeholder="{{ _('Enter last name') }}">
<label for="{{ form.last_name.id_for_label }}">{{ _("Last Name") }}</label>
</div>
{{ form.last_name.errors }}
</div>
<!-- Email -->
<div class="col-sm-6 col-md-4">
<div class="form-floating">
<input type="email" class="form-control" id="{{ form.email.id_for_label }}" name="{{ form.email.name }}" value="{{ form.email.value|default:'' }}" placeholder="{{ _('Enter email') }}">
<label for="{{ form.email.id_for_label }}">{{ _("Email") }}</label>
</div>
{{ form.email.errors }}
</div>
<!-- Phone Number -->
<div class="col-sm-6 col-md-4">
<div class="form-floating">
<input type="text" class="form-control" id="{{ form.phone_number.id_for_label }}" name="{{ form.phone_number.name }}" value="{{ form.phone_number.value|default:'' }}" placeholder="{{ _('Enter phone number') }}">
<label for="{{ form.phone_number.id_for_label }}">{{ _("Phone Number") }}</label>
</div>
{{ form.phone_number.errors }}
</div>
<!-- Salary -->
<div class="col-sm-6 col-md-4">
<div class="form-floating">
<input type="number" class="form-control" id="{{ form.salary.id_for_label }}" name="{{ form.salary.name }}" value="{{ form.salary.value|default:'' }}" placeholder="{{ _('Enter salary') }}">
<label for="{{ form.salary.id_for_label }}">{{ _("Salary") }}</label>
</div>
{{ form.salary.errors }}
</div>
<!-- Obligations -->
<div class="col-sm-6 col-md-4">
<div class="form-floating">
<input type="number" class="form-control" id="{{ form.obligations.id_for_label }}" name="{{ form.obligations.name }}" value="{{ form.obligations.value|default:'' }}" placeholder="{{ _('Enter obligations') }}">
<label for="{{ form.obligations.id_for_label }}">{{ _("Obligations") }}</label>
</div>
{{ form.obligations.errors }}
</div>
<!-- Car Make -->
<div class="col-sm-6 col-md-4">
<div class="form-floating">
<select class="form-select form-select-sm" id="{{ form.id_car_make.id_for_label }}" name="{{ form.id_car_make.name }}">
{% for value, label in form.id_car_make.field.choices %}
<option value="{{ value }}" {% if form.id_car_make.value == value %}selected{% endif %}>{{ label }}</option>
{% endfor %}
</select>
<label for="{{ form.id_car_make.id_for_label }}">{{ _("Car Make") }}</label>
</div>
{{ form.id_car_make.errors }}
</div>
<!-- Car Model -->
<div class="col-sm-6 col-md-4">
<div class="form-floating">
<select class="form-select form-select-sm" id="{{ form.id_car_model.id_for_label }}" name="{{ form.id_car_model.name }}">
{% for value, label in form.id_car_model.field.choices %}
<option value="{{ value }}" {% if form.id_car_model.value == value %}selected{% endif %}>{{ label }}</option>
{% endfor %}
</select>
<label for="{{ form.id_car_model.id_for_label }}">{{ _("Car Make") }}</label>
</div>
{{ form.id_car_model.errors }}
</div>
<!-- Year -->
<div class="col-sm-6 col-md-4">
<div class="form-floating">
<input type="number" class="form-control" id="{{ form.year.id_for_label }}" name="{{ form.year.name }}" value="{{ form.year.value|default:'' }}" placeholder="{{ _('Enter year') }}">
<label for="{{ form.year.id_for_label }}">{{ _("Year") }}</label>
</div>
{{ form.year.errors }}
</div>
<!-- Source -->
<div class="col-sm-6 col-md-4">
<div class="form-floating">
<select class="form-control" id="{{ form.source.id_for_label }}" name="{{ form.source.name }}">
{% for value, label in form.source.field.choices %}
<option value="{{ value }}" {% if form.source.value == value %}selected{% endif %}>{{ label }}</option>
{% endfor %}
</select>
<label for="{{ form.source.id_for_label }}">{{ _("Source") }}</label>
</div>
{{ form.source.errors }}
</div>
<!-- Channel -->
<div class="col-sm-6 col-md-4">
<div class="form-floating">
<select class="form-control" id="{{ form.channel.id_for_label }}" name="{{ form.channel.name }}">
{% for value, label in form.channel.field.choices %}
<option value="{{ value }}" {% if form.channel.value == value %}selected{% endif %}>{{ label }}</option>
{% endfor %}
</select>
<label for="{{ form.channel.id_for_label }}">{{ _("Channel") }}</label>
</div>
{{ form.channel.errors }}
</div>
<!-- Assigned -->
<div class="col-sm-6 col-md-4">
<div class="form-floating">
<select class="form-control" id="{{ form.assigned.id_for_label }}" name="{{ form.assigned.name }}">
{% for value, label in form.assigned.field.choices %}
<option value="{{ value }}" {% if form.assigned.value == value %}selected{% endif %}>{{ label }}</option>
{% endfor %}
</select>
<label for="{{ form.assigned.id_for_label }}">{{ _("Assigned To") }}</label>
</div>
{{ form.assigned.errors }}
</div>
<!-- Priority -->
<div class="col-sm-6 col-md-4">
<div class="form-floating">
<select class="form-control" id="{{ form.priority.id_for_label }}" name="{{ form.priority.name }}">
{% for value, label in form.priority.field.choices %}
<option value="{{ value }}" {% if form.priority.value == value %}selected{% endif %}>{{ label }}</option>
{% endfor %}
</select>
<label for="{{ form.priority.id_for_label }}">{{ _("Priority") }}</label>
</div>
{{ form.priority.errors }}
</div>
<div class="col-sm-12">
<button type="submit" name="add_lead" id="lead-save" class="btn btn-phoenix-primary">{{ _("Save") }}</button>
<a href="{% url 'lead_list' %}" class="btn btn-phoenix-secondary">{{ _("Cancel") }}</a>
</div>
</form>
</div>
</div>
<script>
function getCookie(name) {
let cookieValue = null;
if (document.cookie && document.cookie !== "") {
const cookies = document.cookie.split(";");
for (let cookie of cookies) {
cookie = cookie.trim();
if (cookie.substring(0, name.length + 1) === name + "=") {
cookieValue = decodeURIComponent(cookie.substring(name.length + 1));
break;
}
}
}
return cookieValue;
}
document.addEventListener("DOMContentLoaded", function () {
const ajaxUrl = "{% url 'ajax_handler' %}";
const makeSelect = document.getElementById("{{ form.id_car_make.id_for_label }}");
const modelSelect = document.getElementById("{{ form.id_car_model.id_for_label }}");
const csrfToken = getCookie("csrftoken");
function resetDropdown(dropdown, placeholder) {
dropdown.innerHTML = `<option value="">${placeholder}</option>`;
}
async function loadModels(makeId) {
resetDropdown(modelSelect, '{% trans "Select" %}');
const response = await fetch(`${ajaxUrl}?action=get_models&make_id=${makeId}`, {
headers: {
"X-CSRFToken": csrfToken,
"X-Requested-With": "XMLHttpRequest",
},
});
const data = await response.json();
data.forEach((model) => {
const option = document.createElement("option");
option.value = model.id_car_model;
option.textContent = document.documentElement.lang === "en" ? model.name : model.arabic_name;
modelSelect.appendChild(option);
});
}
// AJAX logic: Load models when car make changes
makeSelect.addEventListener("change", () => {
const make_id = makeSelect.value;
if (make_id) loadModels(make_id);
});
// Ensure the form submits normally
document.querySelector("form").addEventListener("submit", (event) => {
// Do not call event.preventDefault() here unless you handle form submission manually
console.log("Form submitted normally"); // Debugging
});
});
</script>
{% endblock %}

View File

@ -10,31 +10,31 @@
<a class="nav-link dropdown-indicator label-1" href="#nv-dashboards" role="button" data-bs-toggle="collapse" aria-expanded="false" aria-controls="nv-dashboards"> <a class="nav-link dropdown-indicator label-1" href="#nv-dashboards" role="button" data-bs-toggle="collapse" aria-expanded="false" aria-controls="nv-dashboards">
<div class="d-flex align-items-center"> <div class="d-flex align-items-center">
<div class="dropdown-indicator-icon-wrapper"><span class="fas fa-caret-right dropdown-indicator-icon"></span></div> <div class="dropdown-indicator-icon-wrapper"><span class="fas fa-caret-right dropdown-indicator-icon"></span></div>
<span class="nav-link-icon"><span data-feather="pie-chart"></span></span><span class="nav-link-text">Dashboards</span> <span class="nav-link-icon"><span data-feather="pie-chart"></span></span><span class="nav-link-text">{{ _("Dashboards") }}</span>
</div> </div>
</a> </a>
<div class="parent-wrapper label-1"> <div class="parent-wrapper label-1">
<ul class="nav collapse parent" data-bs-parent="#navbarVerticalCollapse" id="nv-dashboards"> <ul class="nav collapse parent" data-bs-parent="#navbarVerticalCollapse" id="nv-dashboards">
<li class="collapsed-nav-item-title d-none">Dashboards</li> <li class="collapsed-nav-item-title d-none">{{ _("Dashboards") }}</li>
<li class="nav-item"> <li class="nav-item">
<a class="nav-link" href="{% url 'accounting' %}"> <a class="nav-link" href="{% url 'accounting' %}">
<div class="d-flex align-items-center"><span class="nav-link-text">Accounting</span></div> <div class="d-flex align-items-center"><span class="nav-link-text">{{ _("Accounting") }}</span></div>
</a> </a>
</li> </li>
<li class="nav-item"> <li class="nav-item">
<a class="nav-link" href="#"> <a class="nav-link" href="#">
<div class="d-flex align-items-center"><span class="nav-link-text">Inventory</span></div> <div class="d-flex align-items-center"><span class="nav-link-text">{{ _("Inventory") }}</span></div>
</a> </a>
</li> </li>
<li class="nav-item"> <li class="nav-item">
<a class="nav-link" href="#"> <a class="nav-link" href="#">
<div class="d-flex align-items-center"><span class="nav-link-text">Sales</span></div> <div class="d-flex align-items-center"><span class="nav-link-text">{{ _("Sales") }}</span></div>
</a> </a>
</li> </li>
<li class="nav-item"> <li class="nav-item">
<a class="nav-link" href="#"> <a class="nav-link" href="#">
<div class="d-flex align-items-center"><span class="nav-link-text">CRM</span></div> <div class="d-flex align-items-center"><span class="nav-link-text">{{ _("CRM") }}</span></div>
</a> </a>
</li> </li>
</ul> </ul>
@ -139,7 +139,7 @@
</div> </div>
</div> </div>
<div class="nav-item-wrapper"> <div class="nav-item-wrapper">
<a class="nav-link dropdown-indicator label-1" href="#nv-crm" role="button" data-bs-toggle="collapse" aria-expanded="false" aria-controls="nv-sales"> <a class="nav-link dropdown-indicator label-1" href="#nv-crm" role="button" data-bs-toggle="collapse" aria-expanded="false" aria-controls="nv-crm">
<div class="d-flex align-items-center"> <div class="d-flex align-items-center">
<div class="dropdown-indicator-icon-wrapper"><span class="fas fa-caret-right dropdown-indicator-icon"></span></div> <div class="dropdown-indicator-icon-wrapper"><span class="fas fa-caret-right dropdown-indicator-icon"></span></div>
<span class="nav-link-icon"><span data-feather="phone"></span></span><span class="nav-link-text">{% trans 'crm'|upper %}</span> <span class="nav-link-icon"><span data-feather="phone"></span></span><span class="nav-link-text">{% trans 'crm'|upper %}</span>
@ -189,6 +189,34 @@
</ul> </ul>
</div> </div>
</div> </div>
<div class="nav-item-wrapper">
<a class="nav-link dropdown-indicator label-1" href="#nv-accounting" role="button" data-bs-toggle="collapse" aria-expanded="false" aria-controls="nv-accounting">
<div class="d-flex align-items-center">
<div class="dropdown-indicator-icon-wrapper"><span class="fas fa-caret-right dropdown-indicator-icon"></span></div>
<span class="nav-link-icon"><span data-feather="book"></span></span><span class="nav-link-text">{% trans 'Accounting' %}</span>
</div>
</a>
<div class="parent-wrapper label-1">
<ul class="nav collapse parent" data-bs-parent="#navbarVerticalCollapse" id="nv-accounting">
<li class="collapsed-nav-item-title d-none">{% trans 'Accounting' %}</li>
<li class="nav-item">
<a class="nav-link" href="{% url 'account_list' %}">
<div class="d-flex align-items-center">
<span class="nav-link-icon"><span data-feather="book-open"></span></span><span class="nav-link-text">{% trans 'Chart of Accounts' %}</span>
</div>
</a>
</li>
<li class="nav-item">
<a class="nav-link" href="{% url 'bank_account_list' %}">
<div class="d-flex align-items-center">
<span class="nav-link-icon"><span data-feather="users"></span></span><span class="nav-link-text">{% trans 'Banks' %}</span>
</div>
</a>
</li>
</ul>
</div>
</div>
<!--Add Above --> <!--Add Above -->
</li> </li>
</ul> </ul>

View File

@ -282,7 +282,7 @@
<div class="modal-content rounded-top-2"> <div class="modal-content rounded-top-2">
<div class="modal-header"> <div class="modal-header">
<h5 class="modal-title" id="equipmentOptionsModalLabel"> <h5 class="modal-title" id="equipmentOptionsModalLabel">
{% trans 'specifications'|capfirst %} {% trans 'Options'|capfirst %}
</h5> </h5>
<button type="button" class="btn-close" data-bs-dismiss="modal" aria-label="{% trans 'Close' %}"></button> <button type="button" class="btn-close" data-bs-dismiss="modal" aria-label="{% trans 'Close' %}"></button>
</div> </div>
@ -617,6 +617,7 @@
showSpecificationButton.disabled = !trimId showSpecificationButton.disabled = !trimId
showEquipmentButton.disabled = !trimId showEquipmentButton.disabled = !trimId
if (trimId) loadSpecifications(trimId) if (trimId) loadSpecifications(trimId)
loadEquipment(trimId)
}) })
equipmentSelect.addEventListener("change", () => { equipmentSelect.addEventListener("change", () => {

View File

@ -7,165 +7,134 @@
{% block content %} {% block content %}
<div class="row"> <div class="row g-3 justify-content-between">
<div class="row g-3 justify-content-between"> <div class="col-sm-12">
<div class="col-sm-12"> <div class="card border h-100 w-100 overflow-hidden">
<div class="card border h-100 w-100 overflow-hidden"> <div class="bg-holder d-block bg-card" style="background-image:url({% static 'images/spot-illustrations/32.png' %});background-position: top right;">
<div class="bg-holder d-block bg-card" style="background-image:url({% static 'images/spot-illustrations/32.png' %});background-position: top right;">
</div>
<div class="d-dark-none me-5">
<div class="bg-holder d-none d-sm-block d-xl-none d-xxl-block bg-card" style="background-image:url({% static 'images/spot-illustrations/dark_21.png' %}); background-position: bottom right; background-size: auto;">
</div>
</div>
<div class="d-light-none me-5">
<div class="bg-holder d-none d-sm-block d-xl-none d-xxl-block bg-card" style="background-image:url({% static 'images/spot-illustrations/21.png' %}); background-position: bottom right; background-size: auto;">
</div>
</div>
<div class="card-body px-lg-5 position-relative">
<br>
<div class="row align-items-center g-3 g-sm-5 text-start text-sm-start">
<div class="col-12 col-sm-auto ">
<div class="avatar avatar-3xl avatar-bordered mb-3">
{% if cars.first.id_car_make.logo %}
<img class="rounded" src="{{ cars.first.id_car_make.logo.url }}" alt="">
{% else %}
<img class="rounded" src="{% static 'images/logos/car_make/sedan.svg' %}" alt="">
{% endif %}
</div>
</div>
<div class="col-12 col-sm-auto flex-1">
<h3 class="mb-2">{{ cars.first.id_car_make.get_local_name }}<span class="ms-2 text-body-tertiary fw-semibold">{{ cars.first.id_car_model.get_local_name }}</span></h3>
<p class="text-body-tertiary fw-semibold">{{ cars.first.id_car_serie.name }}, <span class="fs-10">{{ cars.first.id_car_trim.name }}</span></p>
</div>
</div>
</div>
</div>
</div> </div>
</div> <div class="d-dark-none me-5">
<div class="row g-3 justify-content-between mt-4"> <div class="bg-holder d-none d-sm-block d-xl-none d-xxl-block bg-card" style="background-image:url({% static 'images/spot-illustrations/dark_21.png' %}); background-position: bottom right; background-size: auto;">
<div class="col-sm-12"> </div>
<div class="table-list" id="inventoryTable"> </div>
<div class="table-responsive scrollbar mb-3"> <div class="d-light-none me-5">
<table class="table table-sm fs-9 mb-0 overflow-hidden"> <div class="bg-holder d-none d-sm-block d-xl-none d-xxl-block bg-card" style="background-image:url({% static 'images/spot-illustrations/21.png' %}); background-position: bottom right; background-size: auto;">
<thead class="text-body"> </div>
<tr> </div>
<th class="pe-1 align-middle white-space-nowrap text-center" data-sort="stock">{% trans 'Stock' %}</th> <div class="card-body px-lg-5 position-relative">
<th class="pe-1 align-middle white-space-nowrap text-start" data-sort="vin" style="min-width: 4.5rem;">{% trans "VIN" %}</th> <br>
<th class="pe-1 align-middle white-space-nowrap text-center" data-sort="date">{% trans "Year"|upper %}</th> <div class="row align-items-center g-3 g-sm-5 text-start text-sm-start">
<th class="pe-1 align-middle white-space-nowrap text-center" data-sort="extColor" style="min-width: 8.5rem">{% trans 'Exterior Color'|upper %}</th>
<th class="pe-1 align-middle white-space-nowrap text-center" data-sort="intColor" style="min-width: 8.5rem">{% trans 'Interior Color'|upper %}</th> <div class="col-12 col-sm-auto ">
<th class="pe-1 align-middle white-space-nowrap text-center" data-sort="location" style="min-width: 12.5rem;">{% trans "Showroom Location"|upper %}</th> <div class="avatar avatar-3xl avatar-bordered mb-3">
<th class="pe-1 align-middle white-space-nowrap text-center" data-sort="status">{% trans 'Status'|upper %}</th> {% if cars.first.id_car_make.logo %}
<th class="pe-1 align-middle white-space-nowrap text-center" data-sort="age" style="min-width: 7rem">{% trans 'Age'|upper %}</th> <img class="rounded" src="{{ cars.first.id_car_make.logo.url }}" alt="">
<th class="no-sort align-middle white-space-nowrap text-center"></th> {% else %}
</tr> <img class="rounded" src="{% static 'images/logos/car_make/sedan.svg' %}" alt="">
</thead>
<tbody>
{% for car in cars %}
<tr>
<td class="align-middle white-space-nowrap text-center fw-bold text-body-tertiary">
{% if car.stock_type == "new" %}
<span class="badge badge-phoenix badge-phoenix-success"><span class="badge-label">{{_("New")}}</span></span>
{% elif car.status == "used" %}
<span class="badge badge-phoenix badge-phoenix-info"><span class="badge-label">{{_("Used")}}</span></span>
{% endif %}
</td>
<td class="align-middle white-space-nowrap text-start fw-bold">{{ car.vin }}</td>
<td class="align-middle white-space-nowrap text-center fw-bold">{{ car.year }}</td>
{% if car.colors.exists %}
<td class="align-middle white-space-nowrap text-body fs-9 text-start">
<div class="d-flex flex-column align-items-center">
<span class="color-div" style="background: linear-gradient(90deg, rgba({{ car.colors.first.exterior.rgb }},1) 10%, rgba({{ car.colors.first.exterior.rgb }},0.10) 100%);" title="{{ car.colors.first.exterior.get_local_name }}"></span><span>{{ car.colors.first.exterior.get_local_name }}</span>
</div>
</td>
<td class="align-middle white-space-nowrap text-body fs-9 text-start">
<div class="d-flex flex-column align-items-center">
<span class="color-div" style="background: linear-gradient(90deg, rgba({{ car.colors.first.interior.rgb }},1) 10%, rgba({{ car.colors.first.interior.rgb }},0.10) 100%);" title="{{ car.colors.first.interior.get_local_name }}"></span><span>{{ car.colors.first.interior.get_local_name }}</span>
</div>
</td>
{% else %}
<td class="align-middle white-space-nowrap text-body fs-9 text-center"><span class="color-div">{% trans 'No Color' %}</span></td>
<td class="align-middle white-space-nowrap text-body fs-9 text-center"><span class="color-div">{% trans 'No Color' %}</span></td>
{% endif %}
<td class="align-middle white-space-nowrap text-body fs-9 text-center">
{% if car.location.is_owner_showroom %}
{% trans 'Our Showroom' %}
{% else %}
{{ car.location.showroom.get_local_name }}
{% endif %}
</td>
<td class="status align-middle white-space-nowrap text-center fw-bold text-body-tertiary">
{% if car.status == "available" %}
<span class="badge badge-phoenix fs-10 badge-phoenix-success"><span class="badge-label">{{_("Available")}}</span><span class="ms-1" data-feather="check" style="height:13px;width:13px;"></span></span>
{% elif car.status == "sold" %}
<span class="badge badge-phoenix fs-10 badge-phoenix-primary"><span class="badge-label">{{_("Sold")}}</span><span class="ms-1" data-feather="package" style="height:13px;width:13px;"></span></span>
{% elif car.status == "hold" %}
<span class="badge badge-phoenix fs-10 badge-phoenix-warning"><span class="badge-label">{{ _("Hold") }}</span><span class="ms-1" data-feather="alert-octagon" style="height:13px;width:13px;"></span></span>
{% elif car.status == "reserved" %}
<span class="badge badge-phoenix fs-10 badge-phoenix-danger"><span class="badge-label">{{ _("Reserved") }}</span><span class="ms-1" data-feather="info" style="height:13px;width:13px;"></span></span>
{% elif car.status == "damaged" %}
<span class="badge badge-phoenix fs-10 badge-phoenix-secondary"><span class="badge-label">{{ _("Damaged") }}</span><span class="ms-1" data-feather="x" style="height:13px;width:13px;"></span></span>
{% endif %}
</td>
<td class="date align-middle white-space-nowrap text-body-tertiary fs-9 ps-4 text-start">
<span class="fw-light">{{ car.receiving_date|timesince }}</span>
</td>
<td class="align-middle white-space-nowrap text-end pe-0 ps-4">
<div class="btn-reveal-trigger position-static">
<button class="btn btn-sm dropdown-toggle dropdown-caret-none transition-none btn-reveal fs-10" type="button" data-bs-toggle="dropdown" data-boundary="window" aria-haspopup="true" aria-expanded="false" data-bs-reference="parent"><span class="fas fa-ellipsis-h fs-10"></span></button>
<div class="dropdown-menu dropdown-menu-end py-2"><a class="dropdown-item" href="{% url 'car_detail' car.pk %}">{% trans "view"|capfirst %}</a>
<div class="dropdown-divider"></div><a class="dropdown-item text-danger" href="#!">Remove</a>
</div>
</div>
</td>
</tr>
{% empty %}
<tr>
<td colspan="7" class="d-flex flex-column align-items-center">
<p class="text-muted">{% trans "No cars available." %}</p>
<a href="{% url 'add_car' %}" class="btn btn-primary">{% trans "Add a Car" %}</a>
</td>
</tr>
{% endfor %}
</tbody>
</table>
</div>
</div>
<!-- Pagination -->
{% if is_paginated %}
<nav aria-label="Page navigation">
<ul class="pagination pagination-sm justify-content-center">
{% if page_obj.has_previous %}
<li class="page-item">
<a class="page-link" href="?page=1" aria-label="First">&laquo;&laquo;</a>
</li>
<li class="page-item">
<a class="page-link" href="?page={{ page_obj.previous_page_number }}" aria-label="Previous">&laquo;</a>
</li>
{% endif %}
{% for num in page_obj.paginator.page_range %}
{% if page_obj.number == num %}
<li class="page-item active">
<span class="page-link">{{ num }}</span>
</li>
{% elif num > page_obj.number|add:'-3' and num < page_obj.number|add:'3' %}
<li class="page-item">
<a class="page-link" href="?page={{ num }}">{{ num }}</a>
</li>
{% endif %}
{% endfor %}
{% if page_obj.has_next %}
<li class="page-item">
<a class="page-link" href="?page={{ page_obj.next_page_number }}" aria-label="Next">&raquo;</a>
</li>
<li class="page-item">
<a class="page-link" href="?page={{ page_obj.paginator.num_pages }}" aria-label="Last">&raquo;&raquo;</a>
</li>
{% endif %}
</ul>
</nav>
{% endif %} {% endif %}
</div>
</div>
<div class="col-12 col-sm-auto flex-1">
<h3 class="mb-2">{{ cars.first.id_car_make.get_local_name }}<span class="ms-2 text-body-tertiary fw-semibold">{{ cars.first.id_car_model.get_local_name }}</span></h3>
<p class="text-body-tertiary fw-semibold">{{ cars.first.id_car_serie.name }}, <span class="fs-10">{{ cars.first.id_car_trim.name }}</span></p>
</div>
</div>
</div> </div>
</div> </div>
</div> </div>
</div>
<div class="row g-3 justify-content-between mt-4">
<div class="col-sm-12">
<div class="table-list" id="inventoryTable">
<div class="table-responsive scrollbar mb-3">
<table class="table table-sm fs-9 mb-0 overflow-hidden">
<thead class="text-body">
<tr>
<th class="pe-1 align-middle white-space-nowrap text-center" data-sort="stock">{% trans 'Stock'|upper %}</th>
<th class="pe-1 align-middle white-space-nowrap text-start" data-sort="vin" style="min-width: 4.5rem;">{% trans "VIN" %}</th>
<th class="pe-1 align-middle white-space-nowrap text-center" data-sort="date">{% trans "Year"|upper %}</th>
<th class="pe-1 align-middle white-space-nowrap text-center" data-sort="extColor" style="min-width: 8.5rem">{% trans 'Exterior Color'|upper %}</th>
<th class="pe-1 align-middle white-space-nowrap text-center" data-sort="intColor" style="min-width: 8.5rem">{% trans 'Interior Color'|upper %}</th>
<th class="pe-1 align-middle white-space-nowrap text-center" data-sort="location" style="min-width: 12.5rem;">{% trans "Showroom Location"|upper %}</th>
<th class="pe-1 align-middle white-space-nowrap text-center" data-sort="status">{% trans 'Status'|upper %}</th>
<th class="pe-1 align-middle white-space-nowrap text-center" data-sort="age" style="min-width: 7rem">{% trans 'Age'|upper %}</th>
<th class="no-sort align-middle white-space-nowrap text-center"></th>
</tr>
</thead>
<tbody>
{% for car in cars %}
<tr>
<td class="align-middle white-space-nowrap text-center fw-bold text-body-tertiary">
{% if car.stock_type == "new" %}
<span class="badge badge-phoenix badge-phoenix-success"><span class="badge-label">{{_("New")}}</span></span>
{% elif car.status == "used" %}
<span class="badge badge-phoenix badge-phoenix-info"><span class="badge-label">{{_("Used")}}</span></span>
{% endif %}
</td>
<td class="align-middle white-space-nowrap text-start"><a class="fs-8 fw-bold" href="{% url 'car_detail' car.pk %}">{{ car.vin }}</a></td>
<td class="align-middle white-space-nowrap text-center fw-bold">{{ car.year }}</td>
{% if car.colors.exists %}
<td class="align-middle white-space-nowrap text-body fs-9 text-start">
<div class="d-flex flex-column align-items-center">
<span class="color-div" style="background: linear-gradient(90deg, rgba({{ car.colors.first.exterior.rgb }},1) 10%, rgba({{ car.colors.first.exterior.rgb }},0.10) 100%);" title="{{ car.colors.first.exterior.get_local_name }}"></span><span>{{ car.colors.first.exterior.get_local_name }}</span>
</div>
</td>
<td class="align-middle white-space-nowrap text-body fs-9 text-start">
<div class="d-flex flex-column align-items-center">
<span class="color-div" style="background: linear-gradient(90deg, rgba({{ car.colors.first.interior.rgb }},1) 10%, rgba({{ car.colors.first.interior.rgb }},0.10) 100%);" title="{{ car.colors.first.interior.get_local_name }}"></span><span>{{ car.colors.first.interior.get_local_name }}</span>
</div>
</td>
{% else %}
<td class="align-middle white-space-nowrap text-body fs-9 text-center"><span class="color-div">{% trans 'No Color' %}</span></td>
<td class="align-middle white-space-nowrap text-body fs-9 text-center"><span class="color-div">{% trans 'No Color' %}</span></td>
{% endif %}
<td class="align-middle white-space-nowrap text-body fs-9 text-center">
{% if car.location.is_owner_showroom %}
{% trans 'Our Showroom' %}
{% else %}
{{ car.location.showroom.get_local_name }}
{% endif %}
</td>
<td class="status align-middle white-space-nowrap text-center fw-bold text-body-tertiary">
{% if car.status == "available" %}
<span class="badge badge-phoenix fs-10 badge-phoenix-success"><span class="badge-label">{{_("Available")}}</span><span class="ms-1" data-feather="check" style="height:13px;width:13px;"></span></span>
{% elif car.status == "sold" %}
<span class="badge badge-phoenix fs-10 badge-phoenix-primary"><span class="badge-label">{{_("Sold")}}</span><span class="ms-1" data-feather="package" style="height:13px;width:13px;"></span></span>
{% elif car.status == "hold" %}
<span class="badge badge-phoenix fs-10 badge-phoenix-warning"><span class="badge-label">{{ _("Hold") }}</span><span class="ms-1" data-feather="alert-octagon" style="height:13px;width:13px;"></span></span>
{% elif car.status == "reserved" %}
<span class="badge badge-phoenix fs-10 badge-phoenix-danger"><span class="badge-label">{{ _("Reserved") }}</span><span class="ms-1" data-feather="info" style="height:13px;width:13px;"></span></span>
{% elif car.status == "damaged" %}
<span class="badge badge-phoenix fs-10 badge-phoenix-secondary"><span class="badge-label">{{ _("Damaged") }}</span><span class="ms-1" data-feather="x" style="height:13px;width:13px;"></span></span>
{% endif %}
</td>
<td class="date align-middle white-space-nowrap text-body-tertiary fs-9 ps-4 text-start">
<span class="fw-light">{{ car.receiving_date|timesince }}</span>
</td>
<td class="align-middle white-space-nowrap text-end pe-0 ps-4">
<div class="btn-reveal-trigger position-static">
<button class="btn btn-sm dropdown-toggle dropdown-caret-none transition-none btn-reveal fs-10" type="button" data-bs-toggle="dropdown" data-boundary="window" aria-haspopup="true" aria-expanded="false" data-bs-reference="parent"><span class="fas fa-ellipsis-h fs-10"></span></button>
<div class="dropdown-menu dropdown-menu-end py-2"><a class="dropdown-item" href="{% url 'car_detail' car.pk %}">{% trans "view"|capfirst %}</a>
<div class="dropdown-divider"></div><a class="dropdown-item text-danger" href="#!">Remove</a>
</div>
</div>
</td>
</tr>
{% empty %}
<tr>
<td colspan="7" class="d-flex flex-column align-items-center">
<p class="text-muted">{% trans "No cars available." %}</p>
<a href="{% url 'add_car' %}" class="btn btn-primary">{% trans "Add a Car" %}</a>
</td>
</tr>
{% endfor %}
</tbody>
</table>
</div>
</div>
<!-- Pagination -->
{% if is_paginated %}
{% include 'partials/pagination.html' %}
{% endif %}
</div>
</div>
{% endblock %} {% endblock %}

View File

@ -6,15 +6,38 @@
{% endblock %} {% endblock %}
{% block content %} {% block content %}
<div class="px-3 mb-6">
<div class="row justify-content-between">
<div class="col-6 col-md-4 col-xxl-2 text-center border-translucent border-start-xxl border-end-xxl-0 border-bottom-xxl-0 border-end border-bottom pb-4 pb-xxl-0 "><span class="uil fs-5 lh-1 uil-car-sideview text-primary"></span>
<h1 class="fs-5 pt-3">{{ inventory.total_cars }}</h1>
<p class="fs-9 mb-0">{% trans "Total Cars in Inventory" %}</p>
</div>
<div class="col-6 col-md-4 col-xxl-2 text-center border-translucent border-start-xxl border-end-xxl-0 border-bottom-xxl-0 border-end-md border-bottom pb-4 pb-xxl-0"><span class="uil fs-5 lh-1 uil-envelope-upload text-info"></span>
<h1 class="fs-5 pt-3">1,866</h1>
<p class="fs-9 mb-0">Emails Sent</p>
</div>
<div class="col-6 col-md-4 col-xxl-2 text-center border-translucent border-start-xxl border-bottom-xxl-0 border-bottom border-end border-end-md-0 pb-4 pb-xxl-0 pt-4 pt-md-0"><span class="uil fs-5 lh-1 uil-envelopes text-primary"></span>
<h1 class="fs-5 pt-3">1,366</h1>
<p class="fs-9 mb-0">Emails Delivered</p>
</div>
<div class="col-6 col-md-4 col-xxl-2 text-center border-translucent border-start-xxl border-end-md border-end-xxl-0 border-bottom border-bottom-md-0 pb-4 pb-xxl-0 pt-4 pt-xxl-0"><span class="uil fs-5 lh-1 uil-envelope-open text-info"></span>
<h1 class="fs-5 pt-3">1,200</h1>
<p class="fs-9 mb-0">Emails Opened</p>
</div>
<div class="col-6 col-md-4 col-xxl-2 text-center border-translucent border-start-xxl border-end border-end-xxl-0 pb-md-4 pb-xxl-0 pt-4 pt-xxl-0"><span class="uil fs-5 lh-1 uil-envelope-check text-success"></span>
<h1 class="fs-5 pt-3">900</h1>
<p class="fs-9 mb-0">Emails Clicked</p>
</div>
<div class="col-6 col-md-4 col-xxl-2 text-center border-translucent border-start-xxl border-end-xxl pb-md-4 pb-xxl-0 pt-4 pt-xxl-0"><span class="uil fs-5 lh-1 uil-envelope-block text-danger"></span>
<h1 class="fs-5 pt-3">500</h1>
<p class="fs-9 mb-0">Emails Bounce</p>
</div>
</div>
</div>
<div class="row mt-4"> <div class="row mt-4">
<!-- Total Cars --> <!-- Total Cars -->
<div class="alert alert-phoenix-primary">
<div class="d-flex justify-content-between">
<strong class="fs-6">{% trans "Total Cars in Inventory" %}</strong>
<strong class="fs-6">{{ inventory.total_cars }}</strong>
</div>
</div>
<!-- Inventory by Makes --> <!-- Inventory by Makes -->
<div class="accordion" id="makesAccordion"> <div class="accordion" id="makesAccordion">
{% for make in inventory.makes %} {% for make in inventory.makes %}

View File

@ -1,118 +1,114 @@
{% extends "base.html" %}
{% load i18n %}
{% load custom_filters %}
{% block content %}
<div class="row-fluid p-2"> <div class="row-fluid p-2">
<form id="car-form"> <form id="car-form">
<!-- Other form fields --> <div>
<div> <label for="vin_no">VIN/Barcode/QR Code:</label>
<label for="vin_no">VIN Number:</label> <input type="text" id="vin_no" name="vin_no" readonly>
<input type="text" id="vin_no" name="vin_no" readonly> <button type="button" id="capture-btn">Capture Code</button>
<button type="button" id="scan-vin-btn">Scan VIN</button> </div>
<button type="submit">Search</button>
</form>
<!-- Camera Stream -->
<div id="camera-container" style="display:none;">
<video id="camera" autoplay playsinline width="400"></video>
<button id="toggle-btn">Toggle Camera</button>
<button id="scan-btn">Scan</button>
</div> </div>
<button type="submit">Submit</button>
</form>
<!-- Scanner Modal --> <p id="result"></p>
<div id="scanner-modal" style="display:none;">
<div id="video"></div>
<button id="stop-scanner">Stop Scanner</button>
</div> </div>
<p id="result"></p>
</div>
<script src="https://cdnjs.cloudflare.com/ajax/libs/quagga/0.12.1/quagga.min.js"></script>
<script> <script>
const scanVinBtn = document.getElementById('scan-vin-btn'); let captureBtn = document.getElementById('capture-btn');
const vinInput = document.getElementById('vin_no'); let cameraContainer = document.getElementById('camera-container');
const scannerModal = document.getElementById('scanner-modal'); let videoElement = document.getElementById('camera');
const videoElement = document.getElementById('video'); let toggleBtn = document.getElementById('toggle-btn');
const stopScannerBtn = document.getElementById('stop-scanner'); let scanBtn = document.getElementById('scan-btn');
const resultElement = document.getElementById('result'); let vinInput = document.getElementById('vin_no');
let resultElement = document.getElementById('result');
let mediaStream = null;
let currentCameraIndex = 0;
let cameras = [];
// Open the scanner modal // List available cameras
scanVinBtn.addEventListener('click', () => { async function getCameras() {
scannerModal.style.display = 'block'; const devices = await navigator.mediaDevices.enumerateDevices();
startScanner(); cameras = devices.filter(device => device.kind === 'videoinput');
});
// Stop the scanner
stopScannerBtn.addEventListener('click', () => {
stopScanner();
scannerModal.style.display = 'none';
});
function startScanner() {
Quagga.init({
inputStream: {
name: "Live",
type: "LiveStream",
target: videoElement,
},
decoder: {
readers: ["code_128_reader"], // Use appropriate barcode reader
}
}, function(err) {
if (err) {
console.error(err);
resultElement.textContent = 'Error initializing scanner.';
return;
}
Quagga.start();
});
Quagga.onDetected(function(data) {
const vin = data.codeResult.code;
vinInput.value = vin; // Set the scanned VIN to the input field
stopScanner();
scannerModal.style.display = 'none';
// Send the VIN via AJAX to the backend
decodeVin(vin);
});
} }
function stopScanner() { // Start the camera with the given device ID
Quagga.stop(); async function startCamera(deviceId) {
} if (mediaStream) {
mediaStream.getTracks().forEach(track => track.stop()); // Stop the current stream
function decodeVin(vin) {
fetch('/api/decode-vin/', {
method: 'POST',
headers: {
'Content-Type': 'application/json',
'X-CSRFToken': getCSRFToken()
},
body: JSON.stringify({ vin_no: vin })
})
.then(response => response.json())
.then(data => {
if (data.success) {
resultElement.textContent = `VIN decoded: ${JSON.stringify(data.data)}`;
} else {
resultElement.textContent = `Error: ${data.error}`;
}
})
.catch(err => {
console.error(err);
resultElement.textContent = 'Error processing VIN.';
});
}
// Function to get CSRF token
function getCSRFToken() {
const name = 'csrftoken';
const cookies = document.cookie.split(';');
for (let i = 0; i < cookies.length; i++) {
const cookie = cookies[i].trim();
if (cookie.startsWith(name + '=')) {
return cookie.substring(name.length + 1);
}
} }
return '';
mediaStream = await navigator.mediaDevices.getUserMedia({
video: { deviceId: { exact: deviceId } }
});
videoElement.srcObject = mediaStream;
} }
</script>
{% endblock %} // Toggle between cameras
toggleBtn.addEventListener('click', async () => {
currentCameraIndex = (currentCameraIndex + 1) % cameras.length;
await startCamera(cameras[currentCameraIndex].deviceId);
});
// Capture image and send to backend
scanBtn.addEventListener('click', () => {
const canvas = document.createElement('canvas');
canvas.width = videoElement.videoWidth;
canvas.height = videoElement.videoHeight;
const context = canvas.getContext('2d');
context.drawImage(videoElement, 0, 0, canvas.width, canvas.height);
canvas.toBlob((blob) => {
const formData = new FormData();
formData.append('image', blob, 'code_image.jpg');
fetch('{% url 'car_search' %}', {
method: 'POST',
headers: { 'X-CSRFToken': '{% csrf_token %}' },
body: formData
})
.then(response => response.json())
.then(data => {
if (data.success) {
vinInput.value = data.code;
resultElement.textContent = `Code found: ${data.code}`;
} else {
resultElement.textContent = `Error: ${data.error}`;
}
})
.catch(err => {
console.error('Error processing code:', err);
resultElement.textContent = 'Error processing code.';
})
.finally(() => {
stopCamera();
});
});
});
// Open camera and start video stream
captureBtn.addEventListener('click', async () => {
cameraContainer.style.display = 'block';
await getCameras();
if (cameras.length > 0) {
await startCamera(cameras[currentCameraIndex].deviceId);
} else {
resultElement.textContent = 'No cameras found.';
}
});
// Stop the camera stream
function stopCamera() {
if (mediaStream) {
mediaStream.getTracks().forEach(track => track.stop());
mediaStream = null;
}
cameraContainer.style.display = 'none';
}
</script>

View File

@ -1,126 +1,82 @@
{% extends "base.html" %} {% extends "base.html" %}
{% load i18n %} {% load i18n %}
{% block title %}{% trans "Bank Accounts" %}{% endblock title %} {% block title %}{% trans "Bank Accounts" %}{% endblock title %}
{% block customers %} {% block bank_accounts %}
<a class="nav-link active fw-bold"> <a class="nav-link active fw-bold">
{% trans "Customers"|capfirst %} {% trans "Bank Accounts" %}
<span class="visually-hidden">(current)</span> <span class="visually-hidden">(current)</span>
</a> </a>
{% endblock %} {% endblock %}
{% block content %} {% block content %}
<div class="d-flex flex-column min-vh-100">
<div class="d-flex flex-column flex-sm-grow-1 ms-sm-14 p-4">
<main class="d-grid gap-4 p-1">
<!-- Search Bar -->
<div class="row g-4">
<div class="col-12">
<div class="row-fluid p-2">
<form method="get">
<div class="input-group input-group-sm">
<button class="btn btn-sm btn-secondary rounded-start" type="submit">
{% trans "search" %}
</button>
<input type="text"
name="q"
class="form-control form-control-sm rounded-end"
value="{{ request.GET.q }}"
placeholder="{% trans 'Search accounts...' %}" />
{% if request.GET.q %}
<a href="{% url request.resolver_match.view_name %}"
class="btn btn-sm btn-outline-danger ms-1 rounded">
<i class="bi bi-x-lg"></i>
</a>
{% endif %}
</div>
</form>
</div>
</div>
</div>
<!-- Customer Table --> <!-- Search Bar -->
<div class="row g-4"> <div class="row g-3">
<div class="col-12"> <div class="col-12">
<div class="card"> <form method="get">
<div class="card-header bg-primary text-white"> <div class="input-group input-group-sm">
<h5 class="mb-0">{% trans "Customers List" %}</h5> <button class="btn btn-sm btn-secondary rounded-start" type="submit">
</div> {% trans "search" %}
<div class="card-body p-0"> </button>
<table class="table table-hover table-sm mb-0"> <input type="text"
<thead class="table-light"> name="q"
<tr> class="form-control form-control-sm rounded-end"
<th>{% trans "Name" %}</th> value="{{ request.GET.q }}"
<th>{% trans "Type" %}</th> placeholder="{% trans 'Search accounts...' %}" />
<th class="text-center">{% trans "Actions" %}</th> {% if request.GET.q %}
</tr> <a href="{% url request.resolver_match.view_name %}"
</thead> class="btn btn-sm btn-outline-danger ms-1 rounded">
<tbody> <i class="bi bi-x-lg"></i>
{% for account in bank_accounts %} </a>
<tr> {% endif %}
<td>{{ account.name }}</td>
<td>{{ account.cash_account }}</td>
<td class="text-center">
<a href="{% url 'bank_account_detail' account.pk %}"
class="btn btn-sm btn-success">
{% trans "view" %}
</a>
</td>
</tr>
{% empty %}
<tr>
<td colspan="5" class="text-center text-muted">
{% trans "No customers found." %}
</td>
</tr>
{% endfor %}
</tbody>
</table>
</div>
<!-- Pagination -->
{% if is_paginated %}
<div class="card-footer bg-light">
<nav aria-label="Page navigation">
<ul class="pagination pagination-sm justify-content-center mb-0">
{% if page_obj.has_previous %}
<li class="page-item">
<a class="page-link" href="?page={{ page_obj.previous_page_number }}" aria-label="{% trans 'Previous' %}">
<span aria-hidden="true">&laquo;</span>
</a>
</li>
{% else %}
<li class="page-item disabled">
<span class="page-link" aria-hidden="true">&laquo;</span>
</li>
{% endif %}
{% for num in page_obj.paginator.page_range %}
{% if page_obj.number == num %}
<li class="page-item active">
<span class="page-link">{{ num }}</span>
</li>
{% else %}
<li class="page-item">
<a class="page-link" href="?page={{ num }}">{{ num }}</a>
</li>
{% endif %}
{% endfor %}
{% if page_obj.has_next %}
<li class="page-item">
<a class="page-link" href="?page={{ page_obj.next_page_number }}" aria-label="{% trans 'Next' %}">
<span aria-hidden="true">&raquo;</span>
</a>
</li>
{% else %}
<li class="page-item disabled">
<span class="page-link" aria-hidden="true">&raquo;</span>
</li>
{% endif %}
</ul>
</nav>
</div>
{% endif %}
</div>
</div> </div>
</form>
</div>
</div>
<!-- Bank Accounts Table -->
<div class="row g-3">
<div class="col-12">
<div class="card">
<div class="card-header bg-primary text-white">
<h5 class="mb-0">{% trans "Bank Accounts" %}</h5>
</div> </div>
</main> <div class="card-body p-0">
<table class="table table-hover table-sm mb-0">
<thead class="table-light">
<tr>
<th>{% trans "Name" %}</th>
<th>{% trans "Type" %}</th>
<th class="text-center">{% trans "Actions" %}</th>
</tr>
</thead>
<tbody>
{% for account in bank_accounts %}
<tr>
<td>{{ account.name }}</td>
<td>{{ account.cash_account }}</td>
<td class="text-center">
<a href="{% url 'bank_account_detail' account.pk %}"
class="btn btn-sm btn-success">
{% trans "view" %}
</a>
</td>
</tr>
{% empty %}
<tr>
<td colspan="5" class="text-center text-muted">
{% trans "No customers found." %}
</td>
</tr>
{% endfor %}
</tbody>
</table>
</div>
<!-- Pagination -->
{% if is_paginated %}
{% include 'partials/pagination.html' %}
{% endif %}
</div>
</div> </div>
</div> </div>
{% endblock %} {% endblock %}

View File

@ -0,0 +1,40 @@
{% load i18n static %}
<div class="card-footer bg-light">
<nav aria-label="Page navigation">
<ul class="pagination pagination-sm justify-content-center mb-0">
{% if page_obj.has_previous %}
<li class="page-item">
<a class="page-link" href="?page={{ page_obj.previous_page_number }}" aria-label="{% trans 'Previous' %}">
<span aria-hidden="true">&laquo;</span>
</a>
</li>
{% else %}
<li class="page-item disabled">
<span class="page-link" aria-hidden="true">&laquo;</span>
</li>
{% endif %}
{% for num in page_obj.paginator.page_range %}
{% if page_obj.number == num %}
<li class="page-item active">
<span class="page-link">{{ num }}</span>
</li>
{% else %}
<li class="page-item">
<a class="page-link" href="?page={{ num }}">{{ num }}</a>
</li>
{% endif %}
{% endfor %}
{% if page_obj.has_next %}
<li class="page-item">
<a class="page-link" href="?page={{ page_obj.next_page_number }}" aria-label="{% trans 'Next' %}">
<span aria-hidden="true">&raquo;</span>
</a>
</li>
{% else %}
<li class="page-item disabled">
<span class="page-link" aria-hidden="true">&raquo;</span>
</li>
{% endif %}
</ul>
</nav>
</div>