staff and lead changes
1
.gitignore
vendored
@ -11,6 +11,7 @@ dbtest.sqlite3
|
||||
db.sqlite3
|
||||
db.sqlite3.backup
|
||||
db.sqlite*
|
||||
*.sqlite3
|
||||
media
|
||||
car*.json
|
||||
car_inventory/settings.py
|
||||
|
||||
@ -34,5 +34,3 @@ urlpatterns += i18n_patterns(
|
||||
)
|
||||
|
||||
urlpatterns += static(settings.MEDIA_URL, document_root=settings.MEDIA_ROOT)
|
||||
|
||||
|
||||
|
||||
BIN
dbtest.sqlite3
@ -1,4 +1,4 @@
|
||||
# Generated by Django 5.1.7 on 2025-07-10 12:55
|
||||
# Generated by Django 5.2.4 on 2025-07-14 15:04
|
||||
|
||||
import django.db.models.deletion
|
||||
import django.utils.timezone
|
||||
|
||||
@ -1,4 +1,4 @@
|
||||
# Generated by Django 5.1.7 on 2025-07-10 12:55
|
||||
# Generated by Django 5.2.4 on 2025-07-14 15:04
|
||||
|
||||
import django.db.models.deletion
|
||||
from django.db import migrations, models
|
||||
|
||||
@ -144,7 +144,7 @@ class StaffForm(forms.ModelForm):
|
||||
)
|
||||
class Meta:
|
||||
model = Staff
|
||||
fields = ["name", "arabic_name", "phone_number", "address","image","group"]
|
||||
fields = ["name", "arabic_name", "phone_number", "address","logo","group"]
|
||||
|
||||
|
||||
# Dealer Form
|
||||
|
||||
@ -1,8 +0,0 @@
|
||||
from django.core.management.base import BaseCommand
|
||||
from inventory.models import VatRate
|
||||
from decimal import Decimal
|
||||
|
||||
|
||||
class Command(BaseCommand):
|
||||
def handle(self, *args, **kwargs):
|
||||
VatRate.objects.get_or_create(rate=Decimal("0.15"), is_active=True)
|
||||
@ -1,4 +1,4 @@
|
||||
# Generated by Django 5.1.7 on 2025-07-10 12:55
|
||||
# Generated by Django 5.2.4 on 2025-07-14 15:04
|
||||
|
||||
import datetime
|
||||
import django.core.serializers.json
|
||||
@ -19,10 +19,10 @@ class Migration(migrations.Migration):
|
||||
initial = True
|
||||
|
||||
dependencies = [
|
||||
('appointment', '__first__'),
|
||||
('appointment', '0001_initial'),
|
||||
('auth', '0012_alter_user_first_name_max_length'),
|
||||
('contenttypes', '0002_remove_content_type_name'),
|
||||
('django_ledger', '0021_alter_bankaccountmodel_account_model_and_more'),
|
||||
('django_ledger', '0022_alter_billmodel_bill_items_and_more'),
|
||||
migrations.swappable_dependency(settings.AUTH_USER_MODEL),
|
||||
]
|
||||
|
||||
@ -54,61 +54,21 @@ class Migration(migrations.Migration):
|
||||
],
|
||||
options={
|
||||
'verbose_name': 'Make',
|
||||
'indexes': [models.Index(fields=['name'], name='car_make_name_idx'), models.Index(fields=['is_sa_import'], name='car_make_sa_import_idx'), models.Index(fields=['car_type'], name='car_make_type_idx')],
|
||||
},
|
||||
bases=(models.Model, inventory.mixins.LocalizedNameMixin),
|
||||
),
|
||||
migrations.CreateModel(
|
||||
name='ExteriorColors',
|
||||
name='CarModel',
|
||||
fields=[
|
||||
('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
|
||||
('name', models.CharField(max_length=255, verbose_name='Name')),
|
||||
('arabic_name', models.CharField(max_length=255, verbose_name='Arabic Name')),
|
||||
('rgb', models.CharField(blank=True, max_length=24, null=True, verbose_name='RGB')),
|
||||
('id_car_model', models.AutoField(primary_key=True, serialize=False)),
|
||||
('name', models.CharField(blank=True, max_length=255, null=True)),
|
||||
('arabic_name', models.CharField(blank=True, max_length=255, null=True)),
|
||||
('slug', models.SlugField(blank=True, max_length=255, null=True, unique=True)),
|
||||
('id_car_make', models.ForeignKey(db_column='id_car_make', on_delete=django.db.models.deletion.DO_NOTHING, to='inventory.carmake')),
|
||||
],
|
||||
options={
|
||||
'verbose_name': 'Exterior Colors',
|
||||
'verbose_name_plural': 'Exterior Colors',
|
||||
},
|
||||
bases=(models.Model, inventory.mixins.LocalizedNameMixin),
|
||||
),
|
||||
migrations.CreateModel(
|
||||
name='InteriorColors',
|
||||
fields=[
|
||||
('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
|
||||
('name', models.CharField(max_length=255, verbose_name='Name')),
|
||||
('arabic_name', models.CharField(max_length=255, verbose_name='Arabic Name')),
|
||||
('rgb', models.CharField(blank=True, max_length=24, null=True, verbose_name='RGB')),
|
||||
],
|
||||
options={
|
||||
'verbose_name': 'Interior Colors',
|
||||
'verbose_name_plural': 'Interior Colors',
|
||||
},
|
||||
bases=(models.Model, inventory.mixins.LocalizedNameMixin),
|
||||
),
|
||||
migrations.CreateModel(
|
||||
name='VatRate',
|
||||
fields=[
|
||||
('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
|
||||
('rate', models.DecimalField(decimal_places=2, default=Decimal('0.15'), max_digits=5)),
|
||||
('is_active', models.BooleanField(default=True)),
|
||||
('created_at', models.DateTimeField(auto_now_add=True)),
|
||||
],
|
||||
),
|
||||
migrations.CreateModel(
|
||||
name='AdditionalServices',
|
||||
fields=[
|
||||
('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
|
||||
('name', models.CharField(max_length=255, verbose_name='Name')),
|
||||
('arabic_name', models.CharField(max_length=255, verbose_name='Arabic Name')),
|
||||
('description', models.TextField(verbose_name='Description')),
|
||||
('price', models.DecimalField(decimal_places=2, max_digits=14, verbose_name='Price')),
|
||||
('taxable', models.BooleanField(default=False, verbose_name='taxable')),
|
||||
('uom', models.CharField(choices=[('EA', 'Each'), ('PR', 'Pair'), ('SET', 'Set'), ('GAL', 'Gallon'), ('L', 'Liter'), ('M', 'Meter'), ('KG', 'Kilogram'), ('HR', 'Hour'), ('BX', 'Box'), ('RL', 'Roll'), ('PKG', 'Package'), ('DZ', 'Dozen'), ('SQ_M', 'Square Meter'), ('PC', 'Piece'), ('BDL', 'Bundle')], max_length=10, verbose_name='Unit of Measurement')),
|
||||
('item', models.OneToOneField(blank=True, null=True, on_delete=django.db.models.deletion.CASCADE, to='django_ledger.itemmodel', verbose_name='Item')),
|
||||
],
|
||||
options={
|
||||
'verbose_name': 'Additional Services',
|
||||
'verbose_name_plural': 'Additional Services',
|
||||
'verbose_name': 'Model',
|
||||
},
|
||||
bases=(models.Model, inventory.mixins.LocalizedNameMixin),
|
||||
),
|
||||
@ -129,47 +89,13 @@ class Migration(migrations.Migration):
|
||||
('hash', models.CharField(blank=True, max_length=64, null=True, verbose_name='Hash')),
|
||||
('item_model', models.OneToOneField(blank=True, null=True, on_delete=django.db.models.deletion.DO_NOTHING, to='django_ledger.itemmodel', verbose_name='Item Model')),
|
||||
('id_car_make', models.ForeignKey(blank=True, db_column='id_car_make', null=True, on_delete=django.db.models.deletion.DO_NOTHING, to='inventory.carmake', verbose_name='Make')),
|
||||
('id_car_model', models.ForeignKey(blank=True, db_column='id_car_model', null=True, on_delete=django.db.models.deletion.DO_NOTHING, to='inventory.carmodel', verbose_name='Model')),
|
||||
],
|
||||
options={
|
||||
'verbose_name': 'Car',
|
||||
'verbose_name_plural': 'Cars',
|
||||
},
|
||||
),
|
||||
migrations.CreateModel(
|
||||
name='CarFinance',
|
||||
fields=[
|
||||
('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
|
||||
('cost_price', models.DecimalField(decimal_places=2, max_digits=14, verbose_name='Cost Price')),
|
||||
('selling_price', models.DecimalField(decimal_places=2, max_digits=14, verbose_name='Selling Price')),
|
||||
('discount_amount', models.DecimalField(decimal_places=2, default=Decimal('0.00'), max_digits=14, verbose_name='Discount Amount')),
|
||||
('is_sold', models.BooleanField(default=False)),
|
||||
('additional_services', models.ManyToManyField(blank=True, related_name='additional_finances', to='inventory.additionalservices')),
|
||||
('car', models.OneToOneField(on_delete=django.db.models.deletion.CASCADE, related_name='finances', to='inventory.car')),
|
||||
],
|
||||
options={
|
||||
'verbose_name': 'Car Financial Details',
|
||||
'verbose_name_plural': 'Car Financial Details',
|
||||
},
|
||||
),
|
||||
migrations.CreateModel(
|
||||
name='CarModel',
|
||||
fields=[
|
||||
('id_car_model', models.AutoField(primary_key=True, serialize=False)),
|
||||
('name', models.CharField(blank=True, max_length=255, null=True)),
|
||||
('arabic_name', models.CharField(blank=True, max_length=255, null=True)),
|
||||
('slug', models.SlugField(blank=True, max_length=255, null=True, unique=True)),
|
||||
('id_car_make', models.ForeignKey(db_column='id_car_make', on_delete=django.db.models.deletion.DO_NOTHING, to='inventory.carmake')),
|
||||
],
|
||||
options={
|
||||
'verbose_name': 'Model',
|
||||
},
|
||||
bases=(models.Model, inventory.mixins.LocalizedNameMixin),
|
||||
),
|
||||
migrations.AddField(
|
||||
model_name='car',
|
||||
name='id_car_model',
|
||||
field=models.ForeignKey(blank=True, db_column='id_car_model', null=True, on_delete=django.db.models.deletion.DO_NOTHING, to='inventory.carmodel', verbose_name='Model'),
|
||||
),
|
||||
migrations.CreateModel(
|
||||
name='CarOption',
|
||||
fields=[
|
||||
@ -214,6 +140,21 @@ class Migration(migrations.Migration):
|
||||
'verbose_name_plural': 'Registrations',
|
||||
},
|
||||
),
|
||||
migrations.CreateModel(
|
||||
name='CarReservation',
|
||||
fields=[
|
||||
('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
|
||||
('reserved_at', models.DateTimeField(auto_now_add=True, verbose_name='Reserved At')),
|
||||
('reserved_until', models.DateTimeField(verbose_name='Reserved Until')),
|
||||
('car', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, related_name='reservations', to='inventory.car', verbose_name='Car')),
|
||||
('reserved_by', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, related_name='reservations', to=settings.AUTH_USER_MODEL, verbose_name='Reserved By')),
|
||||
],
|
||||
options={
|
||||
'verbose_name': 'Car Reservation',
|
||||
'verbose_name_plural': 'Car Reservations',
|
||||
'ordering': ['-reserved_at'],
|
||||
},
|
||||
),
|
||||
migrations.CreateModel(
|
||||
name='CarSerie',
|
||||
fields=[
|
||||
@ -336,6 +277,10 @@ class Migration(migrations.Migration):
|
||||
('group', models.OneToOneField(on_delete=django.db.models.deletion.CASCADE, to='auth.group', verbose_name='Group')),
|
||||
('dealer', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, related_name='groups', to='inventory.dealer')),
|
||||
],
|
||||
options={
|
||||
'verbose_name': 'Custom Group',
|
||||
'verbose_name_plural': 'Custom Groups',
|
||||
},
|
||||
),
|
||||
migrations.CreateModel(
|
||||
name='Customer',
|
||||
@ -406,10 +351,24 @@ class Migration(migrations.Migration):
|
||||
name='dealer',
|
||||
field=models.ForeignKey(on_delete=django.db.models.deletion.DO_NOTHING, related_name='cars', to='inventory.dealer', verbose_name='Dealer'),
|
||||
),
|
||||
migrations.AddField(
|
||||
model_name='additionalservices',
|
||||
name='dealer',
|
||||
field=models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to='inventory.dealer', verbose_name='Dealer'),
|
||||
migrations.CreateModel(
|
||||
name='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')),
|
||||
('dealer', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to='inventory.dealer', verbose_name='Dealer')),
|
||||
],
|
||||
options={
|
||||
'verbose_name': 'Additional Services',
|
||||
'verbose_name_plural': 'Additional Services',
|
||||
},
|
||||
bases=(models.Model, inventory.mixins.LocalizedNameMixin),
|
||||
),
|
||||
migrations.CreateModel(
|
||||
name='Activity',
|
||||
@ -443,6 +402,15 @@ class Migration(migrations.Migration):
|
||||
('invoice_unearned_account', models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.SET_NULL, related_name='invoice_unearned', to='django_ledger.accountmodel')),
|
||||
],
|
||||
),
|
||||
migrations.CreateModel(
|
||||
name='DealersMake',
|
||||
fields=[
|
||||
('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
|
||||
('added_at', models.DateTimeField(auto_now_add=True)),
|
||||
('car_make', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, related_name='car_dealers', to='inventory.carmake')),
|
||||
('dealer', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, related_name='dealer_makes', to='inventory.dealer')),
|
||||
],
|
||||
),
|
||||
migrations.CreateModel(
|
||||
name='Email',
|
||||
fields=[
|
||||
@ -463,6 +431,68 @@ class Migration(migrations.Migration):
|
||||
'verbose_name_plural': 'Emails',
|
||||
},
|
||||
),
|
||||
migrations.CreateModel(
|
||||
name='ExteriorColors',
|
||||
fields=[
|
||||
('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
|
||||
('name', models.CharField(max_length=255, verbose_name='Name')),
|
||||
('arabic_name', models.CharField(max_length=255, verbose_name='Arabic Name')),
|
||||
('rgb', models.CharField(blank=True, max_length=24, null=True, verbose_name='RGB')),
|
||||
],
|
||||
options={
|
||||
'verbose_name': 'Exterior Colors',
|
||||
'verbose_name_plural': 'Exterior Colors',
|
||||
'indexes': [models.Index(fields=['name'], name='exterior_color_name_idx'), models.Index(fields=['arabic_name'], name='exterior_color_arabic_name_idx')],
|
||||
},
|
||||
bases=(models.Model, inventory.mixins.LocalizedNameMixin),
|
||||
),
|
||||
migrations.CreateModel(
|
||||
name='ExtraInfo',
|
||||
fields=[
|
||||
('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
|
||||
('object_id', models.CharField(blank=True, max_length=255, null=True)),
|
||||
('related_object_id', models.CharField(blank=True, max_length=255, null=True)),
|
||||
('data', models.JSONField(blank=True, default=dict, encoder=django.core.serializers.json.DjangoJSONEncoder)),
|
||||
('created_at', models.DateTimeField(auto_now_add=True)),
|
||||
('updated_at', models.DateTimeField(auto_now=True)),
|
||||
('content_type', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, related_name='extra_info_primary', to='contenttypes.contenttype')),
|
||||
('created_by', models.ForeignKey(null=True, on_delete=django.db.models.deletion.SET_NULL, related_name='created_extra_info', to=settings.AUTH_USER_MODEL)),
|
||||
('dealer', models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.SET_NULL, related_name='extra_info', to='inventory.dealer')),
|
||||
('related_content_type', models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.SET_NULL, related_name='extra_info_secondary', to='contenttypes.contenttype')),
|
||||
],
|
||||
options={
|
||||
'verbose_name': 'Extra Info',
|
||||
'verbose_name_plural': 'Extra Info',
|
||||
},
|
||||
),
|
||||
migrations.CreateModel(
|
||||
name='InteriorColors',
|
||||
fields=[
|
||||
('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
|
||||
('name', models.CharField(max_length=255, verbose_name='Name')),
|
||||
('arabic_name', models.CharField(max_length=255, verbose_name='Arabic Name')),
|
||||
('rgb', models.CharField(blank=True, max_length=24, null=True, verbose_name='RGB')),
|
||||
],
|
||||
options={
|
||||
'verbose_name': 'Interior Colors',
|
||||
'verbose_name_plural': 'Interior Colors',
|
||||
'indexes': [models.Index(fields=['name'], name='interior_color_name_idx'), models.Index(fields=['arabic_name'], name='interior_color_arabic_name_idx')],
|
||||
},
|
||||
bases=(models.Model, inventory.mixins.LocalizedNameMixin),
|
||||
),
|
||||
migrations.CreateModel(
|
||||
name='CarColors',
|
||||
fields=[
|
||||
('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
|
||||
('car', models.OneToOneField(on_delete=django.db.models.deletion.CASCADE, related_name='colors', to='inventory.car')),
|
||||
('exterior', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, related_name='colors', to='inventory.exteriorcolors')),
|
||||
('interior', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, related_name='colors', to='inventory.interiorcolors')),
|
||||
],
|
||||
options={
|
||||
'verbose_name': 'Color',
|
||||
'verbose_name_plural': 'Colors',
|
||||
},
|
||||
),
|
||||
migrations.CreateModel(
|
||||
name='Lead',
|
||||
fields=[
|
||||
@ -602,6 +632,35 @@ class Migration(migrations.Migration):
|
||||
'verbose_name_plural': 'payments',
|
||||
},
|
||||
),
|
||||
migrations.CreateModel(
|
||||
name='PaymentHistory',
|
||||
fields=[
|
||||
('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
|
||||
('user_data', models.JSONField(blank=True, null=True)),
|
||||
('amount', models.DecimalField(decimal_places=2, max_digits=10, validators=[django.core.validators.MinValueValidator(0.01)])),
|
||||
('currency', models.CharField(default='SAR', max_length=3)),
|
||||
('payment_date', models.DateTimeField(default=django.utils.timezone.now)),
|
||||
('status', models.CharField(choices=[('initiated', 'initiated'), ('pending', 'Pending'), ('completed', 'Completed'), ('paid', 'Paid'), ('failed', 'Failed'), ('refunded', 'Refunded'), ('cancelled', 'Cancelled')], default='pending', max_length=10)),
|
||||
('payment_method', models.CharField(choices=[('credit_card', 'Credit Card'), ('debit_card', 'Debit Card'), ('paypal', 'PayPal'), ('bank_transfer', 'Bank Transfer'), ('crypto', 'Cryptocurrency'), ('other', 'Other')], max_length=20)),
|
||||
('transaction_id', models.CharField(blank=True, max_length=100, null=True, unique=True)),
|
||||
('invoice_number', models.CharField(blank=True, max_length=50, null=True)),
|
||||
('order_reference', models.CharField(blank=True, max_length=100, null=True)),
|
||||
('gateway_response', models.JSONField(blank=True, null=True)),
|
||||
('gateway_name', models.CharField(blank=True, max_length=50, null=True)),
|
||||
('description', models.TextField(blank=True, null=True)),
|
||||
('is_recurring', models.BooleanField(default=False)),
|
||||
('billing_email', models.EmailField(blank=True, max_length=254, null=True)),
|
||||
('billing_address', models.TextField(blank=True, null=True)),
|
||||
('created_at', models.DateTimeField(auto_now_add=True)),
|
||||
('updated_at', models.DateTimeField(auto_now=True)),
|
||||
('user', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, related_name='payments', to=settings.AUTH_USER_MODEL)),
|
||||
],
|
||||
options={
|
||||
'verbose_name': 'Payment History',
|
||||
'verbose_name_plural': 'Payment Histories',
|
||||
'ordering': ['-payment_date'],
|
||||
},
|
||||
),
|
||||
migrations.CreateModel(
|
||||
name='PoItemsUploaded',
|
||||
fields=[
|
||||
@ -613,6 +672,10 @@ class Migration(migrations.Migration):
|
||||
('item', models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.CASCADE, related_name='po_items', to='django_ledger.itemtransactionmodel')),
|
||||
('po', models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.CASCADE, related_name='items', to='django_ledger.purchaseordermodel')),
|
||||
],
|
||||
options={
|
||||
'verbose_name': 'PO Items',
|
||||
'verbose_name_plural': 'PO Items',
|
||||
},
|
||||
),
|
||||
migrations.CreateModel(
|
||||
name='Refund',
|
||||
@ -695,6 +758,8 @@ class Migration(migrations.Migration):
|
||||
('scheduled_by', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to=settings.AUTH_USER_MODEL)),
|
||||
],
|
||||
options={
|
||||
'verbose_name': 'Schedule',
|
||||
'verbose_name_plural': 'Schedules',
|
||||
'ordering': ['-scheduled_at'],
|
||||
},
|
||||
),
|
||||
@ -706,6 +771,8 @@ class Migration(migrations.Migration):
|
||||
('arabic_name', models.CharField(max_length=255, verbose_name='Arabic Name')),
|
||||
('phone_number', phonenumber_field.modelfields.PhoneNumberField(max_length=128, region='SA', verbose_name='Phone Number')),
|
||||
('staff_type', models.CharField(choices=[('inventory', 'Inventory'), ('accountant', 'Accountant'), ('sales', 'Sales')], max_length=255, verbose_name='Staff Type')),
|
||||
('address', models.CharField(blank=True, max_length=200, null=True, verbose_name='Address')),
|
||||
('image', models.ImageField(blank=True, null=True, upload_to='staff/', verbose_name='Image')),
|
||||
('active', models.BooleanField(default=True, verbose_name='Active')),
|
||||
('created', models.DateTimeField(auto_now_add=True, verbose_name='Created')),
|
||||
('updated', models.DateTimeField(auto_now=True, verbose_name='Updated')),
|
||||
@ -783,6 +850,16 @@ class Migration(migrations.Migration):
|
||||
'ordering': ['-timestamp'],
|
||||
},
|
||||
),
|
||||
migrations.CreateModel(
|
||||
name='VatRate',
|
||||
fields=[
|
||||
('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
|
||||
('rate', models.DecimalField(decimal_places=2, default=Decimal('0.15'), max_digits=5)),
|
||||
('is_active', models.BooleanField(default=True)),
|
||||
('created_at', models.DateTimeField(auto_now_add=True)),
|
||||
('dealer', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to='inventory.dealer')),
|
||||
],
|
||||
),
|
||||
migrations.CreateModel(
|
||||
name='Vendor',
|
||||
fields=[
|
||||
@ -814,93 +891,545 @@ class Migration(migrations.Migration):
|
||||
field=models.ForeignKey(on_delete=django.db.models.deletion.DO_NOTHING, related_name='cars', to='inventory.vendor', verbose_name='Vendor'),
|
||||
),
|
||||
migrations.CreateModel(
|
||||
name='CarReservation',
|
||||
name='CarFinance',
|
||||
fields=[
|
||||
('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
|
||||
('reserved_at', models.DateTimeField(auto_now_add=True, verbose_name='Reserved At')),
|
||||
('reserved_until', models.DateTimeField(verbose_name='Reserved Until')),
|
||||
('car', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, related_name='reservations', to='inventory.car', verbose_name='Car')),
|
||||
('reserved_by', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, related_name='reservations', to=settings.AUTH_USER_MODEL, verbose_name='Reserved By')),
|
||||
('cost_price', models.DecimalField(decimal_places=2, max_digits=14, verbose_name='Cost Price')),
|
||||
('selling_price', models.DecimalField(decimal_places=2, default=Decimal('0.00'), max_digits=14, verbose_name='Selling Price')),
|
||||
('marked_price', models.DecimalField(decimal_places=2, default=Decimal('0.00'), max_digits=14, verbose_name='Marked Price')),
|
||||
('discount_amount', models.DecimalField(decimal_places=2, default=Decimal('0.00'), max_digits=14, verbose_name='Discount Amount')),
|
||||
('is_sold', models.BooleanField(default=False)),
|
||||
('additional_services', models.ManyToManyField(blank=True, related_name='additional_finances', to='inventory.additionalservices')),
|
||||
('car', models.OneToOneField(on_delete=django.db.models.deletion.CASCADE, related_name='finances', to='inventory.car')),
|
||||
],
|
||||
options={
|
||||
'verbose_name': 'Car Reservation',
|
||||
'verbose_name_plural': 'Car Reservations',
|
||||
'ordering': ['-reserved_at'],
|
||||
'unique_together': {('car', 'reserved_until')},
|
||||
'verbose_name': 'Car Financial Details',
|
||||
'verbose_name_plural': 'Car Financial Details',
|
||||
'indexes': [models.Index(fields=['car'], name='car_finance_car_idx'), models.Index(fields=['cost_price'], name='car_finance_cost_price_idx'), models.Index(fields=['selling_price'], name='car_finance_selling_price_idx'), models.Index(fields=['discount_amount'], name='car_finance_discount_idx')],
|
||||
},
|
||||
),
|
||||
migrations.CreateModel(
|
||||
name='DealersMake',
|
||||
fields=[
|
||||
('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
|
||||
('added_at', models.DateTimeField(auto_now_add=True)),
|
||||
('car_make', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, related_name='car_dealers', to='inventory.carmake')),
|
||||
('dealer', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, related_name='dealer_makes', to='inventory.dealer')),
|
||||
],
|
||||
options={
|
||||
'unique_together': {('dealer', 'car_make')},
|
||||
},
|
||||
migrations.AddIndex(
|
||||
model_name='carmodel',
|
||||
index=models.Index(fields=['id_car_make'], name='car_model_make_idx'),
|
||||
),
|
||||
migrations.CreateModel(
|
||||
name='ExtraInfo',
|
||||
fields=[
|
||||
('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
|
||||
('object_id', models.CharField(blank=True, max_length=255, null=True)),
|
||||
('related_object_id', models.CharField(blank=True, max_length=255, null=True)),
|
||||
('data', models.JSONField(blank=True, default=dict, encoder=django.core.serializers.json.DjangoJSONEncoder)),
|
||||
('created_at', models.DateTimeField(auto_now_add=True)),
|
||||
('updated_at', models.DateTimeField(auto_now=True)),
|
||||
('content_type', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, related_name='extra_info_primary', to='contenttypes.contenttype')),
|
||||
('created_by', models.ForeignKey(null=True, on_delete=django.db.models.deletion.SET_NULL, related_name='created_extra_info', to=settings.AUTH_USER_MODEL)),
|
||||
('related_content_type', models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.SET_NULL, related_name='extra_info_secondary', to='contenttypes.contenttype')),
|
||||
],
|
||||
options={
|
||||
'verbose_name_plural': 'Extra Info',
|
||||
'indexes': [models.Index(fields=['content_type', 'object_id'], name='inventory_e_content_2ecbed_idx'), models.Index(fields=['related_content_type', 'related_object_id'], name='inventory_e_related_8680bb_idx')],
|
||||
},
|
||||
migrations.AddIndex(
|
||||
model_name='carmodel',
|
||||
index=models.Index(fields=['name'], name='car_model_name_idx'),
|
||||
),
|
||||
migrations.CreateModel(
|
||||
name='CarColors',
|
||||
fields=[
|
||||
('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
|
||||
('car', models.OneToOneField(on_delete=django.db.models.deletion.CASCADE, related_name='colors', to='inventory.car')),
|
||||
('exterior', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, related_name='colors', to='inventory.exteriorcolors')),
|
||||
('interior', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, related_name='colors', to='inventory.interiorcolors')),
|
||||
],
|
||||
options={
|
||||
'verbose_name': 'Color',
|
||||
'verbose_name_plural': 'Colors',
|
||||
'unique_together': {('car', 'exterior', 'interior')},
|
||||
},
|
||||
migrations.AddIndex(
|
||||
model_name='carmodel',
|
||||
index=models.Index(fields=['id_car_make', 'name'], name='car_model_make_name_idx'),
|
||||
),
|
||||
migrations.CreateModel(
|
||||
name='PaymentHistory',
|
||||
fields=[
|
||||
('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
|
||||
('user_data', models.JSONField(blank=True, null=True)),
|
||||
('amount', models.DecimalField(decimal_places=2, max_digits=10, validators=[django.core.validators.MinValueValidator(0.01)])),
|
||||
('currency', models.CharField(default='SAR', max_length=3)),
|
||||
('payment_date', models.DateTimeField(default=django.utils.timezone.now)),
|
||||
('status', models.CharField(choices=[('initiated', 'initiated'), ('pending', 'Pending'), ('completed', 'Completed'), ('paid', 'Paid'), ('failed', 'Failed'), ('refunded', 'Refunded'), ('cancelled', 'Cancelled')], default='pending', max_length=10)),
|
||||
('payment_method', models.CharField(choices=[('credit_card', 'Credit Card'), ('debit_card', 'Debit Card'), ('paypal', 'PayPal'), ('bank_transfer', 'Bank Transfer'), ('crypto', 'Cryptocurrency'), ('other', 'Other')], max_length=20)),
|
||||
('transaction_id', models.CharField(blank=True, max_length=100, null=True, unique=True)),
|
||||
('invoice_number', models.CharField(blank=True, max_length=50, null=True)),
|
||||
('order_reference', models.CharField(blank=True, max_length=100, null=True)),
|
||||
('gateway_response', models.JSONField(blank=True, null=True)),
|
||||
('gateway_name', models.CharField(blank=True, max_length=50, null=True)),
|
||||
('description', models.TextField(blank=True, null=True)),
|
||||
('is_recurring', models.BooleanField(default=False)),
|
||||
('billing_email', models.EmailField(blank=True, max_length=254, null=True)),
|
||||
('billing_address', models.TextField(blank=True, null=True)),
|
||||
('created_at', models.DateTimeField(auto_now_add=True)),
|
||||
('updated_at', models.DateTimeField(auto_now=True)),
|
||||
('user', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, related_name='payments', to=settings.AUTH_USER_MODEL)),
|
||||
],
|
||||
options={
|
||||
'verbose_name': 'Payment History',
|
||||
'verbose_name_plural': 'Payment Histories',
|
||||
'ordering': ['-payment_date'],
|
||||
'indexes': [models.Index(fields=['transaction_id'], name='inventory_p_transac_9469f3_idx'), models.Index(fields=['user'], name='inventory_p_user_id_c31626_idx'), models.Index(fields=['status'], name='inventory_p_status_abcb77_idx'), models.Index(fields=['payment_date'], name='inventory_p_payment_b3068c_idx')],
|
||||
},
|
||||
migrations.AddIndex(
|
||||
model_name='caroption',
|
||||
index=models.Index(fields=['id_parent'], name='car_option_parent_idx'),
|
||||
),
|
||||
migrations.AddIndex(
|
||||
model_name='caroption',
|
||||
index=models.Index(fields=['name'], name='car_option_name_idx'),
|
||||
),
|
||||
migrations.AddIndex(
|
||||
model_name='caroptionvalue',
|
||||
index=models.Index(fields=['id_car_option'], name='car_opt_val_option_idx'),
|
||||
),
|
||||
migrations.AddIndex(
|
||||
model_name='caroptionvalue',
|
||||
index=models.Index(fields=['id_car_equipment'], name='car_opt_val_equipment_idx'),
|
||||
),
|
||||
migrations.AddIndex(
|
||||
model_name='caroptionvalue',
|
||||
index=models.Index(fields=['is_base'], name='car_opt_val_is_base_idx'),
|
||||
),
|
||||
migrations.AddIndex(
|
||||
model_name='caroptionvalue',
|
||||
index=models.Index(fields=['id_car_option', 'id_car_equipment'], name='cov_option_equipment_idx'),
|
||||
),
|
||||
migrations.AlterUniqueTogether(
|
||||
name='carreservation',
|
||||
unique_together={('car', 'reserved_until')},
|
||||
),
|
||||
migrations.AddIndex(
|
||||
model_name='carserie',
|
||||
index=models.Index(fields=['id_car_model'], name='car_serie_model_idx'),
|
||||
),
|
||||
migrations.AddIndex(
|
||||
model_name='carserie',
|
||||
index=models.Index(fields=['year_begin', 'year_end'], name='car_serie_years_idx'),
|
||||
),
|
||||
migrations.AddIndex(
|
||||
model_name='carserie',
|
||||
index=models.Index(fields=['name'], name='car_serie_name_idx'),
|
||||
),
|
||||
migrations.AddIndex(
|
||||
model_name='carserie',
|
||||
index=models.Index(fields=['generation_name'], name='car_serie_generation_idx'),
|
||||
),
|
||||
migrations.AddIndex(
|
||||
model_name='carspecification',
|
||||
index=models.Index(fields=['id_parent'], name='car_spec_parent_idx'),
|
||||
),
|
||||
migrations.AddIndex(
|
||||
model_name='carspecification',
|
||||
index=models.Index(fields=['name'], name='car_spec_name_idx'),
|
||||
),
|
||||
migrations.AddIndex(
|
||||
model_name='cartrim',
|
||||
index=models.Index(fields=['id_car_serie'], name='car_trim_serie_idx'),
|
||||
),
|
||||
migrations.AddIndex(
|
||||
model_name='cartrim',
|
||||
index=models.Index(fields=['start_production_year', 'end_production_year'], name='car_trim_prod_years_idx'),
|
||||
),
|
||||
migrations.AddIndex(
|
||||
model_name='cartrim',
|
||||
index=models.Index(fields=['name'], name='car_trim_name_idx'),
|
||||
),
|
||||
migrations.AddIndex(
|
||||
model_name='carspecificationvalue',
|
||||
index=models.Index(fields=['id_car_trim'], name='car_spec_val_trim_idx'),
|
||||
),
|
||||
migrations.AddIndex(
|
||||
model_name='carspecificationvalue',
|
||||
index=models.Index(fields=['id_car_specification'], name='car_spec_val_spec_idx'),
|
||||
),
|
||||
migrations.AddIndex(
|
||||
model_name='carspecificationvalue',
|
||||
index=models.Index(fields=['id_car_trim', 'id_car_specification'], name='car_spec_val_trim_spec_idx'),
|
||||
),
|
||||
migrations.AddIndex(
|
||||
model_name='carequipment',
|
||||
index=models.Index(fields=['id_car_trim'], name='car_equipment_trim_idx'),
|
||||
),
|
||||
migrations.AddIndex(
|
||||
model_name='carequipment',
|
||||
index=models.Index(fields=['year_begin'], name='car_equipment_year_idx'),
|
||||
),
|
||||
migrations.AddIndex(
|
||||
model_name='carequipment',
|
||||
index=models.Index(fields=['name'], name='car_equipment_name_idx'),
|
||||
),
|
||||
migrations.AddIndex(
|
||||
model_name='dealer',
|
||||
index=models.Index(fields=['name'], name='inventory_d_name_c4ba31_idx'),
|
||||
),
|
||||
migrations.AddIndex(
|
||||
model_name='customgroup',
|
||||
index=models.Index(fields=['name'], name='inventory_c_name_65f272_idx'),
|
||||
),
|
||||
migrations.AddIndex(
|
||||
model_name='customgroup',
|
||||
index=models.Index(fields=['dealer'], name='inventory_c_dealer__313a5a_idx'),
|
||||
),
|
||||
migrations.AddIndex(
|
||||
model_name='customgroup',
|
||||
index=models.Index(fields=['group'], name='inventory_c_group_i_5f1074_idx'),
|
||||
),
|
||||
migrations.AddIndex(
|
||||
model_name='customer',
|
||||
index=models.Index(fields=['title'], name='inventory_c_title_f01e78_idx'),
|
||||
),
|
||||
migrations.AddIndex(
|
||||
model_name='customer',
|
||||
index=models.Index(fields=['first_name'], name='inventory_c_first_n_fd5078_idx'),
|
||||
),
|
||||
migrations.AddIndex(
|
||||
model_name='customer',
|
||||
index=models.Index(fields=['last_name'], name='inventory_c_last_na_4bf5bd_idx'),
|
||||
),
|
||||
migrations.AddIndex(
|
||||
model_name='customer',
|
||||
index=models.Index(fields=['email'], name='inventory_c_email_c8fd29_idx'),
|
||||
),
|
||||
migrations.AddIndex(
|
||||
model_name='customer',
|
||||
index=models.Index(fields=['phone_number'], name='inventory_c_phone_n_1a4571_idx'),
|
||||
),
|
||||
migrations.AddIndex(
|
||||
model_name='activity',
|
||||
index=models.Index(fields=['created_by'], name='activity_created_by_idx'),
|
||||
),
|
||||
migrations.AddIndex(
|
||||
model_name='activity',
|
||||
index=models.Index(fields=['content_type'], name='activity_content_type_idx'),
|
||||
),
|
||||
migrations.AddIndex(
|
||||
model_name='activity',
|
||||
index=models.Index(fields=['content_type', 'object_id'], name='activity_content_object_idx'),
|
||||
),
|
||||
migrations.AddIndex(
|
||||
model_name='activity',
|
||||
index=models.Index(fields=['created'], name='activity_created_date_idx'),
|
||||
),
|
||||
migrations.AddIndex(
|
||||
model_name='activity',
|
||||
index=models.Index(fields=['updated'], name='activity_updated_date_idx'),
|
||||
),
|
||||
migrations.AddIndex(
|
||||
model_name='activity',
|
||||
index=models.Index(fields=['content_type', 'object_id', 'created'], name='a_content_obj_created_idx'),
|
||||
),
|
||||
migrations.AlterUniqueTogether(
|
||||
name='dealersmake',
|
||||
unique_together={('dealer', 'car_make')},
|
||||
),
|
||||
migrations.AddIndex(
|
||||
model_name='email',
|
||||
index=models.Index(fields=['created_by'], name='email_created_by_idx'),
|
||||
),
|
||||
migrations.AddIndex(
|
||||
model_name='email',
|
||||
index=models.Index(fields=['content_type'], name='email_content_type_idx'),
|
||||
),
|
||||
migrations.AddIndex(
|
||||
model_name='email',
|
||||
index=models.Index(fields=['content_type', 'object_id'], name='email_content_object_idx'),
|
||||
),
|
||||
migrations.AddIndex(
|
||||
model_name='email',
|
||||
index=models.Index(fields=['created'], name='email_created_date_idx'),
|
||||
),
|
||||
migrations.AddIndex(
|
||||
model_name='email',
|
||||
index=models.Index(fields=['updated'], name='email_updated_date_idx'),
|
||||
),
|
||||
migrations.AddIndex(
|
||||
model_name='email',
|
||||
index=models.Index(fields=['content_type', 'object_id', 'created'], name='email_content_obj_created_idx'),
|
||||
),
|
||||
migrations.AddIndex(
|
||||
model_name='extrainfo',
|
||||
index=models.Index(fields=['content_type', 'object_id'], name='inventory_e_content_2ecbed_idx'),
|
||||
),
|
||||
migrations.AddIndex(
|
||||
model_name='extrainfo',
|
||||
index=models.Index(fields=['related_content_type', 'related_object_id'], name='inventory_e_related_8680bb_idx'),
|
||||
),
|
||||
migrations.AddIndex(
|
||||
model_name='carcolors',
|
||||
index=models.Index(fields=['exterior'], name='car_colors_exterior_idx'),
|
||||
),
|
||||
migrations.AddIndex(
|
||||
model_name='carcolors',
|
||||
index=models.Index(fields=['interior'], name='car_colors_interior_idx'),
|
||||
),
|
||||
migrations.AddIndex(
|
||||
model_name='carcolors',
|
||||
index=models.Index(fields=['exterior', 'interior'], name='car_colors_ext_int_combo_idx'),
|
||||
),
|
||||
migrations.AlterUniqueTogether(
|
||||
name='carcolors',
|
||||
unique_together={('car', 'exterior', 'interior')},
|
||||
),
|
||||
migrations.AddIndex(
|
||||
model_name='notes',
|
||||
index=models.Index(fields=['dealer'], name='note_dealer_idx'),
|
||||
),
|
||||
migrations.AddIndex(
|
||||
model_name='notes',
|
||||
index=models.Index(fields=['created_by'], name='note_created_by_idx'),
|
||||
),
|
||||
migrations.AddIndex(
|
||||
model_name='notes',
|
||||
index=models.Index(fields=['content_type'], name='note_content_type_idx'),
|
||||
),
|
||||
migrations.AddIndex(
|
||||
model_name='notes',
|
||||
index=models.Index(fields=['content_type', 'object_id'], name='note_content_object_idx'),
|
||||
),
|
||||
migrations.AddIndex(
|
||||
model_name='notes',
|
||||
index=models.Index(fields=['created'], name='note_created_date_idx'),
|
||||
),
|
||||
migrations.AddIndex(
|
||||
model_name='notes',
|
||||
index=models.Index(fields=['updated'], name='note_updated_date_idx'),
|
||||
),
|
||||
migrations.AddIndex(
|
||||
model_name='notes',
|
||||
index=models.Index(fields=['dealer', 'created'], name='note_dealer_created_idx'),
|
||||
),
|
||||
migrations.AddIndex(
|
||||
model_name='notes',
|
||||
index=models.Index(fields=['content_type', 'object_id', 'created'], name='note_content_obj_created_idx'),
|
||||
),
|
||||
migrations.AddIndex(
|
||||
model_name='notification',
|
||||
index=models.Index(fields=['user'], name='notification_user_idx'),
|
||||
),
|
||||
migrations.AddIndex(
|
||||
model_name='notification',
|
||||
index=models.Index(fields=['is_read'], name='notification_is_read_idx'),
|
||||
),
|
||||
migrations.AddIndex(
|
||||
model_name='notification',
|
||||
index=models.Index(fields=['created'], name='notification_created_date_idx'),
|
||||
),
|
||||
migrations.AddIndex(
|
||||
model_name='organization',
|
||||
index=models.Index(fields=['name'], name='inventory_o_name_cc18e2_idx'),
|
||||
),
|
||||
migrations.AddIndex(
|
||||
model_name='organization',
|
||||
index=models.Index(fields=['email'], name='inventory_o_email_d6e7dd_idx'),
|
||||
),
|
||||
migrations.AddIndex(
|
||||
model_name='organization',
|
||||
index=models.Index(fields=['phone_number'], name='inventory_o_phone_n_7cb3d4_idx'),
|
||||
),
|
||||
migrations.AddIndex(
|
||||
model_name='paymenthistory',
|
||||
index=models.Index(fields=['transaction_id'], name='inventory_p_transac_9469f3_idx'),
|
||||
),
|
||||
migrations.AddIndex(
|
||||
model_name='paymenthistory',
|
||||
index=models.Index(fields=['user'], name='inventory_p_user_id_c31626_idx'),
|
||||
),
|
||||
migrations.AddIndex(
|
||||
model_name='paymenthistory',
|
||||
index=models.Index(fields=['status'], name='inventory_p_status_abcb77_idx'),
|
||||
),
|
||||
migrations.AddIndex(
|
||||
model_name='paymenthistory',
|
||||
index=models.Index(fields=['payment_date'], name='inventory_p_payment_b3068c_idx'),
|
||||
),
|
||||
migrations.AddIndex(
|
||||
model_name='poitemsuploaded',
|
||||
index=models.Index(fields=['po'], name='inventory_p_po_id_762198_idx'),
|
||||
),
|
||||
migrations.AddIndex(
|
||||
model_name='poitemsuploaded',
|
||||
index=models.Index(fields=['item'], name='inventory_p_item_id_6dae83_idx'),
|
||||
),
|
||||
migrations.AddIndex(
|
||||
model_name='saleorder',
|
||||
index=models.Index(fields=['dealer'], name='inventory_s_dealer__9eeebf_idx'),
|
||||
),
|
||||
migrations.AddIndex(
|
||||
model_name='saleorder',
|
||||
index=models.Index(fields=['estimate'], name='inventory_s_estimat_82cc8d_idx'),
|
||||
),
|
||||
migrations.AddIndex(
|
||||
model_name='saleorder',
|
||||
index=models.Index(fields=['invoice'], name='inventory_s_invoice_972b40_idx'),
|
||||
),
|
||||
migrations.AddIndex(
|
||||
model_name='saleorder',
|
||||
index=models.Index(fields=['opportunity'], name='inventory_s_opportu_e6990d_idx'),
|
||||
),
|
||||
migrations.AddIndex(
|
||||
model_name='saleorder',
|
||||
index=models.Index(fields=['customer'], name='inventory_s_custome_d75f5f_idx'),
|
||||
),
|
||||
migrations.AddIndex(
|
||||
model_name='saleorder',
|
||||
index=models.Index(fields=['status'], name='inventory_s_status_34054d_idx'),
|
||||
),
|
||||
migrations.AddIndex(
|
||||
model_name='saleorder',
|
||||
index=models.Index(fields=['order_date'], name='inventory_s_order_d_24f877_idx'),
|
||||
),
|
||||
migrations.AddIndex(
|
||||
model_name='saleorder',
|
||||
index=models.Index(fields=['expected_delivery_date'], name='inventory_s_expecte_6facc1_idx'),
|
||||
),
|
||||
migrations.AddIndex(
|
||||
model_name='saleorder',
|
||||
index=models.Index(fields=['actual_delivery_date'], name='inventory_s_actual__bb0b80_idx'),
|
||||
),
|
||||
migrations.AddIndex(
|
||||
model_name='saleorder',
|
||||
index=models.Index(fields=['cancelled_date'], name='inventory_s_cancell_cf8528_idx'),
|
||||
),
|
||||
migrations.AddIndex(
|
||||
model_name='schedule',
|
||||
index=models.Index(fields=['dealer'], name='inventory_s_dealer__879e62_idx'),
|
||||
),
|
||||
migrations.AddIndex(
|
||||
model_name='schedule',
|
||||
index=models.Index(fields=['customer'], name='inventory_s_custome_19a62e_idx'),
|
||||
),
|
||||
migrations.AddIndex(
|
||||
model_name='schedule',
|
||||
index=models.Index(fields=['content_type', 'object_id'], name='inventory_s_content_d3912b_idx'),
|
||||
),
|
||||
migrations.AddIndex(
|
||||
model_name='schedule',
|
||||
index=models.Index(fields=['scheduled_at'], name='inventory_s_schedul_04d173_idx'),
|
||||
),
|
||||
migrations.AddIndex(
|
||||
model_name='staff',
|
||||
index=models.Index(fields=['name'], name='inventory_s_name_da615c_idx'),
|
||||
),
|
||||
migrations.AddIndex(
|
||||
model_name='staff',
|
||||
index=models.Index(fields=['staff_type'], name='inventory_s_staff_t_680ea0_idx'),
|
||||
),
|
||||
migrations.AddIndex(
|
||||
model_name='opportunity',
|
||||
index=models.Index(fields=['dealer'], name='inventory_o_dealer__74a272_idx'),
|
||||
),
|
||||
migrations.AddIndex(
|
||||
model_name='opportunity',
|
||||
index=models.Index(fields=['customer'], name='inventory_o_custome_dffe96_idx'),
|
||||
),
|
||||
migrations.AddIndex(
|
||||
model_name='opportunity',
|
||||
index=models.Index(fields=['car'], name='inventory_o_car_id_9b4f5f_idx'),
|
||||
),
|
||||
migrations.AddIndex(
|
||||
model_name='opportunity',
|
||||
index=models.Index(fields=['lead'], name='inventory_o_lead_id_7b2f47_idx'),
|
||||
),
|
||||
migrations.AddIndex(
|
||||
model_name='opportunity',
|
||||
index=models.Index(fields=['organization'], name='inventory_o_organiz_82d31b_idx'),
|
||||
),
|
||||
migrations.AddIndex(
|
||||
model_name='opportunity',
|
||||
index=models.Index(fields=['created'], name='inventory_o_created_124e5d_idx'),
|
||||
),
|
||||
migrations.AddIndex(
|
||||
model_name='lead',
|
||||
index=models.Index(fields=['dealer'], name='inventory_l_dealer__ce2dfd_idx'),
|
||||
),
|
||||
migrations.AddIndex(
|
||||
model_name='lead',
|
||||
index=models.Index(fields=['customer'], name='inventory_l_custome_b7dfe3_idx'),
|
||||
),
|
||||
migrations.AddIndex(
|
||||
model_name='lead',
|
||||
index=models.Index(fields=['organization'], name='inventory_l_organiz_990f08_idx'),
|
||||
),
|
||||
migrations.AddIndex(
|
||||
model_name='lead',
|
||||
index=models.Index(fields=['staff'], name='inventory_l_staff_i_01ba72_idx'),
|
||||
),
|
||||
migrations.AddIndex(
|
||||
model_name='lead',
|
||||
index=models.Index(fields=['first_name'], name='inventory_l_first_n_77bb1b_idx'),
|
||||
),
|
||||
migrations.AddIndex(
|
||||
model_name='lead',
|
||||
index=models.Index(fields=['last_name'], name='inventory_l_last_na_730f4a_idx'),
|
||||
),
|
||||
migrations.AddIndex(
|
||||
model_name='lead',
|
||||
index=models.Index(fields=['email'], name='inventory_l_email_e502f1_idx'),
|
||||
),
|
||||
migrations.AddIndex(
|
||||
model_name='lead',
|
||||
index=models.Index(fields=['phone_number'], name='inventory_l_phone_n_d9dab8_idx'),
|
||||
),
|
||||
migrations.AddIndex(
|
||||
model_name='lead',
|
||||
index=models.Index(fields=['created'], name='inventory_l_created_24d483_idx'),
|
||||
),
|
||||
migrations.AddIndex(
|
||||
model_name='tasks',
|
||||
index=models.Index(fields=['dealer'], name='task_dealer_idx'),
|
||||
),
|
||||
migrations.AddIndex(
|
||||
model_name='tasks',
|
||||
index=models.Index(fields=['created_by'], name='task_created_by_idx'),
|
||||
),
|
||||
migrations.AddIndex(
|
||||
model_name='tasks',
|
||||
index=models.Index(fields=['content_type'], name='task_content_type_idx'),
|
||||
),
|
||||
migrations.AddIndex(
|
||||
model_name='tasks',
|
||||
index=models.Index(fields=['content_type', 'object_id'], name='task_content_object_idx'),
|
||||
),
|
||||
migrations.AddIndex(
|
||||
model_name='tasks',
|
||||
index=models.Index(fields=['created'], name='task_created_date_idx'),
|
||||
),
|
||||
migrations.AddIndex(
|
||||
model_name='tasks',
|
||||
index=models.Index(fields=['updated'], name='task_updated_date_idx'),
|
||||
),
|
||||
migrations.AddIndex(
|
||||
model_name='tasks',
|
||||
index=models.Index(fields=['dealer', 'created'], name='task_dealer_created_idx'),
|
||||
),
|
||||
migrations.AddIndex(
|
||||
model_name='tasks',
|
||||
index=models.Index(fields=['content_type', 'object_id', 'created'], name='task_content_obj_created_idx'),
|
||||
),
|
||||
migrations.AddIndex(
|
||||
model_name='vendor',
|
||||
index=models.Index(fields=['slug'], name='vendor_slug_idx'),
|
||||
),
|
||||
migrations.AddIndex(
|
||||
model_name='vendor',
|
||||
index=models.Index(fields=['active'], name='vendor_active_idx'),
|
||||
),
|
||||
migrations.AddIndex(
|
||||
model_name='vendor',
|
||||
index=models.Index(fields=['crn'], name='vendor_crn_idx'),
|
||||
),
|
||||
migrations.AddIndex(
|
||||
model_name='vendor',
|
||||
index=models.Index(fields=['vrn'], name='vendor_vrn_idx'),
|
||||
),
|
||||
migrations.AddIndex(
|
||||
model_name='car',
|
||||
index=models.Index(fields=['vin'], name='car_vin_idx'),
|
||||
),
|
||||
migrations.AddIndex(
|
||||
model_name='car',
|
||||
index=models.Index(fields=['year'], name='car_year_idx'),
|
||||
),
|
||||
migrations.AddIndex(
|
||||
model_name='car',
|
||||
index=models.Index(fields=['status'], name='car_status_idx'),
|
||||
),
|
||||
migrations.AddIndex(
|
||||
model_name='car',
|
||||
index=models.Index(fields=['dealer'], name='car_dealer_idx'),
|
||||
),
|
||||
migrations.AddIndex(
|
||||
model_name='car',
|
||||
index=models.Index(fields=['vendor'], name='car_vendor_idx'),
|
||||
),
|
||||
migrations.AddIndex(
|
||||
model_name='car',
|
||||
index=models.Index(fields=['id_car_make'], name='car_make_idx'),
|
||||
),
|
||||
migrations.AddIndex(
|
||||
model_name='car',
|
||||
index=models.Index(fields=['id_car_model'], name='car_model_idx'),
|
||||
),
|
||||
migrations.AddIndex(
|
||||
model_name='car',
|
||||
index=models.Index(fields=['id_car_serie'], name='car_serie_idx'),
|
||||
),
|
||||
migrations.AddIndex(
|
||||
model_name='car',
|
||||
index=models.Index(fields=['id_car_trim'], name='car_trim_idx'),
|
||||
),
|
||||
migrations.AddIndex(
|
||||
model_name='car',
|
||||
index=models.Index(fields=['id_car_make', 'id_car_model'], name='car_make_model_idx'),
|
||||
),
|
||||
migrations.AddIndex(
|
||||
model_name='car',
|
||||
index=models.Index(fields=['id_car_make', 'year'], name='car_make_year_idx'),
|
||||
),
|
||||
migrations.AddIndex(
|
||||
model_name='car',
|
||||
index=models.Index(fields=['dealer', 'status'], name='car_dealer_status_idx'),
|
||||
),
|
||||
migrations.AddIndex(
|
||||
model_name='car',
|
||||
index=models.Index(fields=['vendor', 'status'], name='car_vendor_status_idx'),
|
||||
),
|
||||
migrations.AddIndex(
|
||||
model_name='car',
|
||||
index=models.Index(fields=['year', 'status'], name='car_year_status_idx'),
|
||||
),
|
||||
migrations.AddIndex(
|
||||
model_name='car',
|
||||
index=models.Index(condition=models.Q(('status', 'available')), fields=['status'], name='car_active_status_idx'),
|
||||
),
|
||||
]
|
||||
|
||||
@ -44,6 +44,8 @@ from appointment.models import StaffMember
|
||||
from plans.quota import get_user_quota
|
||||
from plans.models import UserPlan
|
||||
from django.db.models import Q
|
||||
from imagekit.models import ImageSpecField
|
||||
from imagekit.processors import ResizeToFill
|
||||
# from plans.models import AbstractPlan
|
||||
# from simple_history.models import HistoricalRecords
|
||||
|
||||
@ -706,7 +708,7 @@ class Car(Base):
|
||||
[
|
||||
self.colors,
|
||||
self.finances,
|
||||
self.finances.selling_price > 0,
|
||||
self.finances.marked_price > 0,
|
||||
]
|
||||
)
|
||||
except Exception:
|
||||
@ -903,7 +905,7 @@ class CarFinance(models.Model):
|
||||
|
||||
@property
|
||||
def total(self):
|
||||
return self.selling_price
|
||||
return self.marked_price
|
||||
|
||||
@property
|
||||
def total_additionals_no_vat(self):
|
||||
@ -916,8 +918,8 @@ class CarFinance(models.Model):
|
||||
@property
|
||||
def total_discount(self):
|
||||
if self.discount_amount > 0:
|
||||
return self.selling_price - self.discount_amount
|
||||
return self.selling_price
|
||||
return self.marked_price - self.discount_amount
|
||||
return self.marked_price
|
||||
|
||||
@property
|
||||
def total_vat(self):
|
||||
@ -932,12 +934,13 @@ class CarFinance(models.Model):
|
||||
|
||||
@property
|
||||
def revenue(self):
|
||||
return self.selling_price - self.cost_price
|
||||
return self.marked_price - self.cost_price
|
||||
|
||||
def to_dict(self):
|
||||
return {
|
||||
"cost_price": str(self.cost_price),
|
||||
"selling_price": str(self.selling_price),
|
||||
"marked_price": str(self.marked_price),
|
||||
"discount_amount": str(self.discount_amount),
|
||||
"total": str(self.total),
|
||||
"total_discount": str(self.total_discount),
|
||||
@ -946,7 +949,7 @@ class CarFinance(models.Model):
|
||||
}
|
||||
|
||||
def __str__(self):
|
||||
return f"Car: {self.car}, Selling Price: {self.selling_price}"
|
||||
return f"Car: {self.car}, Marked Price: {self.marked_price}"
|
||||
|
||||
# def save(self, *args, **kwargs):
|
||||
# self.full_clean()
|
||||
@ -965,6 +968,7 @@ class CarFinance(models.Model):
|
||||
models.Index(fields=['car'], name='car_finance_car_idx'),
|
||||
models.Index(fields=['cost_price'], name='car_finance_cost_price_idx'),
|
||||
models.Index(fields=['selling_price'], name='car_finance_selling_price_idx'),
|
||||
models.Index(fields=['marked_price'], name='car_finance_marked_price_idx'),
|
||||
models.Index(fields=['discount_amount'], name='car_finance_discount_idx'),
|
||||
]
|
||||
|
||||
@ -1137,7 +1141,7 @@ class Dealer(models.Model, LocalizedNameMixin):
|
||||
max_length=200, blank=True, null=True, verbose_name=_("Address")
|
||||
)
|
||||
logo = models.ImageField(
|
||||
upload_to="logos/users", blank=True, null=True, verbose_name=_("Logo")
|
||||
upload_to="logos/users", blank=True, null=True, verbose_name=_("Logo"),default="logo.png"
|
||||
)
|
||||
entity = models.ForeignKey(
|
||||
EntityModel, on_delete=models.SET_NULL, null=True, blank=True
|
||||
@ -1233,8 +1237,14 @@ class Staff(models.Model, LocalizedNameMixin):
|
||||
address = models.CharField(
|
||||
max_length=200, blank=True, null=True, verbose_name=_("Address")
|
||||
)
|
||||
image = models.ImageField(
|
||||
upload_to="staff/", blank=True, null=True, verbose_name=_("Image")
|
||||
logo = models.ImageField(
|
||||
upload_to="logos/staff", blank=True, null=True, verbose_name=_("Image")
|
||||
)
|
||||
thumbnail = ImageSpecField(
|
||||
source='logo',
|
||||
processors=[ResizeToFill(40, 40)],
|
||||
format='WEBP',
|
||||
options={'quality': 80}
|
||||
)
|
||||
active = models.BooleanField(default=True, verbose_name=_("Active"))
|
||||
created = models.DateTimeField(auto_now_add=True, verbose_name=_("Created"))
|
||||
@ -2713,7 +2723,7 @@ class SaleOrder(models.Model):
|
||||
|
||||
@property
|
||||
def price(self):
|
||||
return self.car.finances.selling_price
|
||||
return self.car.finances.marked_price
|
||||
|
||||
@property
|
||||
def items(self):
|
||||
|
||||
@ -366,7 +366,7 @@ def update_item_model_cost(sender, instance, created, **kwargs):
|
||||
description="",
|
||||
)
|
||||
|
||||
instance.car.item_model.default_amount = instance.selling_price
|
||||
instance.car.item_model.default_amount = instance.marked_price
|
||||
if not isinstance(instance.car.item_model.additional_info, dict):
|
||||
instance.car.item_model.additional_info = {}
|
||||
instance.car.item_model.additional_info.update({"car_finance": instance.to_dict()})
|
||||
@ -975,7 +975,7 @@ def car_created_notification(sender, instance, created, **kwargs):
|
||||
models.Notification.objects.create(
|
||||
user=accountant,
|
||||
message=f"""
|
||||
New Car {instance.vin} has been added to dealer {instance.dealer.name}.
|
||||
New Car {instance.id_car_make}-{instance.id_car_model}-{instance.year}-{instance.vin} has been added to the inventory.
|
||||
<a href="{instance.get_absolute_url()}" target="_blank">View</a>
|
||||
""",
|
||||
)
|
||||
|
||||
@ -282,7 +282,7 @@ def get_car_finance_data(model):
|
||||
data = model.get_itemtxs_data()[0].all()
|
||||
total = sum(
|
||||
[
|
||||
Decimal(item.item_model.additional_info["car_finance"]["selling_price"])
|
||||
Decimal(item.item_model.additional_info["car_finance"]["marked_price"])
|
||||
* Decimal(item.ce_quantity or item.quantity)
|
||||
for item in data
|
||||
]
|
||||
@ -308,8 +308,8 @@ def get_car_finance_data(model):
|
||||
"year": x.item_model.additional_info["car_info"]["year"],
|
||||
"trim": x.item_model.additional_info["car_info"]["mileage"],
|
||||
"cost_price": x.item_model.additional_info["car_finance"]["cost_price"],
|
||||
"selling_price": x.item_model.additional_info["car_finance"][
|
||||
"selling_price"
|
||||
"marked_price": x.item_model.additional_info["car_finance"][
|
||||
"marked_price"
|
||||
],
|
||||
"discount": x.item_model.additional_info["car_finance"][
|
||||
"discount_amount"
|
||||
@ -404,7 +404,7 @@ def get_financial_values(model):
|
||||
data = model.get_itemtxs_data()[0].all()
|
||||
total = sum(
|
||||
[
|
||||
Decimal(item.item_model.additional_info["car_finance"]["selling_price"])
|
||||
Decimal(item.item_model.additional_info["car_finance"]["marked_price"])
|
||||
* Decimal(item.ce_quantity or item.quantity)
|
||||
for item in data
|
||||
]
|
||||
@ -435,7 +435,7 @@ def get_financial_values(model):
|
||||
"finances": x.item_model.additional_info["car_finance"],
|
||||
"quantity": x.ce_quantity or x.quantity,
|
||||
"total": Decimal(
|
||||
x.item_model.additional_info["car_finance"]["selling_price"]
|
||||
x.item_model.additional_info["car_finance"]["marked_price"]
|
||||
)
|
||||
* Decimal(x.ce_quantity or x.quantity),
|
||||
}
|
||||
@ -787,7 +787,7 @@ class CarTransfer:
|
||||
self.car.custom_cards.delete()
|
||||
|
||||
self.car.finances.cost_price = self.transfer.total_price
|
||||
self.car.finances.selling_price = 0
|
||||
self.car.finances.marked_price = 0
|
||||
self.car.finances.discount_amount = 0
|
||||
self.car.finances.save()
|
||||
self.car.location.owner = self.to_dealer
|
||||
@ -1027,7 +1027,7 @@ class CarFinanceCalculator:
|
||||
quantity = self._get_quantity(item)
|
||||
car_finance = self._get_nested_value(item, self.CAR_FINANCE_KEY)
|
||||
car_info = self._get_nested_value(item, self.CAR_INFO_KEY)
|
||||
unit_price = Decimal(car_finance.get("selling_price", 0))
|
||||
unit_price = Decimal(car_finance.get("marked_price", 0))
|
||||
return {
|
||||
"item_number": item.item_model.item_number,
|
||||
"vin": car_info.get("vin"),
|
||||
@ -1065,7 +1065,7 @@ class CarFinanceCalculator:
|
||||
|
||||
def calculate_totals(self):
|
||||
total_price = sum(
|
||||
Decimal(self._get_nested_value(item, self.CAR_FINANCE_KEY, "selling_price"))
|
||||
Decimal(self._get_nested_value(item, self.CAR_FINANCE_KEY, "marked_price"))
|
||||
* int(self._get_quantity(item))
|
||||
for item in self.item_transactions
|
||||
)
|
||||
@ -1073,7 +1073,7 @@ class CarFinanceCalculator:
|
||||
Decimal(x.get("price_")) for x in self._get_additional_services()
|
||||
)
|
||||
|
||||
total_discount = self.extra_info.data.get("discount")
|
||||
total_discount = self.extra_info.data.get("discount",0)
|
||||
|
||||
# total_discount = sum(
|
||||
# Decimal(
|
||||
@ -1081,7 +1081,9 @@ class CarFinanceCalculator:
|
||||
# )
|
||||
# for item in self.item_transactions
|
||||
# )
|
||||
total_price_discounted = total_price - Decimal(total_discount)
|
||||
total_price_discounted = total_price
|
||||
if total_discount:
|
||||
total_price_discounted = total_price - Decimal(total_discount)
|
||||
total_vat_amount = total_price_discounted * self.vat_rate
|
||||
|
||||
return {
|
||||
|
||||
@ -449,7 +449,7 @@ class ManagerDashboard(LoginRequiredMixin, TemplateView):
|
||||
except ZeroDivisionError as e:
|
||||
print(f"error: {e}")
|
||||
|
||||
|
||||
|
||||
qs = (
|
||||
models.Car.objects.values("id_car_make__name")
|
||||
.annotate(count=Count("id"))
|
||||
@ -2140,6 +2140,7 @@ class DealerDetailView(LoginRequiredMixin, PermissionRequiredMixin,DetailView):
|
||||
def dealer_vat_rate_update(request,slug):
|
||||
dealer = get_object_or_404(models.Dealer,slug=slug)
|
||||
models.VatRate.objects.filter(dealer=dealer).update(rate=request.POST.get("rate"))
|
||||
messages.success(request, _("VAT rate updated successfully"))
|
||||
return redirect("dealer_detail", slug=slug)
|
||||
|
||||
class DealerUpdateView(LoginRequiredMixin,PermissionRequiredMixin, SuccessMessageMixin, UpdateView):
|
||||
@ -2527,8 +2528,8 @@ def vendorDetailView(request, dealer_slug,slug):
|
||||
return render(
|
||||
request, template_name="vendors/view_vendor.html", context={"vendor": vendor}
|
||||
)
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
class VendorCreateView(
|
||||
@ -3403,6 +3404,7 @@ class UserUpdateView(
|
||||
# self.object.staff_member.services_offered.add(service)
|
||||
|
||||
staff = form.save(commit=False)
|
||||
print(form.cleaned_data)
|
||||
staff.name = form.cleaned_data["name"]
|
||||
staff.arabic_name = form.cleaned_data["arabic_name"]
|
||||
staff.phone_number = form.cleaned_data["phone_number"]
|
||||
@ -3875,7 +3877,7 @@ class BankAccountDetailView(LoginRequiredMixin, PermissionRequiredMixin, DetailV
|
||||
template_name = "ledger/bank_accounts/bank_account_detail.html"
|
||||
context_object_name = "bank_account"
|
||||
permission_required = ["django_ledger.view_bankaccountmodel"]
|
||||
|
||||
|
||||
def get_queryset(self):
|
||||
dealer = get_object_or_404(models.Dealer, slug=self.kwargs["dealer_slug"])
|
||||
query=self.request.GET.get('q')
|
||||
@ -4446,7 +4448,7 @@ def create_estimate(request, dealer_slug, slug=None):
|
||||
finances__is_sold=False,
|
||||
colors__isnull=False,
|
||||
finances__isnull=False,
|
||||
finances__selling_price__gt=1,
|
||||
finances__marked_price__gt=1,
|
||||
status="available",
|
||||
).all()
|
||||
|
||||
@ -4455,8 +4457,8 @@ def create_estimate(request, dealer_slug, slug=None):
|
||||
{
|
||||
"item_number": i.item_model.item_number,
|
||||
"quantity": 1,
|
||||
"unit_cost": round(float(i.finances.selling_price)),
|
||||
"unit_revenue": round(float(i.finances.selling_price)),
|
||||
"unit_cost": round(float(i.finances.marked_price)),
|
||||
"unit_revenue": round(float(i.finances.marked_price)),
|
||||
"total_amount": round(float(i.finances.total_vat)),
|
||||
}
|
||||
)
|
||||
@ -4476,7 +4478,7 @@ def create_estimate(request, dealer_slug, slug=None):
|
||||
# estimate_itemtxs = {
|
||||
# item.item_number: {
|
||||
# "unit_cost": instance.finances.cost_price,
|
||||
# "unit_revenue": instance.finances.selling_price,
|
||||
# "unit_revenue": instance.finances.marked_price,
|
||||
# "quantity": Decimal(quantities),
|
||||
# "total_amount": instance.finances.total_vat * int(quantities),
|
||||
# }
|
||||
@ -4559,7 +4561,7 @@ def create_estimate(request, dealer_slug, slug=None):
|
||||
dealer=dealer,
|
||||
colors__isnull=False,
|
||||
finances__isnull=False,
|
||||
finances__selling_price__gt=1,
|
||||
finances__marked_price__gt=1,
|
||||
status="available",
|
||||
)
|
||||
.annotate(
|
||||
@ -5656,8 +5658,8 @@ class LeadListView(LoginRequiredMixin, PermissionRequiredMixin, ListView):
|
||||
if self.request.is_staff:
|
||||
return qs.filter(staff=self.request.staff)
|
||||
return models.Lead.objects.none()
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
class LeadDetailView(LoginRequiredMixin, PermissionRequiredMixin, DetailView):
|
||||
"""
|
||||
@ -6564,7 +6566,7 @@ class OpportunityCreateView(LoginRequiredMixin,PermissionRequiredMixin,CreateVie
|
||||
dealer = get_object_or_404(models.Dealer,slug=self.kwargs.get("dealer_slug"))
|
||||
staff = getattr(self.request.user.staffmember, "staff", None)
|
||||
form = super().get_form(form_class)
|
||||
form.fields["car"].queryset = models.Car.objects.filter(dealer=dealer,status='available',finances__selling_price__gt=0)
|
||||
form.fields["car"].queryset = models.Car.objects.filter(dealer=dealer,status='available',finances__marked_price__gt=0)
|
||||
form.fields["lead"].queryset = models.Lead.objects.filter(dealer=dealer,staff=staff)
|
||||
return form
|
||||
|
||||
@ -6605,7 +6607,7 @@ class OpportunityUpdateView(LoginRequiredMixin,PermissionRequiredMixin, SuccessM
|
||||
form = super().get_form(form_class)
|
||||
dealer = get_object_or_404(models.Dealer,slug=self.kwargs.get("dealer_slug"))
|
||||
staff = getattr(self.request.user.staffmember, "staff", None)
|
||||
form.fields["car"].queryset = models.Car.objects.filter(dealer=dealer,status='available',finances__selling_price__gt=0)
|
||||
form.fields["car"].queryset = models.Car.objects.filter(dealer=dealer,status='available',finances__marked_price__gt=0)
|
||||
form.fields["lead"].queryset = models.Lead.objects.filter(dealer=dealer,staff=staff)
|
||||
return form
|
||||
|
||||
@ -6723,9 +6725,9 @@ class OpportunityListView(LoginRequiredMixin,PermissionRequiredMixin, ListView):
|
||||
Q(customer__first_name__icontains=search)
|
||||
| Q(customer__last_name__icontains=search)
|
||||
| Q(customer__email__icontains=search)
|
||||
|
||||
|
||||
)
|
||||
|
||||
|
||||
|
||||
# Stage filter
|
||||
stage = self.request.GET.get("stage")
|
||||
@ -6929,6 +6931,7 @@ class ItemServiceCreateView(
|
||||
permission_required = ["inventory.add_additionalservices"]
|
||||
|
||||
def form_valid(self, form):
|
||||
sleep(5)
|
||||
dealer = get_user_type(self.request)
|
||||
vat = models.VatRate.objects.get(dealer=dealer,is_active=True)
|
||||
form.instance.dealer = dealer
|
||||
@ -7144,7 +7147,7 @@ class ItemExpenseListView(LoginRequiredMixin, PermissionRequiredMixin, ListView)
|
||||
# def get_queryset(self):
|
||||
# dealer = get_user_type(self.request)
|
||||
# return dealer.entity.get_items_expenses()
|
||||
|
||||
|
||||
def get_queryset(self):
|
||||
dealer = get_user_type(self.request)
|
||||
query=self.request.GET.get('q')
|
||||
@ -8725,7 +8728,7 @@ class LedgerModelListView(LoginRequiredMixin,PermissionRequiredMixin, ListView,
|
||||
show_visible = False
|
||||
allow_empty = True
|
||||
paginate_by = 30
|
||||
|
||||
|
||||
|
||||
def get_queryset(self):
|
||||
qs = super().get_queryset()
|
||||
@ -9042,7 +9045,7 @@ class JournalEntryModelTXSDetailView(JournalEntryModelTXSDetailViewBase):
|
||||
"""
|
||||
|
||||
template_name = "ledger/journal_entry/journal_entry_txs.html"
|
||||
|
||||
|
||||
|
||||
@login_required
|
||||
@permission_required("django_ledger.change_ledgermodel", raise_exception=True)
|
||||
@ -10018,9 +10021,9 @@ class PurchaseOrderListView(LoginRequiredMixin, PermissionRequiredMixin, ListVie
|
||||
if query:
|
||||
qs=apply_search_filters(qs,query)
|
||||
return qs
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
# def get_queryset(self):
|
||||
# dealer = get_user_type(self.request)
|
||||
# entity = dealer.entity
|
||||
@ -10409,11 +10412,10 @@ def bulk_update_car_price(request):
|
||||
car = models.Car.objects.get(pk=car_pk)
|
||||
if not hasattr(car, "finances"):
|
||||
models.CarFinance.objects.create(
|
||||
car=car, cost_price=Decimal(price), selling_price=0
|
||||
car=car, cost_price=Decimal(price)
|
||||
)
|
||||
else:
|
||||
car.finances.cost_price = Decimal(price)
|
||||
car.finances.selling_price = 0
|
||||
car.finances.save()
|
||||
messages.success(request, "Price updated successfully")
|
||||
|
||||
|
||||
@ -24,8 +24,6 @@ python3 manage.py populate_colors
|
||||
|
||||
python3 manage.py tenhal_plan
|
||||
|
||||
python3 manage.py set_vat
|
||||
|
||||
python3 manage.py set_custom_permissions
|
||||
|
||||
python3 manage.py initial_services_offered
|
||||
|
||||
@ -45,7 +45,7 @@
|
||||
}
|
||||
|
||||
.form-control, .form-select {
|
||||
/* text-align: center; */
|
||||
text-align: center;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
|
||||
|
After Width: | Height: | Size: 740 B |
|
After Width: | Height: | Size: 746 B |
|
After Width: | Height: | Size: 530 B |
|
After Width: | Height: | Size: 312 B |
BIN
static/images/logos/staff/customer3.jpg
Normal file
|
After Width: | Height: | Size: 39 KiB |
BIN
static/images/logos/staff/customer4.jpg
Normal file
|
After Width: | Height: | Size: 46 KiB |
BIN
static/images/logos/staff/customer4_VhAPbzd.jpg
Normal file
|
After Width: | Height: | Size: 46 KiB |
BIN
static/images/logos/staff/customer4_yoUBW1d.jpg
Normal file
|
After Width: | Height: | Size: 46 KiB |
BIN
static/sounds/tone.wav
Normal file
9
staticfiles/css/all.min.css
vendored
Normal file
@ -45,7 +45,7 @@
|
||||
}
|
||||
|
||||
.form-control, .form-select {
|
||||
text-align: center;
|
||||
/* text-align: center; */
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
@ -130,4 +130,5 @@ html[dir="rtl"] .form-icon-container .form-control {
|
||||
|
||||
@keyframes spin {
|
||||
to { transform: rotate(360deg); }
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
3
staticfiles/debug_toolbar/css/print.css
Normal file
@ -0,0 +1,3 @@
|
||||
#djDebug {
|
||||
display: none !important;
|
||||
}
|
||||
1181
staticfiles/debug_toolbar/css/toolbar.css
Normal file
105
staticfiles/debug_toolbar/js/history.js
Normal file
@ -0,0 +1,105 @@
|
||||
import { $$, ajaxForm, replaceToolbarState } from "./utils.js";
|
||||
|
||||
const djDebug = document.getElementById("djDebug");
|
||||
|
||||
function difference(setA, setB) {
|
||||
const _difference = new Set(setA);
|
||||
for (const elem of setB) {
|
||||
_difference.delete(elem);
|
||||
}
|
||||
return _difference;
|
||||
}
|
||||
|
||||
/**
|
||||
* Create an array of dataset properties from a NodeList.
|
||||
*/
|
||||
function pluckData(nodes, key) {
|
||||
return [...nodes].map((obj) => obj.dataset[key]);
|
||||
}
|
||||
|
||||
function refreshHistory() {
|
||||
const formTarget = djDebug.querySelector(".refreshHistory");
|
||||
const container = document.getElementById("djdtHistoryRequests");
|
||||
const oldIds = new Set(
|
||||
pluckData(container.querySelectorAll("tr[data-store-id]"), "storeId")
|
||||
);
|
||||
|
||||
ajaxForm(formTarget)
|
||||
.then((data) => {
|
||||
// Remove existing rows first then re-populate with new data
|
||||
for (const node of container.querySelectorAll(
|
||||
"tr[data-store-id]"
|
||||
)) {
|
||||
node.remove();
|
||||
}
|
||||
for (const request of data.requests) {
|
||||
container.innerHTML = request.content + container.innerHTML;
|
||||
}
|
||||
})
|
||||
.then(() => {
|
||||
const allIds = new Set(
|
||||
pluckData(
|
||||
container.querySelectorAll("tr[data-store-id]"),
|
||||
"storeId"
|
||||
)
|
||||
);
|
||||
const newIds = difference(allIds, oldIds);
|
||||
const lastRequestId = newIds.values().next().value;
|
||||
return {
|
||||
allIds,
|
||||
newIds,
|
||||
lastRequestId,
|
||||
};
|
||||
})
|
||||
.then((refreshInfo) => {
|
||||
for (const newId of refreshInfo.newIds) {
|
||||
const row = container.querySelector(
|
||||
`tr[data-store-id="${newId}"]`
|
||||
);
|
||||
row.classList.add("flash-new");
|
||||
}
|
||||
setTimeout(() => {
|
||||
for (const row of container.querySelectorAll(
|
||||
"tr[data-store-id]"
|
||||
)) {
|
||||
row.classList.remove("flash-new");
|
||||
}
|
||||
}, 2000);
|
||||
});
|
||||
}
|
||||
|
||||
function switchHistory(newStoreId) {
|
||||
const formTarget = djDebug.querySelector(
|
||||
`.switchHistory[data-store-id='${newStoreId}']`
|
||||
);
|
||||
const tbody = formTarget.closest("tbody");
|
||||
|
||||
const highlighted = tbody.querySelector(".djdt-highlighted");
|
||||
if (highlighted) {
|
||||
highlighted.classList.remove("djdt-highlighted");
|
||||
}
|
||||
formTarget.closest("tr").classList.add("djdt-highlighted");
|
||||
|
||||
ajaxForm(formTarget).then((data) => {
|
||||
if (Object.keys(data).length === 0) {
|
||||
const container = document.getElementById("djdtHistoryRequests");
|
||||
container.querySelector(
|
||||
`button[data-store-id="${newStoreId}"]`
|
||||
).innerHTML = "Switch [EXPIRED]";
|
||||
}
|
||||
replaceToolbarState(newStoreId, data);
|
||||
});
|
||||
}
|
||||
|
||||
$$.on(djDebug, "click", ".switchHistory", function (event) {
|
||||
event.preventDefault();
|
||||
switchHistory(this.dataset.storeId);
|
||||
});
|
||||
|
||||
$$.on(djDebug, "click", ".refreshHistory", (event) => {
|
||||
event.preventDefault();
|
||||
refreshHistory();
|
||||
});
|
||||
// We don't refresh the whole toolbar each fetch or ajax request,
|
||||
// so we need to refresh the history when we open the panel
|
||||
$$.onPanelRender(djDebug, "HistoryPanel", refreshHistory);
|
||||
1
staticfiles/debug_toolbar/js/redirect.js
Normal file
@ -0,0 +1 @@
|
||||
document.getElementById("redirect_to").focus();
|
||||
82
staticfiles/debug_toolbar/js/timer.js
Normal file
@ -0,0 +1,82 @@
|
||||
import { $$ } from "./utils.js";
|
||||
|
||||
function insertBrowserTiming() {
|
||||
const timingOffset = performance.timing.navigationStart;
|
||||
const timingEnd = performance.timing.loadEventEnd;
|
||||
const totalTime = timingEnd - timingOffset;
|
||||
function getLeft(stat) {
|
||||
if (totalTime !== 0) {
|
||||
return (
|
||||
((performance.timing[stat] - timingOffset) / totalTime) * 100.0
|
||||
);
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
function getCSSWidth(stat, endStat) {
|
||||
let width = 0;
|
||||
if (totalTime !== 0) {
|
||||
width =
|
||||
((performance.timing[endStat] - performance.timing[stat]) /
|
||||
totalTime) *
|
||||
100.0;
|
||||
}
|
||||
const denominator = 100.0 - getLeft(stat);
|
||||
if (denominator !== 0) {
|
||||
// Calculate relative percent (same as sql panel logic)
|
||||
width = (100.0 * width) / denominator;
|
||||
} else {
|
||||
width = 0;
|
||||
}
|
||||
return width < 1 ? "2px" : `${width}%`;
|
||||
}
|
||||
function addRow(tbody, stat, endStat) {
|
||||
const row = document.createElement("tr");
|
||||
const elapsed = performance.timing[stat] - timingOffset;
|
||||
if (endStat) {
|
||||
const duration =
|
||||
performance.timing[endStat] - performance.timing[stat];
|
||||
// Render a start through end bar
|
||||
row.innerHTML = `
|
||||
<td>${stat.replace("Start", "")}</td>
|
||||
<td><svg class="djDebugLineChart" xmlns="http://www.w3.org/2000/svg" viewbox="0 0 100 5" preserveAspectRatio="none"><rect y="0" height="5" fill="#ccc" /></svg></td>
|
||||
<td>${elapsed} (+${duration})</td>
|
||||
`;
|
||||
row.querySelector("rect").setAttribute(
|
||||
"width",
|
||||
getCSSWidth(stat, endStat)
|
||||
);
|
||||
} else {
|
||||
// Render a point in time
|
||||
row.innerHTML = `
|
||||
<td>${stat}</td>
|
||||
<td><svg class="djDebugLineChart" xmlns="http://www.w3.org/2000/svg" viewbox="0 0 100 5" preserveAspectRatio="none"><rect y="0" height="5" fill="#ccc" /></svg></td>
|
||||
<td>${elapsed}</td>
|
||||
`;
|
||||
row.querySelector("rect").setAttribute("width", 2);
|
||||
}
|
||||
row.querySelector("rect").setAttribute("x", getLeft(stat));
|
||||
tbody.appendChild(row);
|
||||
}
|
||||
|
||||
const browserTiming = document.getElementById("djDebugBrowserTiming");
|
||||
// Determine if the browser timing section has already been rendered.
|
||||
if (browserTiming.classList.contains("djdt-hidden")) {
|
||||
const tbody = document.getElementById("djDebugBrowserTimingTableBody");
|
||||
// This is a reasonably complete and ordered set of timing periods (2 params) and events (1 param)
|
||||
addRow(tbody, "domainLookupStart", "domainLookupEnd");
|
||||
addRow(tbody, "connectStart", "connectEnd");
|
||||
addRow(tbody, "requestStart", "responseEnd"); // There is no requestEnd
|
||||
addRow(tbody, "responseStart", "responseEnd");
|
||||
addRow(tbody, "domLoading", "domComplete"); // Spans the events below
|
||||
addRow(tbody, "domInteractive");
|
||||
addRow(tbody, "domContentLoadedEventStart", "domContentLoadedEventEnd");
|
||||
addRow(tbody, "loadEventStart", "loadEventEnd");
|
||||
browserTiming.classList.remove("djdt-hidden");
|
||||
}
|
||||
}
|
||||
|
||||
const djDebug = document.getElementById("djDebug");
|
||||
// Insert the browser timing now since it's possible for this
|
||||
// script to miss the initial panel load event.
|
||||
insertBrowserTiming();
|
||||
$$.onPanelRender(djDebug, "TimerPanel", insertBrowserTiming);
|
||||
401
staticfiles/debug_toolbar/js/toolbar.js
Normal file
@ -0,0 +1,401 @@
|
||||
import { $$, ajax, debounce, replaceToolbarState } from "./utils.js";
|
||||
|
||||
function onKeyDown(event) {
|
||||
if (event.keyCode === 27) {
|
||||
djdt.hideOneLevel();
|
||||
}
|
||||
}
|
||||
|
||||
function getDebugElement() {
|
||||
// Fetch the debug element from the DOM.
|
||||
// This is used to avoid writing the element's id
|
||||
// everywhere the element is being selected. A fixed reference
|
||||
// to the element should be avoided because the entire DOM could
|
||||
// be reloaded such as via HTMX boosting.
|
||||
return document.getElementById("djDebug");
|
||||
}
|
||||
|
||||
const djdt = {
|
||||
handleDragged: false,
|
||||
needUpdateOnFetch: false,
|
||||
init() {
|
||||
const djDebug = getDebugElement();
|
||||
djdt.needUpdateOnFetch = djDebug.dataset.updateOnFetch === "True";
|
||||
$$.on(djDebug, "click", "#djDebugPanelList li a", function (event) {
|
||||
event.preventDefault();
|
||||
if (!this.className) {
|
||||
return;
|
||||
}
|
||||
const panelId = this.className;
|
||||
const current = document.getElementById(panelId);
|
||||
if ($$.visible(current)) {
|
||||
djdt.hidePanels();
|
||||
} else {
|
||||
djdt.hidePanels();
|
||||
|
||||
$$.show(current);
|
||||
this.parentElement.classList.add("djdt-active");
|
||||
|
||||
const inner = current.querySelector(
|
||||
".djDebugPanelContent .djdt-scroll"
|
||||
);
|
||||
const storeId = djDebug.dataset.storeId;
|
||||
if (storeId && inner.children.length === 0) {
|
||||
const url = new URL(
|
||||
djDebug.dataset.renderPanelUrl,
|
||||
window.location
|
||||
);
|
||||
url.searchParams.append("store_id", storeId);
|
||||
url.searchParams.append("panel_id", panelId);
|
||||
ajax(url).then((data) => {
|
||||
inner.previousElementSibling.remove(); // Remove AJAX loader
|
||||
inner.innerHTML = data.content;
|
||||
$$.executeScripts(data.scripts);
|
||||
$$.applyStyles(inner);
|
||||
djDebug.dispatchEvent(
|
||||
new CustomEvent("djdt.panel.render", {
|
||||
detail: { panelId: panelId },
|
||||
})
|
||||
);
|
||||
});
|
||||
} else {
|
||||
djDebug.dispatchEvent(
|
||||
new CustomEvent("djdt.panel.render", {
|
||||
detail: { panelId: panelId },
|
||||
})
|
||||
);
|
||||
}
|
||||
}
|
||||
});
|
||||
$$.on(djDebug, "click", ".djDebugClose", () => {
|
||||
djdt.hideOneLevel();
|
||||
});
|
||||
$$.on(
|
||||
djDebug,
|
||||
"click",
|
||||
".djDebugPanelButton input[type=checkbox]",
|
||||
function () {
|
||||
djdt.cookie.set(
|
||||
this.dataset.cookie,
|
||||
this.checked ? "on" : "off",
|
||||
{
|
||||
path: "/",
|
||||
expires: 10,
|
||||
}
|
||||
);
|
||||
}
|
||||
);
|
||||
|
||||
// Used by the SQL and template panels
|
||||
$$.on(djDebug, "click", ".remoteCall", function (event) {
|
||||
event.preventDefault();
|
||||
|
||||
let url;
|
||||
const ajaxData = {};
|
||||
|
||||
if (this.tagName === "BUTTON") {
|
||||
const form = this.closest("form");
|
||||
url = this.formAction;
|
||||
ajaxData.method = form.method.toUpperCase();
|
||||
ajaxData.body = new FormData(form);
|
||||
} else if (this.tagName === "A") {
|
||||
url = this.href;
|
||||
}
|
||||
|
||||
ajax(url, ajaxData).then((data) => {
|
||||
const win = document.getElementById("djDebugWindow");
|
||||
win.innerHTML = data.content;
|
||||
$$.show(win);
|
||||
});
|
||||
});
|
||||
|
||||
// Used by the cache, profiling and SQL panels
|
||||
$$.on(djDebug, "click", ".djToggleSwitch", function () {
|
||||
const id = this.dataset.toggleId;
|
||||
const toggleOpen = "+";
|
||||
const toggleClose = "-";
|
||||
const openMe = this.textContent === toggleOpen;
|
||||
const name = this.dataset.toggleName;
|
||||
const container = document.getElementById(`${name}_${id}`);
|
||||
for (const el of container.querySelectorAll(".djDebugCollapsed")) {
|
||||
$$.toggle(el, openMe);
|
||||
}
|
||||
for (const el of container.querySelectorAll(
|
||||
".djDebugUncollapsed"
|
||||
)) {
|
||||
$$.toggle(el, !openMe);
|
||||
}
|
||||
for (const el of this.closest(
|
||||
".djDebugPanelContent"
|
||||
).querySelectorAll(`.djToggleDetails_${id}`)) {
|
||||
if (openMe) {
|
||||
el.classList.add("djSelected");
|
||||
el.classList.remove("djUnselected");
|
||||
this.textContent = toggleClose;
|
||||
} else {
|
||||
el.classList.remove("djSelected");
|
||||
el.classList.add("djUnselected");
|
||||
this.textContent = toggleOpen;
|
||||
}
|
||||
const switch_ = el.querySelector(".djToggleSwitch");
|
||||
if (switch_) {
|
||||
switch_.textContent = this.textContent;
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
$$.on(djDebug, "click", "#djHideToolBarButton", (event) => {
|
||||
event.preventDefault();
|
||||
djdt.hideToolbar();
|
||||
});
|
||||
|
||||
$$.on(djDebug, "click", "#djShowToolBarButton", () => {
|
||||
if (!djdt.handleDragged) {
|
||||
djdt.showToolbar();
|
||||
}
|
||||
});
|
||||
let startPageY;
|
||||
let baseY;
|
||||
const handle = document.getElementById("djDebugToolbarHandle");
|
||||
function onHandleMove(event) {
|
||||
// Chrome can send spurious mousemove events, so don't do anything unless the
|
||||
// cursor really moved. Otherwise, it will be impossible to expand the toolbar
|
||||
// due to djdt.handleDragged being set to true.
|
||||
if (djdt.handleDragged || event.pageY !== startPageY) {
|
||||
let top = baseY + event.pageY;
|
||||
|
||||
if (top < 0) {
|
||||
top = 0;
|
||||
} else if (top + handle.offsetHeight > window.innerHeight) {
|
||||
top = window.innerHeight - handle.offsetHeight;
|
||||
}
|
||||
|
||||
handle.style.top = `${top}px`;
|
||||
djdt.handleDragged = true;
|
||||
}
|
||||
}
|
||||
$$.on(djDebug, "mousedown", "#djShowToolBarButton", (event) => {
|
||||
event.preventDefault();
|
||||
startPageY = event.pageY;
|
||||
baseY = handle.offsetTop - startPageY;
|
||||
document.addEventListener("mousemove", onHandleMove);
|
||||
|
||||
document.addEventListener(
|
||||
"mouseup",
|
||||
(event) => {
|
||||
document.removeEventListener("mousemove", onHandleMove);
|
||||
if (djdt.handleDragged) {
|
||||
event.preventDefault();
|
||||
localStorage.setItem("djdt.top", handle.offsetTop);
|
||||
requestAnimationFrame(() => {
|
||||
djdt.handleDragged = false;
|
||||
});
|
||||
djdt.ensureHandleVisibility();
|
||||
}
|
||||
},
|
||||
{ once: true }
|
||||
);
|
||||
});
|
||||
|
||||
// Make sure the debug element is rendered at least once.
|
||||
// showToolbar will continue to show it in the future if the
|
||||
// entire DOM is reloaded.
|
||||
$$.show(djDebug);
|
||||
const show =
|
||||
localStorage.getItem("djdt.show") || djDebug.dataset.defaultShow;
|
||||
if (show === "true") {
|
||||
djdt.showToolbar();
|
||||
} else {
|
||||
djdt.hideToolbar();
|
||||
}
|
||||
if (djDebug.dataset.sidebarUrl !== undefined) {
|
||||
djdt.updateOnAjax();
|
||||
}
|
||||
|
||||
const prefersDark = window.matchMedia(
|
||||
"(prefers-color-scheme: dark)"
|
||||
).matches;
|
||||
const themeList = prefersDark
|
||||
? ["auto", "light", "dark"]
|
||||
: ["auto", "dark", "light"];
|
||||
const setTheme = (theme) => {
|
||||
djDebug.setAttribute(
|
||||
"data-theme",
|
||||
theme === "auto" ? (prefersDark ? "dark" : "light") : theme
|
||||
);
|
||||
djDebug.setAttribute("data-user-theme", theme);
|
||||
};
|
||||
|
||||
// Updates the theme using user settings
|
||||
let userTheme = localStorage.getItem("djdt.user-theme") || "auto";
|
||||
setTheme(userTheme);
|
||||
|
||||
// Adds the listener to the Theme Toggle Button
|
||||
$$.on(djDebug, "click", "#djToggleThemeButton", () => {
|
||||
const index = themeList.indexOf(userTheme);
|
||||
userTheme = themeList[(index + 1) % themeList.length];
|
||||
localStorage.setItem("djdt.user-theme", userTheme);
|
||||
setTheme(userTheme);
|
||||
});
|
||||
},
|
||||
hidePanels() {
|
||||
const djDebug = getDebugElement();
|
||||
$$.hide(document.getElementById("djDebugWindow"));
|
||||
for (const el of djDebug.querySelectorAll(".djdt-panelContent")) {
|
||||
$$.hide(el);
|
||||
}
|
||||
for (const el of document.querySelectorAll("#djDebugToolbar li")) {
|
||||
el.classList.remove("djdt-active");
|
||||
}
|
||||
},
|
||||
ensureHandleVisibility() {
|
||||
const handle = document.getElementById("djDebugToolbarHandle");
|
||||
// set handle position
|
||||
const handleTop = Math.min(
|
||||
localStorage.getItem("djdt.top") || 265,
|
||||
window.innerHeight - handle.offsetWidth
|
||||
);
|
||||
handle.style.top = `${handleTop}px`;
|
||||
},
|
||||
hideToolbar() {
|
||||
djdt.hidePanels();
|
||||
|
||||
$$.hide(document.getElementById("djDebugToolbar"));
|
||||
|
||||
const handle = document.getElementById("djDebugToolbarHandle");
|
||||
$$.show(handle);
|
||||
djdt.ensureHandleVisibility();
|
||||
window.addEventListener("resize", djdt.ensureHandleVisibility);
|
||||
document.removeEventListener("keydown", onKeyDown);
|
||||
|
||||
localStorage.setItem("djdt.show", "false");
|
||||
},
|
||||
hideOneLevel() {
|
||||
const win = document.getElementById("djDebugWindow");
|
||||
if ($$.visible(win)) {
|
||||
$$.hide(win);
|
||||
} else {
|
||||
const toolbar = document.getElementById("djDebugToolbar");
|
||||
if (toolbar.querySelector("li.djdt-active")) {
|
||||
djdt.hidePanels();
|
||||
} else {
|
||||
djdt.hideToolbar();
|
||||
}
|
||||
}
|
||||
},
|
||||
showToolbar() {
|
||||
document.addEventListener("keydown", onKeyDown);
|
||||
$$.show(document.getElementById("djDebug"));
|
||||
$$.hide(document.getElementById("djDebugToolbarHandle"));
|
||||
$$.show(document.getElementById("djDebugToolbar"));
|
||||
localStorage.setItem("djdt.show", "true");
|
||||
window.removeEventListener("resize", djdt.ensureHandleVisibility);
|
||||
},
|
||||
updateOnAjax() {
|
||||
const sidebarUrl =
|
||||
document.getElementById("djDebug").dataset.sidebarUrl;
|
||||
const slowjax = debounce(ajax, 200);
|
||||
|
||||
function handleAjaxResponse(storeId) {
|
||||
const encodedStoreId = encodeURIComponent(storeId);
|
||||
const dest = `${sidebarUrl}?store_id=${encodedStoreId}`;
|
||||
slowjax(dest).then((data) => {
|
||||
if (djdt.needUpdateOnFetch) {
|
||||
replaceToolbarState(encodedStoreId, data);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
// Patch XHR / traditional AJAX requests
|
||||
const origOpen = XMLHttpRequest.prototype.open;
|
||||
XMLHttpRequest.prototype.open = function (...args) {
|
||||
this.addEventListener("load", function () {
|
||||
// Chromium emits a "Refused to get unsafe header" uncatchable warning
|
||||
// when the header can't be fetched. While it doesn't impede execution
|
||||
// it's worrisome to developers.
|
||||
if (
|
||||
this.getAllResponseHeaders().indexOf("djdt-store-id") >= 0
|
||||
) {
|
||||
handleAjaxResponse(this.getResponseHeader("djdt-store-id"));
|
||||
}
|
||||
});
|
||||
origOpen.apply(this, args);
|
||||
};
|
||||
|
||||
const origFetch = window.fetch;
|
||||
window.fetch = function (...args) {
|
||||
// Heads up! Before modifying this code, please be aware of the
|
||||
// possible unhandled errors that might arise from changing this.
|
||||
// For details, see
|
||||
// https://github.com/django-commons/django-debug-toolbar/pull/2100
|
||||
const promise = origFetch.apply(this, args);
|
||||
return promise.then((response) => {
|
||||
if (response.headers.get("djdt-store-id") !== null) {
|
||||
try {
|
||||
handleAjaxResponse(
|
||||
response.headers.get("djdt-store-id")
|
||||
);
|
||||
} catch (err) {
|
||||
throw new Error(
|
||||
`"${err.name}" occurred within django-debug-toolbar: ${err.message}`
|
||||
);
|
||||
}
|
||||
}
|
||||
return response;
|
||||
});
|
||||
};
|
||||
},
|
||||
cookie: {
|
||||
get(key) {
|
||||
if (!document.cookie.includes(key)) {
|
||||
return null;
|
||||
}
|
||||
|
||||
const cookieArray = document.cookie.split("; ");
|
||||
const cookies = {};
|
||||
|
||||
for (const e of cookieArray) {
|
||||
const parts = e.split("=");
|
||||
cookies[parts[0]] = parts[1];
|
||||
}
|
||||
|
||||
return cookies[key];
|
||||
},
|
||||
set(key, value, options = {}) {
|
||||
if (typeof options.expires === "number") {
|
||||
const days = options.expires;
|
||||
const expires = new Date();
|
||||
expires.setDate(expires.setDate() + days);
|
||||
options.expires = expires;
|
||||
}
|
||||
|
||||
document.cookie = [
|
||||
`${encodeURIComponent(key)}=${String(value)}`,
|
||||
options.expires
|
||||
? `; expires=${options.expires.toUTCString()}`
|
||||
: "",
|
||||
options.path ? `; path=${options.path}` : "",
|
||||
options.domain ? `; domain=${options.domain}` : "",
|
||||
options.secure ? "; secure" : "",
|
||||
"samesite" in options
|
||||
? `; samesite=${options.samesite}`
|
||||
: "; samesite=lax",
|
||||
].join("");
|
||||
|
||||
return value;
|
||||
},
|
||||
},
|
||||
};
|
||||
window.djdt = {
|
||||
show_toolbar: djdt.showToolbar,
|
||||
hide_toolbar: djdt.hideToolbar,
|
||||
init: djdt.init,
|
||||
close: djdt.hideOneLevel,
|
||||
cookie: djdt.cookie,
|
||||
};
|
||||
|
||||
if (document.readyState !== "loading") {
|
||||
djdt.init();
|
||||
} else {
|
||||
document.addEventListener("DOMContentLoaded", djdt.init);
|
||||
}
|
||||
144
staticfiles/debug_toolbar/js/utils.js
Normal file
@ -0,0 +1,144 @@
|
||||
const $$ = {
|
||||
on(root, eventName, selector, fn) {
|
||||
root.removeEventListener(eventName, fn);
|
||||
root.addEventListener(eventName, (event) => {
|
||||
const target = event.target.closest(selector);
|
||||
if (root.contains(target)) {
|
||||
fn.call(target, event);
|
||||
}
|
||||
});
|
||||
},
|
||||
onPanelRender(root, panelId, fn) {
|
||||
/*
|
||||
This is a helper function to attach a handler for a `djdt.panel.render`
|
||||
event of a specific panel.
|
||||
|
||||
root: The container element that the listener should be attached to.
|
||||
panelId: The Id of the panel.
|
||||
fn: A function to execute when the event is triggered.
|
||||
*/
|
||||
root.addEventListener("djdt.panel.render", (event) => {
|
||||
if (event.detail.panelId === panelId) {
|
||||
fn.call(event);
|
||||
}
|
||||
});
|
||||
},
|
||||
show(element) {
|
||||
element.classList.remove("djdt-hidden");
|
||||
},
|
||||
hide(element) {
|
||||
element.classList.add("djdt-hidden");
|
||||
},
|
||||
toggle(element, value) {
|
||||
if (value) {
|
||||
$$.show(element);
|
||||
} else {
|
||||
$$.hide(element);
|
||||
}
|
||||
},
|
||||
visible(element) {
|
||||
return !element.classList.contains("djdt-hidden");
|
||||
},
|
||||
executeScripts(scripts) {
|
||||
for (const script of scripts) {
|
||||
const el = document.createElement("script");
|
||||
el.type = "module";
|
||||
el.src = script;
|
||||
el.async = true;
|
||||
document.head.appendChild(el);
|
||||
}
|
||||
},
|
||||
applyStyles(container) {
|
||||
/*
|
||||
* Given a container element, apply styles set via data-djdt-styles attribute.
|
||||
* The format is data-djdt-styles="styleName1:value;styleName2:value2"
|
||||
* The style names should use the CSSStyleDeclaration camel cased names.
|
||||
*/
|
||||
for (const element of container.querySelectorAll(
|
||||
"[data-djdt-styles]"
|
||||
)) {
|
||||
const styles = element.dataset.djdtStyles || "";
|
||||
for (const styleText of styles.split(";")) {
|
||||
const styleKeyPair = styleText.split(":");
|
||||
if (styleKeyPair.length === 2) {
|
||||
const name = styleKeyPair[0].trim();
|
||||
const value = styleKeyPair[1].trim();
|
||||
element.style[name] = value;
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
};
|
||||
|
||||
function ajax(url, init) {
|
||||
return fetch(url, Object.assign({ credentials: "same-origin" }, init))
|
||||
.then((response) => {
|
||||
if (response.ok) {
|
||||
return response
|
||||
.json()
|
||||
.catch((error) =>
|
||||
Promise.reject(
|
||||
new Error(
|
||||
`The response is a invalid Json object : ${error}`
|
||||
)
|
||||
)
|
||||
);
|
||||
}
|
||||
return Promise.reject(
|
||||
new Error(`${response.status}: ${response.statusText}`)
|
||||
);
|
||||
})
|
||||
.catch((error) => {
|
||||
const win = document.getElementById("djDebugWindow");
|
||||
win.innerHTML = `<div class="djDebugPanelTitle"><h3>${error.message}</h3><button type="button" class="djDebugClose">»</button></div>`;
|
||||
$$.show(win);
|
||||
throw error;
|
||||
});
|
||||
}
|
||||
|
||||
function ajaxForm(element) {
|
||||
const form = element.closest("form");
|
||||
const url = new URL(form.action);
|
||||
const formData = new FormData(form);
|
||||
for (const [name, value] of formData.entries()) {
|
||||
url.searchParams.append(name, value);
|
||||
}
|
||||
const ajaxData = {
|
||||
method: form.method.toUpperCase(),
|
||||
};
|
||||
return ajax(url, ajaxData);
|
||||
}
|
||||
|
||||
function replaceToolbarState(newStoreId, data) {
|
||||
const djDebug = document.getElementById("djDebug");
|
||||
djDebug.setAttribute("data-store-id", newStoreId);
|
||||
// Check if response is empty, it could be due to an expired storeId.
|
||||
for (const panelId of Object.keys(data)) {
|
||||
const panel = document.getElementById(panelId);
|
||||
if (panel) {
|
||||
panel.outerHTML = data[panelId].content;
|
||||
document.getElementById(`djdt-${panelId}`).outerHTML =
|
||||
data[panelId].button;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
function debounce(func, delay) {
|
||||
let timer = null;
|
||||
let resolves = [];
|
||||
|
||||
return (...args) => {
|
||||
clearTimeout(timer);
|
||||
timer = setTimeout(() => {
|
||||
const result = func(...args);
|
||||
for (const r of resolves) {
|
||||
r(result);
|
||||
}
|
||||
resolves = [];
|
||||
}, delay);
|
||||
|
||||
return new Promise((r) => resolves.push(r));
|
||||
};
|
||||
}
|
||||
|
||||
export { $$, ajax, ajaxForm, replaceToolbarState, debounce };
|
||||
6
staticfiles/js/fontawesome.min.js
vendored
Normal file
@ -46,8 +46,8 @@
|
||||
<link href="{% static 'css/theme.min.css' %}" type="text/css" rel="stylesheet" id="style-default">
|
||||
<link href="{% static 'css/user.min.css' %}" type="text/css" rel="stylesheet" id="user-style-default">
|
||||
{% endif %}
|
||||
{% comment %} <script src="{% static 'js/main.js' %}"></script> {% endcomment %}
|
||||
{% comment %} <script src="{% static 'js/jquery.min.js' %}"></script> {% endcomment %}
|
||||
<script src="{% static 'js/main.js' %}"></script>
|
||||
<script src="{% static 'js/jquery.min.js' %}"></script>
|
||||
{% comment %} <script src="{% static 'js/echarts.js' %}"></script> {% endcomment %}
|
||||
|
||||
{% block customCSS %}
|
||||
|
||||
@ -35,4 +35,4 @@
|
||||
</table>
|
||||
</div>
|
||||
</div>
|
||||
{% endblock %}
|
||||
{% endblock %}
|
||||
|
||||
@ -509,11 +509,10 @@
|
||||
<input class="form-check-input" type="checkbox" data-bulk-select='{"body":"all-email-table-body"}' />
|
||||
</div>
|
||||
</th>
|
||||
<th class="sort white-space-nowrap align-middle pe-3 ps-0 text-uppercase" scope="col" data-sort="subject" style="width:31%; min-width:350px">Title</th>
|
||||
<th class="sort align-middle pe-3 text-uppercase" scope="col" data-sort="sent" style="width:15%; min-width:130px">Assigned to</th>
|
||||
<th class="sort align-middle text-start text-uppercase" scope="col" data-sort="date" style="min-width:165px">Due Date</th>
|
||||
<th class="sort align-middle text-start text-uppercase" scope="col" data-sort="date" style="min-width:165px">Completed</th>
|
||||
<th class="sort align-middle text-start text-uppercase" scope="col" data-sort="date" style="min-width:165px"></th>
|
||||
<th class="sort white-space-nowrap align-middle pe-3 ps-0" scope="col" data-sort="subject" style="width:31%; min-width:100px">Name</th>
|
||||
<th class="sort align-middle pe-3 text" scope="col" data-sort="sent" style="width:15%; min-width:400px">Note</th>
|
||||
<th class="sort align-middle text-start" scope="col" data-sort="date" style="min-width:100px">Due Date</th>
|
||||
<th class="sort align-middle text-start" scope="col" data-sort="date" style="min-width:100px">Completed</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody class="list" id="all-tasks-table-body">
|
||||
@ -525,7 +524,9 @@
|
||||
</div>
|
||||
<div class="row align-items-center justify-content-between py-2 pe-0 fs-9">
|
||||
<div class="col-auto d-flex">
|
||||
<p class="mb-0 d-none d-sm-block me-3 fw-semibold text-body" data-list-info="data-list-info"></p><a class="fw-semibold" href="" data-list-view="*">View all<span class="fas fa-angle-right ms-1" data-fa-transform="down-1"></span></a><a class="fw-semibold d-none" href="" data-list-view="less">View Less<span class="fas fa-angle-right ms-1" data-fa-transform="down-1"></span></a>
|
||||
<p class="mb-0 d-none d-sm-block me-3 fw-semibold text-body" data-list-info="data-list-info"></p>
|
||||
<a class="nav-link px-3 d-block" href="{% url 'appointment:get_user_appointments' %}"> <span class="me-2 text-body align-bottom" data-feather="calendar"></span>{{ _("View in Calendar") }}
|
||||
<span class="fas fa-angle-right ms-1" data-fa-transform="down-1"></span></a>
|
||||
</div>
|
||||
<div class="col-auto d-flex">
|
||||
<button class="page-link" data-list-pagination="prev"><span class="fas fa-chevron-left"></span></button>
|
||||
|
||||
@ -50,7 +50,7 @@
|
||||
<div class="" dir="ltr">{{ _("Phone Number") }}</div>
|
||||
</div>
|
||||
</th>
|
||||
|
||||
|
||||
<th class="align-middle white-space-nowrap text-uppercase" scope="col" style="width: 10%;">
|
||||
<div class="d-inline-flex flex-center">
|
||||
<div class="d-flex align-items-center bg-warning-subtle rounded me-2"><span class="text-warning-dark" data-feather="zap"></span></div>
|
||||
@ -131,10 +131,27 @@
|
||||
|
||||
<td class="align-middle white-space-nowrap fw-semibold"><a class="text-body-highlight" href="">{{ lead.id_car_make.get_local_name }} - {{ lead.id_car_model.get_local_name }} {{ lead.year }}</a></td>
|
||||
<td class="align-middle white-space-nowrap fw-semibold"><a class="text-body-highlight" href="">{{ lead.email }}</a></td>
|
||||
<td class="align-middle white-space-nowrap fw-semibold"><a class="text-body-highlight" href="tel:{{ lead.phone_number }}">{{ lead.phone_number }}</a></td>
|
||||
<td class="align-middle white-space-nowrap fw-semibold"><a class="text-body-highlight" href="tel:{{ lead.phone_number }}">{{ lead.phone_number }}</a></td>
|
||||
<td class="align-middle white-space-nowrap text-body-tertiary text-opacity-85 fw-semibold text-body-highlight">{{ lead.next_action|upper }}</td>
|
||||
<td class="align-middle white-space-nowrap fw-semibold">{{ lead.next_action_date|upper }}</td>
|
||||
<td class="align-middle white-space-nowrap text-body-tertiary text-opacity-85 fw-semibold text-body-highlight">{{ lead.staff|upper }}</td>
|
||||
<td class="align-middle white-space-nowrap text-body-tertiary text-opacity-85 fw-semibold text-body-highlight">
|
||||
<div class="d-flex align-items-center">
|
||||
<div class="avatar avatar-tiny me-2">
|
||||
{% if lead.staff.logo %}
|
||||
<img class="avatar-img rounded-circle" src="{{ lead.staff.thumbnail.url }}" onerror="this.src='/static/img/brand/brand-logo.png'" alt="Logo">
|
||||
{% endif %}
|
||||
</div>
|
||||
<small>
|
||||
{% if lead.staff == request.staff %}
|
||||
{{ _("Me") }}
|
||||
{% elif LANGUAGE_CODE == "en" %}
|
||||
{{ lead.staff.name|capfirst }}
|
||||
{% else %}
|
||||
{{ lead.staff.arabic_name }}
|
||||
{% endif %}
|
||||
</small>
|
||||
</div>
|
||||
</td>
|
||||
{% comment %} <td class="align-middle white-space-nowrap text-body-tertiary text-opacity-85 fw-semibold text-body-highlight">
|
||||
{% if lead.opportunity.stage == "prospect" %}
|
||||
<span class="badge text-bg-primary">{{ lead.opportunity.stage|upper }}</span>
|
||||
|
||||
@ -413,10 +413,10 @@
|
||||
<li class="nav-item dropdown">
|
||||
<a class="nav-link lh-1 pe-0" id="navbarDropdownUser" role="button" data-bs-toggle="dropdown" data-bs-auto-close="outside" aria-haspopup="true" aria-expanded="false">
|
||||
<div class="avatar avatar-l text-center align-middle">
|
||||
{% if user.dealer.logo %}
|
||||
{% if request.is_dealer and user.dealer.logo %}
|
||||
<img class="rounded-circle" src="{{ user.dealer.logo.url }}" alt="" />
|
||||
{% elif user.staff.dealer.logo %}
|
||||
<img class="rounded-circle" src="{{ user.staff.dealer.logo.url }}" alt="" />
|
||||
{% elif request.is_staff and request.staff.logo %}
|
||||
<img class="rounded-circle" src="{{ request.staff.thumbnail.url }}" alt="" />
|
||||
{% else %}
|
||||
<span class="fa fa-user text-body-tertiary fa-2x" style="width: 32px;"></span>
|
||||
{% endif %}
|
||||
@ -429,8 +429,8 @@
|
||||
<div class="avatar avatar-xl">
|
||||
{% if user.dealer.logo %}
|
||||
<img class="rounded-circle" src="{{ user.dealer.logo.url }}" alt="" />
|
||||
{% elif user.staff.dealer.logo %}
|
||||
<img class="rounded-circle" src="{{ user.staff.dealer.logo.url }}" alt="" />
|
||||
{% elif request.is_staff and request.staff.logo %}
|
||||
<img class="rounded-circle" src="{{ request.staff.thumbnail.url }}" alt="" />
|
||||
{% else %}
|
||||
<span class="fa fa-user text-body-tertiary fa-2x" style="width: 32px;"></span>
|
||||
{% endif %}
|
||||
|
||||
@ -28,7 +28,7 @@
|
||||
aria-label="Close"></button>
|
||||
</div>
|
||||
{% endif %}
|
||||
|
||||
|
||||
<!---->
|
||||
<div class="row justify-content-center mt-5 mb-3 {% if not vendor_exists %}disabled{% endif %}">
|
||||
|
||||
@ -46,7 +46,7 @@
|
||||
<div class="d-flex flex-column">
|
||||
<div class="d-flex flex-column flex-sm-grow-1 p-0">
|
||||
<div class="row g-4">
|
||||
|
||||
|
||||
<!-- VIN Section -->
|
||||
<div class="col-lg-12 ">
|
||||
<div class="card bg-body mb-3 shadow-sm">
|
||||
@ -213,35 +213,39 @@
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
|
||||
|
||||
|
||||
<hr class="my-2">
|
||||
<!--Save Buttons-->
|
||||
<div class="d-grid gap-2 d-md-flex justify-content-md-center mt-3">
|
||||
|
||||
<button type="submit"
|
||||
hx-on-before-request="this.setAttribute('disabled','true')"
|
||||
hx-on-after-request="this.removeAttribute('disabled')"
|
||||
name="add_another"
|
||||
value="true"
|
||||
class="btn btn-lg btn-phoenix-success md-me-2">
|
||||
|
||||
{% trans "Save and Add Another" %}
|
||||
</button>
|
||||
<button type="submit"
|
||||
hx-on-before-request="this.setAttribute('disabled','true')"
|
||||
hx-on-after-request="this.removeAttribute('disabled')"
|
||||
name="go_to_stats"
|
||||
value="true"
|
||||
class="btn btn-lg btn-phoenix-primary">
|
||||
{% trans "Save and Go to Inventory" %}
|
||||
</button>
|
||||
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
|
||||
|
||||
|
||||
</form>
|
||||
|
||||
|
||||
|
||||
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@ -56,11 +56,11 @@
|
||||
</h3>
|
||||
</div>
|
||||
<div class="card-body bg-light-subtle">
|
||||
|
||||
|
||||
<form method="post" action="">
|
||||
{% csrf_token %}
|
||||
{{ form|crispy }}
|
||||
|
||||
|
||||
<hr class="my-2">
|
||||
<div class="d-grid gap-2 d-md-flex justify-content-md-center mt-3">
|
||||
<button class="btn btn-lg btn-phoenix-success md-me-2" type="submit"><i class="fa-solid fa-floppy-disk me-1"></i>{{ _("Save") }}</button>
|
||||
@ -68,7 +68,7 @@
|
||||
</div>
|
||||
</form>
|
||||
|
||||
|
||||
|
||||
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@ -12,14 +12,14 @@
|
||||
{% endblock %}
|
||||
|
||||
{% block content %}
|
||||
{% comment %}
|
||||
{% comment %}
|
||||
<div class="row my-5">
|
||||
<!-- Display Form Errors -->
|
||||
<div class="card shadow rounded bg-body">
|
||||
<div class="card-header">
|
||||
<h4 class="mb-3">
|
||||
{% if account.created %}
|
||||
<!--<i class="bi bi-pencil-square"></i>-->
|
||||
<!--<i class="bi bi-pencil-square"></i>-->
|
||||
<i class="fa-solid fa-book"></i> {{ _("Edit Account") }}
|
||||
{% else %}
|
||||
<!--<i class="bi bi-person-plus"></i> -->
|
||||
@ -57,16 +57,16 @@
|
||||
<div class="card-header bg-gray-200 py-3 border-0 rounded-top-3">
|
||||
<h3 class="mb-0 fs-4 text-center text-white">
|
||||
{% if account.created %}
|
||||
|
||||
|
||||
<i class="fa-solid fa-book"></i> {{ _("Edit Account") }}
|
||||
{% else %}
|
||||
|
||||
|
||||
<i class="fa-solid fa-book"></i> {{ _("Add Account") }}
|
||||
{% endif %}
|
||||
</h3>
|
||||
</div>
|
||||
<div class="card-body bg-light-subtle">
|
||||
|
||||
|
||||
<form method="post" class="form" novalidate>
|
||||
{% csrf_token %}
|
||||
{{ form|crispy }}
|
||||
@ -75,12 +75,18 @@
|
||||
{% endfor %}
|
||||
<hr class="my-2">
|
||||
<div class="d-grid gap-2 d-md-flex justify-content-md-center mt-3">
|
||||
<button class="btn btn-lg btn-phoenix-success md-me-2" type="submit"><i class="fa-solid fa-floppy-disk me-1"></i>{{ _("Save") }}</button>
|
||||
<button
|
||||
|
||||
class="btn btn-lg btn-phoenix-success md-me-2"
|
||||
type="submit">
|
||||
<i class="saveBtnIcon fa-solid fa-floppy-disk me-1"></i>{{ _("Save") }}
|
||||
</button>
|
||||
|
||||
<a href="{{request.META.HTTP_REFERER}}" class="btn btn-lg btn-phoenix-danger"><i class="fa-solid fa-ban me-1"></i>{% trans "Cancel" %}</a>
|
||||
</div>
|
||||
|
||||
</form>
|
||||
|
||||
|
||||
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@ -66,7 +66,8 @@
|
||||
let counter = document.getElementById('notification-counter');
|
||||
let notificationsContainer = document.getElementById('notifications-container');
|
||||
let eventSource = null;
|
||||
|
||||
const notificationSound = new Audio('/static/sounds/tone.wav');
|
||||
notificationSound.volume = 0.3;
|
||||
|
||||
let initialUnreadCount = {{ notifications_.count|default:0 }};
|
||||
updateCounter(initialUnreadCount);
|
||||
@ -129,6 +130,11 @@
|
||||
const notificationElement = createNotificationElement(data);
|
||||
notificationsContainer.insertAdjacentHTML('afterbegin', notificationElement);
|
||||
|
||||
notificationSound.currentTime = 0;
|
||||
notificationSound.play().catch(e => {
|
||||
console.log("Audio play failed - may need user interaction first:", e);
|
||||
});
|
||||
|
||||
} catch (error) {
|
||||
console.error('Error processing notification:', error);
|
||||
}
|
||||
|
||||
@ -9,7 +9,6 @@
|
||||
<div class="fs-10 d-block">{{task.scheduled_type|capfirst}}</div>
|
||||
</td>
|
||||
<td class="sent align-middle white-space-nowrap text-start fw-thin text-body-tertiary py-2">{{task.notes}}</td>
|
||||
<td class="sent align-middle white-space-nowrap text-start fw-bold text-body-tertiary py-2">{{task.scheduled_by}}</td>
|
||||
<td class="date align-middle white-space-nowrap text-body py-2">{{task.created_at|naturalday|capfirst}}</td>
|
||||
<td class="date align-middle white-space-nowrap text-body py-2">
|
||||
{% if task.completed %}
|
||||
|
||||
@ -10,7 +10,7 @@
|
||||
{% endif %}
|
||||
{% endblock %}
|
||||
{% block content %}
|
||||
|
||||
|
||||
<!---->
|
||||
<div class="row justify-content-center mt-5 mb-3">
|
||||
|
||||
@ -35,10 +35,10 @@
|
||||
<button class="btn btn-lg btn-phoenix-success md-me-2" type="submit"><i class="fa-solid fa-floppy-disk me-1"></i>{{ _("Save") }}</button>
|
||||
<a href="{{request.META.HTTP_REFERER}}" class="btn btn-lg btn-phoenix-danger"><i class="fa-solid fa-ban me-1"></i>{% trans "Cancel" %}</a>
|
||||
</div>
|
||||
|
||||
|
||||
</form>
|
||||
|
||||
|
||||
|
||||
|
||||
</div>
|
||||
</div>
|
||||
|
||||
14
templates/shared/submit_button.html
Normal file
@ -0,0 +1,14 @@
|
||||
<button type="submit" id="submitBtn" class="btn btn-lg btn-phoenix-success md-me-2" onclick="loadingSpinner()">
|
||||
<i id="loadingSpinner" class="submitIcon fa-solid fa-floppy-disk me-1"></i>{{button_text}}
|
||||
</button>
|
||||
<script>
|
||||
function loadingSpinner() {
|
||||
var spinner = document.getElementById("loadingSpinner");
|
||||
var button = document.getElementById("submitBtn");
|
||||
button.disabled = true;
|
||||
spinner.classList.add("fa-spinner");
|
||||
spinner.classList.add("fa-spin");
|
||||
|
||||
return true;
|
||||
}
|
||||
</script>
|
||||
@ -12,11 +12,18 @@
|
||||
</div>
|
||||
<div class="card-body">
|
||||
<div class="row">
|
||||
<div class="col-md-6">
|
||||
<div class="col-md-2">
|
||||
{% if user_.logo %}
|
||||
<img class="rounded-circle" src="{{ user_.logo.url }}" style="width: 100px; height: 100px;" alt="User Image">
|
||||
{% else %}
|
||||
<img class="rounded-circle" src="{% static 'img/default-user.png' %}" style="width: 100px; height: 100px;" alt="Default User Image">
|
||||
{% endif %}
|
||||
</div>
|
||||
<div class="col-md-5">
|
||||
<p><strong>{{ _("Name") }}:</strong> {{ user_.name }}</p>
|
||||
<p><strong>{{ _("Arabic Name") }}:</strong> {{ user_.arabic_name }}</p>
|
||||
</div>
|
||||
<div class="col-md-6">
|
||||
<div class="col-md-5">
|
||||
<p><strong>{{ _("Phone Number") }}:</strong> {{ user_.phone_number }}</p>
|
||||
<div>
|
||||
<span><strong>{{ _("Roles") }}:</strong></span>
|
||||
@ -31,7 +38,7 @@
|
||||
</div>
|
||||
</div>
|
||||
<div class="table-responsive scrollbar mx-n1 px-1">
|
||||
<div class="card-header ">
|
||||
<div class="card-header ">
|
||||
|
||||
</div>
|
||||
<h4 class="my-4">Groups</h4>
|
||||
|
||||
@ -32,7 +32,7 @@
|
||||
<div class="row">
|
||||
<div class="col-sm-9">
|
||||
|
||||
<form class="row g-3 mb-9" method="post" class="form" novalidate>
|
||||
<form class="row g-3 mb-9" method="post" class="form" enctype="multipart/form-data" novalidate>
|
||||
{% csrf_token %}
|
||||
{{ redirect_field }}
|
||||
{{ form.name|as_crispy_field }}
|
||||
@ -40,7 +40,7 @@
|
||||
{{ form.email|as_crispy_field }}
|
||||
{{ form.phone_number|as_crispy_field }}
|
||||
{{ form.address|as_crispy_field }}
|
||||
{{ form.image|as_crispy_field }}
|
||||
{{ form.logo|as_crispy_field }}
|
||||
{{ form.group|as_crispy_field }}
|
||||
{% for error in form.errors %}
|
||||
<div class="text-danger">{{ error }}</div>
|
||||
|
||||
@ -39,7 +39,12 @@
|
||||
{% for user in users %}
|
||||
<tr>
|
||||
<td class="align-middle white-space-nowrap ps-1">
|
||||
<div>
|
||||
<div class="d-flex align-items-center">
|
||||
<div class="avatar avatar-tiny me-2">
|
||||
{% if user.logo %}
|
||||
<img class="avatar-img rounded-circle" src="{{ user.thumbnail.url }}" onerror="this.src='/static/img/brand/brand-logo.png'" alt="Logo">
|
||||
{% endif %}
|
||||
</div>
|
||||
<a class="fs-8 fw-bold" href="{% url 'user_detail' request.dealer.slug user.slug%}">{{ user.arabic_name }}</a>
|
||||
</div>
|
||||
</td>
|
||||
|
||||