Merge pull request 'Audit log changes' (#70) from frontend into main
Reviewed-on: #70
This commit is contained in:
commit
cba8a39d1b
@ -1,10 +1,12 @@
|
|||||||
#!/bin/sh
|
#!/bin/sh
|
||||||
echo "Delete Old Migrations"
|
echo "Delete Old Migrations"
|
||||||
find ./inventory -type f -iname "00*.py" -delete
|
find ./inventory -type f -iname "00*.py" -delete
|
||||||
|
find ./haikalbot -type f -iname "00*.py" -delete
|
||||||
|
|
||||||
echo "Delete Old Cache"
|
echo "Delete Old Cache"
|
||||||
find ./car_inventory -type d -iname "__pycache__"|xargs rm -rf
|
find ./car_inventory -type d -iname "__pycache__"|xargs rm -rf
|
||||||
find ./inventory -type d -iname "__pycache__"|xargs rm -rf
|
find ./inventory -type d -iname "__pycache__"|xargs rm -rf
|
||||||
|
find ./haikalbot -type d -iname "__pycache__"|xargs rm -rf
|
||||||
|
|
||||||
|
|
||||||
echo "Apply Base Migrate"
|
echo "Apply Base Migrate"
|
||||||
|
|||||||
@ -1,23 +0,0 @@
|
|||||||
# Generated by Django 5.2.1 on 2025-05-25 23:01
|
|
||||||
|
|
||||||
from django.db import migrations, models
|
|
||||||
|
|
||||||
|
|
||||||
class Migration(migrations.Migration):
|
|
||||||
|
|
||||||
initial = True
|
|
||||||
|
|
||||||
dependencies = [
|
|
||||||
]
|
|
||||||
|
|
||||||
operations = [
|
|
||||||
migrations.CreateModel(
|
|
||||||
name='ChatLog',
|
|
||||||
fields=[
|
|
||||||
('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
|
|
||||||
('user_message', models.TextField()),
|
|
||||||
('chatbot_response', models.TextField()),
|
|
||||||
('timestamp', models.DateTimeField(auto_now_add=True)),
|
|
||||||
],
|
|
||||||
),
|
|
||||||
]
|
|
||||||
@ -1,22 +0,0 @@
|
|||||||
# Generated by Django 5.2.1 on 2025-05-25 23:01
|
|
||||||
|
|
||||||
import django.db.models.deletion
|
|
||||||
from django.db import migrations, models
|
|
||||||
|
|
||||||
|
|
||||||
class Migration(migrations.Migration):
|
|
||||||
|
|
||||||
initial = True
|
|
||||||
|
|
||||||
dependencies = [
|
|
||||||
('haikalbot', '0001_initial'),
|
|
||||||
('inventory', '0001_initial'),
|
|
||||||
]
|
|
||||||
|
|
||||||
operations = [
|
|
||||||
migrations.AddField(
|
|
||||||
model_name='chatlog',
|
|
||||||
name='dealer',
|
|
||||||
field=models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, related_name='chatlogs', to='inventory.dealer'),
|
|
||||||
),
|
|
||||||
]
|
|
||||||
@ -1,33 +0,0 @@
|
|||||||
# Generated by Django 5.2.1 on 2025-05-26 00:28
|
|
||||||
|
|
||||||
import django.db.models.deletion
|
|
||||||
import django.utils.timezone
|
|
||||||
from django.conf import settings
|
|
||||||
from django.db import migrations, models
|
|
||||||
|
|
||||||
|
|
||||||
class Migration(migrations.Migration):
|
|
||||||
|
|
||||||
dependencies = [
|
|
||||||
('haikalbot', '0002_initial'),
|
|
||||||
migrations.swappable_dependency(settings.AUTH_USER_MODEL),
|
|
||||||
]
|
|
||||||
|
|
||||||
operations = [
|
|
||||||
migrations.CreateModel(
|
|
||||||
name='AnalysisCache',
|
|
||||||
fields=[
|
|
||||||
('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
|
|
||||||
('prompt_hash', models.CharField(db_index=True, max_length=64)),
|
|
||||||
('dealer_id', models.IntegerField(blank=True, db_index=True, null=True)),
|
|
||||||
('created_at', models.DateTimeField(default=django.utils.timezone.now)),
|
|
||||||
('updated_at', models.DateTimeField(auto_now=True)),
|
|
||||||
('expires_at', models.DateTimeField()),
|
|
||||||
('result', models.JSONField()),
|
|
||||||
('user', models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.CASCADE, to=settings.AUTH_USER_MODEL)),
|
|
||||||
],
|
|
||||||
options={
|
|
||||||
'indexes': [models.Index(fields=['prompt_hash', 'dealer_id'], name='haikalbot_a_prompt__b98e1e_idx'), models.Index(fields=['expires_at'], name='haikalbot_a_expires_e790cd_idx')],
|
|
||||||
},
|
|
||||||
),
|
|
||||||
]
|
|
||||||
@ -1,36 +0,0 @@
|
|||||||
# Generated by Django 5.2.1 on 2025-05-26 08:17
|
|
||||||
|
|
||||||
from django.db import migrations, models
|
|
||||||
|
|
||||||
|
|
||||||
class Migration(migrations.Migration):
|
|
||||||
|
|
||||||
dependencies = [
|
|
||||||
('haikalbot', '0003_analysiscache'),
|
|
||||||
('inventory', '0001_initial'),
|
|
||||||
]
|
|
||||||
|
|
||||||
operations = [
|
|
||||||
migrations.AlterModelOptions(
|
|
||||||
name='analysiscache',
|
|
||||||
options={'verbose_name_plural': 'Analysis caches'},
|
|
||||||
),
|
|
||||||
migrations.AlterModelOptions(
|
|
||||||
name='chatlog',
|
|
||||||
options={'ordering': ['-timestamp']},
|
|
||||||
),
|
|
||||||
migrations.AlterField(
|
|
||||||
model_name='analysiscache',
|
|
||||||
name='expires_at',
|
|
||||||
field=models.DateTimeField(db_index=True),
|
|
||||||
),
|
|
||||||
migrations.AlterField(
|
|
||||||
model_name='chatlog',
|
|
||||||
name='timestamp',
|
|
||||||
field=models.DateTimeField(auto_now_add=True, db_index=True),
|
|
||||||
),
|
|
||||||
migrations.AddIndex(
|
|
||||||
model_name='chatlog',
|
|
||||||
index=models.Index(fields=['dealer', 'timestamp'], name='haikalbot_c_dealer__6f8d63_idx'),
|
|
||||||
),
|
|
||||||
]
|
|
||||||
@ -1,845 +0,0 @@
|
|||||||
# Generated by Django 5.2.1 on 2025-05-25 23:01
|
|
||||||
|
|
||||||
import datetime
|
|
||||||
import django.core.validators
|
|
||||||
import django.db.models.deletion
|
|
||||||
import django.utils.timezone
|
|
||||||
import inventory.mixins
|
|
||||||
import inventory.models
|
|
||||||
import phonenumber_field.modelfields
|
|
||||||
import uuid
|
|
||||||
from decimal import Decimal
|
|
||||||
from django.conf import settings
|
|
||||||
from django.db import migrations, models
|
|
||||||
|
|
||||||
|
|
||||||
class Migration(migrations.Migration):
|
|
||||||
|
|
||||||
initial = True
|
|
||||||
|
|
||||||
dependencies = [
|
|
||||||
('appointment', '0001_initial'),
|
|
||||||
('auth', '0012_alter_user_first_name_max_length'),
|
|
||||||
('contenttypes', '0002_remove_content_type_name'),
|
|
||||||
('django_ledger', '0001_initial'),
|
|
||||||
migrations.swappable_dependency(settings.AUTH_USER_MODEL),
|
|
||||||
]
|
|
||||||
|
|
||||||
operations = [
|
|
||||||
migrations.CreateModel(
|
|
||||||
name='CarEquipment',
|
|
||||||
fields=[
|
|
||||||
('id_car_equipment', 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)),
|
|
||||||
('year_begin', models.IntegerField(blank=True, null=True)),
|
|
||||||
('slug', models.SlugField(blank=True, max_length=255, null=True, unique=True)),
|
|
||||||
],
|
|
||||||
options={
|
|
||||||
'verbose_name': 'Equipment',
|
|
||||||
},
|
|
||||||
bases=(models.Model, inventory.mixins.LocalizedNameMixin),
|
|
||||||
),
|
|
||||||
migrations.CreateModel(
|
|
||||||
name='CarMake',
|
|
||||||
fields=[
|
|
||||||
('id_car_make', models.AutoField(primary_key=True, serialize=False)),
|
|
||||||
('name', models.CharField(blank=True, max_length=255, null=True)),
|
|
||||||
('slug', models.SlugField(blank=True, max_length=255, null=True, unique=True)),
|
|
||||||
('arabic_name', models.CharField(blank=True, max_length=255, null=True)),
|
|
||||||
('logo', models.ImageField(blank=True, null=True, upload_to='car_make', verbose_name='logo')),
|
|
||||||
('is_sa_import', models.BooleanField(default=False)),
|
|
||||||
('car_type', models.SmallIntegerField(blank=True, choices=[(1, 'Car'), (2, 'Light Commercial'), (3, 'Heavy-Duty Tractors'), (4, 'Trailers'), (5, 'Medium Trucks'), (6, 'Buses'), (20, 'Motorcycles'), (21, 'Buggy'), (22, 'Moto ATV'), (23, 'Scooters'), (24, 'Karting'), (25, 'ATV'), (26, 'Snowmobiles')], null=True)),
|
|
||||||
],
|
|
||||||
options={
|
|
||||||
'verbose_name': 'Make',
|
|
||||||
},
|
|
||||||
bases=(models.Model, inventory.mixins.LocalizedNameMixin),
|
|
||||||
),
|
|
||||||
migrations.CreateModel(
|
|
||||||
name='ExteriorColors',
|
|
||||||
fields=[
|
|
||||||
('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
|
|
||||||
('name', models.CharField(max_length=255, verbose_name='Name')),
|
|
||||||
('arabic_name', models.CharField(max_length=255, verbose_name='Arabic Name')),
|
|
||||||
('rgb', models.CharField(blank=True, max_length=24, null=True, verbose_name='RGB')),
|
|
||||||
],
|
|
||||||
options={
|
|
||||||
'verbose_name': 'Exterior Colors',
|
|
||||||
'verbose_name_plural': 'Exterior Colors',
|
|
||||||
},
|
|
||||||
bases=(models.Model, inventory.mixins.LocalizedNameMixin),
|
|
||||||
),
|
|
||||||
migrations.CreateModel(
|
|
||||||
name='InteriorColors',
|
|
||||||
fields=[
|
|
||||||
('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
|
|
||||||
('name', models.CharField(max_length=255, verbose_name='Name')),
|
|
||||||
('arabic_name', models.CharField(max_length=255, verbose_name='Arabic Name')),
|
|
||||||
('rgb', models.CharField(blank=True, max_length=24, null=True, verbose_name='RGB')),
|
|
||||||
],
|
|
||||||
options={
|
|
||||||
'verbose_name': 'Interior Colors',
|
|
||||||
'verbose_name_plural': 'Interior Colors',
|
|
||||||
},
|
|
||||||
bases=(models.Model, inventory.mixins.LocalizedNameMixin),
|
|
||||||
),
|
|
||||||
migrations.CreateModel(
|
|
||||||
name='Payment',
|
|
||||||
fields=[
|
|
||||||
('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
|
|
||||||
('amount', models.DecimalField(decimal_places=2, max_digits=10, verbose_name='amount')),
|
|
||||||
('payment_method', models.CharField(choices=[('cash', 'cash'), ('credit', 'credit'), ('transfer', 'transfer'), ('debit', 'debit'), ('sadad', 'SADAD')], max_length=50, verbose_name='method')),
|
|
||||||
('reference_number', models.CharField(blank=True, max_length=100, null=True, verbose_name='reference number')),
|
|
||||||
('payment_date', models.DateField(auto_now_add=True, verbose_name='date')),
|
|
||||||
],
|
|
||||||
options={
|
|
||||||
'verbose_name': 'payment',
|
|
||||||
'verbose_name_plural': 'payments',
|
|
||||||
},
|
|
||||||
),
|
|
||||||
migrations.CreateModel(
|
|
||||||
name='VatRate',
|
|
||||||
fields=[
|
|
||||||
('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
|
|
||||||
('rate', models.DecimalField(decimal_places=2, default=Decimal('0.15'), max_digits=5)),
|
|
||||||
('is_active', models.BooleanField(default=True)),
|
|
||||||
('created_at', models.DateTimeField(auto_now_add=True)),
|
|
||||||
],
|
|
||||||
),
|
|
||||||
migrations.CreateModel(
|
|
||||||
name='AdditionalServices',
|
|
||||||
fields=[
|
|
||||||
('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
|
|
||||||
('name', models.CharField(max_length=255, verbose_name='Name')),
|
|
||||||
('arabic_name', models.CharField(max_length=255, verbose_name='Arabic Name')),
|
|
||||||
('description', models.TextField(verbose_name='Description')),
|
|
||||||
('price', models.DecimalField(decimal_places=2, max_digits=14, verbose_name='Price')),
|
|
||||||
('taxable', models.BooleanField(default=False, verbose_name='taxable')),
|
|
||||||
('uom', models.CharField(choices=[('EA', 'Each'), ('PR', 'Pair'), ('SET', 'Set'), ('GAL', 'Gallon'), ('L', 'Liter'), ('M', 'Meter'), ('KG', 'Kilogram'), ('HR', 'Hour'), ('BX', 'Box'), ('RL', 'Roll'), ('PKG', 'Package'), ('DZ', 'Dozen'), ('SQ_M', 'Square Meter'), ('PC', 'Piece'), ('BDL', 'Bundle')], max_length=10, verbose_name='Unit of Measurement')),
|
|
||||||
('item', models.OneToOneField(blank=True, null=True, on_delete=django.db.models.deletion.CASCADE, to='django_ledger.itemmodel', verbose_name='Item')),
|
|
||||||
],
|
|
||||||
options={
|
|
||||||
'verbose_name': 'Additional Services',
|
|
||||||
'verbose_name_plural': 'Additional Services',
|
|
||||||
},
|
|
||||||
bases=(models.Model, inventory.mixins.LocalizedNameMixin),
|
|
||||||
),
|
|
||||||
migrations.CreateModel(
|
|
||||||
name='Car',
|
|
||||||
fields=[
|
|
||||||
('id', models.UUIDField(default=uuid.uuid4, editable=False, primary_key=True, serialize=False, unique=True, verbose_name='Primary Key')),
|
|
||||||
('slug', models.SlugField(blank=True, help_text='Slug for the object. If not provided, it will be generated automatically.', null=True, unique=True, verbose_name='Slug')),
|
|
||||||
('created_at', models.DateTimeField(auto_now_add=True, verbose_name='Created At')),
|
|
||||||
('updated_at', models.DateTimeField(auto_now=True, verbose_name='Updated At')),
|
|
||||||
('vin', models.CharField(max_length=17, unique=True, verbose_name='VIN')),
|
|
||||||
('year', models.IntegerField(verbose_name='Year')),
|
|
||||||
('status', models.CharField(choices=[('available', 'Available'), ('sold', 'Sold'), ('hold', 'Hold'), ('damaged', 'Damaged'), ('reserved', 'Reserved'), ('transfer', 'Transfer')], default='available', max_length=10, verbose_name='Status')),
|
|
||||||
('stock_type', models.CharField(choices=[('new', 'New'), ('used', 'Used')], default='new', max_length=10, verbose_name='Stock Type')),
|
|
||||||
('remarks', models.TextField(blank=True, null=True, verbose_name='Remarks')),
|
|
||||||
('mileage', models.IntegerField(blank=True, null=True, verbose_name='Mileage')),
|
|
||||||
('receiving_date', models.DateTimeField(verbose_name='Receiving Date')),
|
|
||||||
('hash', models.CharField(blank=True, max_length=64, null=True, verbose_name='Hash')),
|
|
||||||
('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')),
|
|
||||||
],
|
|
||||||
options={
|
|
||||||
'verbose_name': 'Car',
|
|
||||||
'verbose_name_plural': 'Cars',
|
|
||||||
},
|
|
||||||
),
|
|
||||||
migrations.CreateModel(
|
|
||||||
name='CarFinance',
|
|
||||||
fields=[
|
|
||||||
('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
|
|
||||||
('cost_price', models.DecimalField(decimal_places=2, max_digits=14, verbose_name='Cost Price')),
|
|
||||||
('selling_price', models.DecimalField(decimal_places=2, max_digits=14, verbose_name='Selling Price')),
|
|
||||||
('discount_amount', models.DecimalField(decimal_places=2, default=Decimal('0.00'), max_digits=14, verbose_name='Discount Amount')),
|
|
||||||
('additional_services', models.ManyToManyField(blank=True, related_name='additional_finances', to='inventory.additionalservices')),
|
|
||||||
('car', models.OneToOneField(on_delete=django.db.models.deletion.CASCADE, related_name='finances', to='inventory.car')),
|
|
||||||
],
|
|
||||||
options={
|
|
||||||
'verbose_name': 'Car Financial Details',
|
|
||||||
'verbose_name_plural': 'Car Financial Details',
|
|
||||||
},
|
|
||||||
),
|
|
||||||
migrations.CreateModel(
|
|
||||||
name='CarModel',
|
|
||||||
fields=[
|
|
||||||
('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=[
|
|
||||||
('id_car_option', 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_parent', models.ForeignKey(blank=True, db_column='id_parent', null=True, on_delete=django.db.models.deletion.DO_NOTHING, to='inventory.caroption')),
|
|
||||||
],
|
|
||||||
options={
|
|
||||||
'verbose_name': 'Option',
|
|
||||||
},
|
|
||||||
bases=(models.Model, inventory.mixins.LocalizedNameMixin),
|
|
||||||
),
|
|
||||||
migrations.CreateModel(
|
|
||||||
name='CarOptionValue',
|
|
||||||
fields=[
|
|
||||||
('id_car_option_value', models.AutoField(primary_key=True, serialize=False)),
|
|
||||||
('value', models.CharField(max_length=500)),
|
|
||||||
('unit', models.CharField(blank=True, max_length=255, null=True)),
|
|
||||||
('is_base', models.IntegerField()),
|
|
||||||
('id_car_equipment', models.ForeignKey(db_column='id_car_equipment', on_delete=django.db.models.deletion.DO_NOTHING, to='inventory.carequipment')),
|
|
||||||
('id_car_option', models.ForeignKey(db_column='id_car_option', on_delete=django.db.models.deletion.DO_NOTHING, to='inventory.caroption')),
|
|
||||||
],
|
|
||||||
options={
|
|
||||||
'verbose_name': 'Option Value',
|
|
||||||
},
|
|
||||||
),
|
|
||||||
migrations.CreateModel(
|
|
||||||
name='CarRegistration',
|
|
||||||
fields=[
|
|
||||||
('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
|
|
||||||
('plate_number', models.IntegerField(verbose_name='Plate Number')),
|
|
||||||
('text1', models.CharField(max_length=1, verbose_name='Text 1')),
|
|
||||||
('text2', models.CharField(blank=True, max_length=1, null=True, verbose_name='Text 2')),
|
|
||||||
('text3', models.CharField(blank=True, max_length=1, null=True, verbose_name='Text 3')),
|
|
||||||
('registration_date', models.DateTimeField(verbose_name='Registration Date')),
|
|
||||||
('car', models.OneToOneField(on_delete=django.db.models.deletion.CASCADE, related_name='registrations', to='inventory.car', verbose_name='Car')),
|
|
||||||
],
|
|
||||||
options={
|
|
||||||
'verbose_name': 'Registration',
|
|
||||||
'verbose_name_plural': 'Registrations',
|
|
||||||
},
|
|
||||||
),
|
|
||||||
migrations.CreateModel(
|
|
||||||
name='CarSerie',
|
|
||||||
fields=[
|
|
||||||
('id_car_serie', 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)),
|
|
||||||
('year_begin', models.IntegerField(blank=True, null=True)),
|
|
||||||
('year_end', models.IntegerField(blank=True, null=True)),
|
|
||||||
('generation_name', models.CharField(blank=True, max_length=255, null=True)),
|
|
||||||
('slug', models.SlugField(blank=True, max_length=255, null=True, unique=True)),
|
|
||||||
('id_car_model', models.ForeignKey(db_column='id_car_model', on_delete=django.db.models.deletion.DO_NOTHING, to='inventory.carmodel')),
|
|
||||||
],
|
|
||||||
options={
|
|
||||||
'verbose_name': 'Series',
|
|
||||||
},
|
|
||||||
bases=(models.Model, inventory.mixins.LocalizedNameMixin),
|
|
||||||
),
|
|
||||||
migrations.AddField(
|
|
||||||
model_name='car',
|
|
||||||
name='id_car_serie',
|
|
||||||
field=models.ForeignKey(blank=True, db_column='id_car_serie', null=True, on_delete=django.db.models.deletion.DO_NOTHING, to='inventory.carserie', verbose_name='Series'),
|
|
||||||
),
|
|
||||||
migrations.CreateModel(
|
|
||||||
name='CarSpecification',
|
|
||||||
fields=[
|
|
||||||
('id_car_specification', models.AutoField(primary_key=True, serialize=False)),
|
|
||||||
('name', models.CharField(max_length=255)),
|
|
||||||
('arabic_name', models.CharField(max_length=255)),
|
|
||||||
('slug', models.SlugField(blank=True, max_length=255, null=True, unique=True)),
|
|
||||||
('id_parent', models.ForeignKey(blank=True, db_column='id_parent', null=True, on_delete=django.db.models.deletion.DO_NOTHING, to='inventory.carspecification')),
|
|
||||||
],
|
|
||||||
options={
|
|
||||||
'verbose_name': 'Specification',
|
|
||||||
},
|
|
||||||
bases=(models.Model, inventory.mixins.LocalizedNameMixin),
|
|
||||||
),
|
|
||||||
migrations.CreateModel(
|
|
||||||
name='CarTrim',
|
|
||||||
fields=[
|
|
||||||
('id_car_trim', models.AutoField(primary_key=True, serialize=False)),
|
|
||||||
('name', models.CharField(blank=True, max_length=255, null=True)),
|
|
||||||
('arabic_name', models.CharField(blank=True, max_length=255, null=True)),
|
|
||||||
('start_production_year', models.IntegerField(blank=True, null=True)),
|
|
||||||
('end_production_year', models.IntegerField(blank=True, null=True)),
|
|
||||||
('slug', models.SlugField(blank=True, max_length=255, null=True, unique=True)),
|
|
||||||
('id_car_serie', models.ForeignKey(db_column='id_car_serie', on_delete=django.db.models.deletion.DO_NOTHING, to='inventory.carserie')),
|
|
||||||
],
|
|
||||||
options={
|
|
||||||
'verbose_name': 'Trim',
|
|
||||||
},
|
|
||||||
bases=(models.Model, inventory.mixins.LocalizedNameMixin),
|
|
||||||
),
|
|
||||||
migrations.CreateModel(
|
|
||||||
name='CarSpecificationValue',
|
|
||||||
fields=[
|
|
||||||
('id_car_specification_value', models.AutoField(primary_key=True, serialize=False)),
|
|
||||||
('value', models.CharField(max_length=500)),
|
|
||||||
('unit', models.CharField(blank=True, max_length=255, null=True)),
|
|
||||||
('id_car_specification', models.ForeignKey(db_column='id_car_specification', on_delete=django.db.models.deletion.DO_NOTHING, to='inventory.carspecification')),
|
|
||||||
('id_car_trim', models.ForeignKey(db_column='id_car_trim', on_delete=django.db.models.deletion.DO_NOTHING, to='inventory.cartrim')),
|
|
||||||
],
|
|
||||||
options={
|
|
||||||
'verbose_name': 'Specification Value',
|
|
||||||
},
|
|
||||||
),
|
|
||||||
migrations.AddField(
|
|
||||||
model_name='carequipment',
|
|
||||||
name='id_car_trim',
|
|
||||||
field=models.ForeignKey(db_column='id_car_trim', on_delete=django.db.models.deletion.DO_NOTHING, to='inventory.cartrim'),
|
|
||||||
),
|
|
||||||
migrations.AddField(
|
|
||||||
model_name='car',
|
|
||||||
name='id_car_trim',
|
|
||||||
field=models.ForeignKey(blank=True, db_column='id_car_trim', null=True, on_delete=django.db.models.deletion.DO_NOTHING, to='inventory.cartrim', verbose_name='Trim'),
|
|
||||||
),
|
|
||||||
migrations.CreateModel(
|
|
||||||
name='CustomCard',
|
|
||||||
fields=[
|
|
||||||
('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
|
|
||||||
('custom_number', models.CharField(max_length=255, verbose_name='Custom Number')),
|
|
||||||
('custom_date', models.DateField(verbose_name='Custom Date')),
|
|
||||||
('car', models.OneToOneField(on_delete=django.db.models.deletion.CASCADE, related_name='custom_cards', to='inventory.car', verbose_name='Car')),
|
|
||||||
],
|
|
||||||
options={
|
|
||||||
'verbose_name': 'Custom Card',
|
|
||||||
'verbose_name_plural': 'Custom Cards',
|
|
||||||
},
|
|
||||||
),
|
|
||||||
migrations.CreateModel(
|
|
||||||
name='Dealer',
|
|
||||||
fields=[
|
|
||||||
('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
|
|
||||||
('crn', models.CharField(blank=True, max_length=10, null=True, verbose_name='Commercial Registration Number')),
|
|
||||||
('vrn', models.CharField(blank=True, max_length=15, null=True, verbose_name='VAT Registration Number')),
|
|
||||||
('arabic_name', models.CharField(max_length=255, verbose_name='Arabic Name')),
|
|
||||||
('name', models.CharField(max_length=255, verbose_name='English Name')),
|
|
||||||
('phone_number', phonenumber_field.modelfields.PhoneNumberField(max_length=128, region='SA', verbose_name='Phone Number')),
|
|
||||||
('address', models.CharField(blank=True, max_length=200, null=True, verbose_name='Address')),
|
|
||||||
('logo', models.ImageField(blank=True, null=True, upload_to='logos/users', verbose_name='Logo')),
|
|
||||||
('joined_at', models.DateTimeField(auto_now_add=True, verbose_name='Joined At')),
|
|
||||||
('updated_at', models.DateTimeField(auto_now=True, verbose_name='Updated At')),
|
|
||||||
('slug', models.SlugField(blank=True, max_length=255, null=True, unique=True)),
|
|
||||||
('entity', models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.SET_NULL, to='django_ledger.entitymodel')),
|
|
||||||
('user', models.OneToOneField(on_delete=django.db.models.deletion.CASCADE, related_name='dealer', to=settings.AUTH_USER_MODEL)),
|
|
||||||
],
|
|
||||||
options={
|
|
||||||
'verbose_name': 'Dealer',
|
|
||||||
'verbose_name_plural': 'Dealers',
|
|
||||||
},
|
|
||||||
bases=(models.Model, inventory.mixins.LocalizedNameMixin),
|
|
||||||
managers=[
|
|
||||||
('objects', inventory.models.DealerUserManager()),
|
|
||||||
],
|
|
||||||
),
|
|
||||||
migrations.CreateModel(
|
|
||||||
name='CustomGroup',
|
|
||||||
fields=[
|
|
||||||
('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
|
|
||||||
('name', models.CharField(max_length=100)),
|
|
||||||
('group', models.OneToOneField(on_delete=django.db.models.deletion.CASCADE, to='auth.group', verbose_name='Group')),
|
|
||||||
('dealer', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, related_name='groups', to='inventory.dealer')),
|
|
||||||
],
|
|
||||||
),
|
|
||||||
migrations.CreateModel(
|
|
||||||
name='Customer',
|
|
||||||
fields=[
|
|
||||||
('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
|
|
||||||
('title', models.CharField(choices=[('mr', 'Mr'), ('mrs', 'Mrs'), ('ms', 'Ms'), ('miss', 'Miss'), ('dr', 'Dr'), ('prof', 'Prof'), ('prince', 'Prince'), ('princess', 'Princess'), ('company', 'Company'), ('na', 'N/A')], default='na', max_length=10, verbose_name='Title')),
|
|
||||||
('first_name', models.CharField(max_length=50, verbose_name='First Name')),
|
|
||||||
('last_name', models.CharField(max_length=50, verbose_name='Last Name')),
|
|
||||||
('gender', models.CharField(choices=[('m', 'Male'), ('f', 'Female')], max_length=1, verbose_name='Gender')),
|
|
||||||
('dob', models.DateField(blank=True, null=True, verbose_name='Date of Birth')),
|
|
||||||
('email', models.EmailField(max_length=254, unique=True, verbose_name='Email')),
|
|
||||||
('national_id', models.CharField(blank=True, max_length=10, null=True, unique=True, verbose_name='National ID')),
|
|
||||||
('phone_number', phonenumber_field.modelfields.PhoneNumberField(max_length=128, region='SA', unique=True, verbose_name='Phone Number')),
|
|
||||||
('address', models.CharField(blank=True, max_length=200, null=True, verbose_name='Address')),
|
|
||||||
('active', models.BooleanField(default=True, verbose_name='Active')),
|
|
||||||
('image', models.ImageField(blank=True, null=True, upload_to='customers/', verbose_name='Image')),
|
|
||||||
('created', models.DateTimeField(auto_now_add=True, verbose_name='Created')),
|
|
||||||
('updated', models.DateTimeField(auto_now=True, verbose_name='Updated')),
|
|
||||||
('slug', models.SlugField(blank=True, editable=False, max_length=255, null=True, unique=True)),
|
|
||||||
('customer_model', models.ForeignKey(null=True, on_delete=django.db.models.deletion.SET_NULL, to='django_ledger.customermodel')),
|
|
||||||
('user', models.OneToOneField(on_delete=django.db.models.deletion.CASCADE, related_name='customer_profile', to=settings.AUTH_USER_MODEL)),
|
|
||||||
('dealer', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, related_name='customers', to='inventory.dealer')),
|
|
||||||
],
|
|
||||||
options={
|
|
||||||
'verbose_name': 'Customer',
|
|
||||||
'verbose_name_plural': 'Customers',
|
|
||||||
},
|
|
||||||
),
|
|
||||||
migrations.CreateModel(
|
|
||||||
name='CarTransfer',
|
|
||||||
fields=[
|
|
||||||
('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
|
|
||||||
('transfer_date', models.DateTimeField(auto_now_add=True, verbose_name='Transfer Date')),
|
|
||||||
('quantity', models.IntegerField(default=1, verbose_name='Quantity')),
|
|
||||||
('remarks', models.TextField(blank=True, null=True, verbose_name='Remarks')),
|
|
||||||
('status', models.CharField(default='draft', max_length=10, verbose_name=[('draft', 'Draft'), ('approved', 'Approved'), ('pending', 'Pending'), ('accepted', 'Accepted'), ('success', 'Success'), ('reject', 'Reject'), ('cancelled', 'Cancelled')])),
|
|
||||||
('is_approved', models.BooleanField(default=False)),
|
|
||||||
('active', models.BooleanField(default=True)),
|
|
||||||
('created_at', models.DateTimeField(auto_now_add=True, verbose_name='Created At')),
|
|
||||||
('updated_at', models.DateTimeField(auto_now=True, verbose_name='Updated At')),
|
|
||||||
('car', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, related_name='transfer_logs', to='inventory.car', verbose_name='Car')),
|
|
||||||
('from_dealer', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, related_name='transfers_out', to='inventory.dealer', verbose_name='From Dealer')),
|
|
||||||
('to_dealer', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, related_name='transfers_in', to='inventory.dealer', verbose_name='To Dealer')),
|
|
||||||
],
|
|
||||||
options={
|
|
||||||
'verbose_name': 'Car Transfer Log',
|
|
||||||
'verbose_name_plural': 'Car Transfer Logs',
|
|
||||||
},
|
|
||||||
),
|
|
||||||
migrations.CreateModel(
|
|
||||||
name='CarLocation',
|
|
||||||
fields=[
|
|
||||||
('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
|
|
||||||
('description', models.TextField(blank=True, help_text='Optional description about the showroom placement.', null=True, verbose_name='Description')),
|
|
||||||
('created_at', models.DateTimeField(auto_now_add=True, verbose_name='Created At')),
|
|
||||||
('updated_at', models.DateTimeField(auto_now=True, verbose_name='Last Updated')),
|
|
||||||
('car', models.OneToOneField(on_delete=django.db.models.deletion.CASCADE, related_name='location', to='inventory.car', verbose_name='Car')),
|
|
||||||
('owner', models.ForeignKey(help_text='Dealer who owns the car.', on_delete=django.db.models.deletion.CASCADE, related_name='owned_cars', to='inventory.dealer', verbose_name='Owner')),
|
|
||||||
('showroom', models.ForeignKey(help_text='Dealer where the car is displayed (can be the owner).', on_delete=django.db.models.deletion.CASCADE, related_name='showroom_cars', to='inventory.dealer', verbose_name='Showroom')),
|
|
||||||
],
|
|
||||||
options={
|
|
||||||
'verbose_name': 'Car Location',
|
|
||||||
'verbose_name_plural': 'Car Locations',
|
|
||||||
},
|
|
||||||
),
|
|
||||||
migrations.AddField(
|
|
||||||
model_name='car',
|
|
||||||
name='dealer',
|
|
||||||
field=models.ForeignKey(on_delete=django.db.models.deletion.DO_NOTHING, related_name='cars', to='inventory.dealer', verbose_name='Dealer'),
|
|
||||||
),
|
|
||||||
migrations.AddField(
|
|
||||||
model_name='additionalservices',
|
|
||||||
name='dealer',
|
|
||||||
field=models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to='inventory.dealer', verbose_name='Dealer'),
|
|
||||||
),
|
|
||||||
migrations.CreateModel(
|
|
||||||
name='Activity',
|
|
||||||
fields=[
|
|
||||||
('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
|
|
||||||
('object_id', models.PositiveIntegerField()),
|
|
||||||
('activity_type', models.CharField(choices=[('call', 'Call'), ('sms', 'SMS'), ('email', 'Email'), ('meeting', 'Meeting'), ('whatsapp', 'WhatsApp'), ('visit', 'Visit'), ('negotiation', 'Negotiation'), ('follow_up', 'Follow Up'), ('won', 'Won'), ('lost', 'Lost'), ('closed', 'Closed'), ('converted', 'Converted'), ('transfer', 'Transfer'), ('add_car', 'Add Car'), ('sale_car', 'Sale Car'), ('reserve_car', 'Reserve Car'), ('transfer_car', 'Transfer Car'), ('remove_car', 'Remove Car'), ('create_quotation', 'Create Quotation'), ('cancel_quotation', 'Cancel Quotation'), ('create_order', 'Create Order'), ('cancel_order', 'Cancel Order'), ('create_invoice', 'Create Invoice'), ('cancel_invoice', 'Cancel Invoice')], max_length=50, verbose_name='Activity Type')),
|
|
||||||
('notes', models.TextField(blank=True, null=True, verbose_name='Notes')),
|
|
||||||
('created', models.DateTimeField(auto_now_add=True, verbose_name='Created')),
|
|
||||||
('updated', models.DateTimeField(auto_now=True, verbose_name='Updated')),
|
|
||||||
('content_type', models.ForeignKey(on_delete=django.db.models.deletion.DO_NOTHING, to='contenttypes.contenttype')),
|
|
||||||
('created_by', models.ForeignKey(on_delete=django.db.models.deletion.DO_NOTHING, related_name='activities_created_by', to=settings.AUTH_USER_MODEL)),
|
|
||||||
('dealer', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, related_name='activities', to='inventory.dealer')),
|
|
||||||
],
|
|
||||||
options={
|
|
||||||
'verbose_name': 'Activity',
|
|
||||||
'verbose_name_plural': 'Activities',
|
|
||||||
},
|
|
||||||
),
|
|
||||||
migrations.CreateModel(
|
|
||||||
name='DealerSettings',
|
|
||||||
fields=[
|
|
||||||
('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
|
|
||||||
('additional_info', models.JSONField(blank=True, default=dict, null=True)),
|
|
||||||
('bill_cash_account', models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.SET_NULL, related_name='bill_cash', to='django_ledger.accountmodel')),
|
|
||||||
('bill_prepaid_account', models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.SET_NULL, related_name='bill_prepaid', to='django_ledger.accountmodel')),
|
|
||||||
('bill_unearned_account', models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.SET_NULL, related_name='bill_unearned', to='django_ledger.accountmodel')),
|
|
||||||
('dealer', models.OneToOneField(blank=True, null=True, on_delete=django.db.models.deletion.CASCADE, related_name='settings', to='inventory.dealer')),
|
|
||||||
('invoice_cash_account', models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.SET_NULL, related_name='invoice_cash', to='django_ledger.accountmodel')),
|
|
||||||
('invoice_prepaid_account', models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.SET_NULL, related_name='invoice_prepaid', to='django_ledger.accountmodel')),
|
|
||||||
('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='Email',
|
|
||||||
fields=[
|
|
||||||
('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
|
|
||||||
('object_id', models.UUIDField()),
|
|
||||||
('from_email', models.TextField(blank=True, null=True, verbose_name='From Email')),
|
|
||||||
('to_email', models.TextField(blank=True, null=True, verbose_name='To Email')),
|
|
||||||
('subject', models.TextField(blank=True, null=True, verbose_name='Subject')),
|
|
||||||
('message', models.TextField(blank=True, null=True, verbose_name='Message')),
|
|
||||||
('status', models.CharField(choices=[('SENT', 'Sent'), ('FAILED', 'Failed'), ('DELIVERED', 'Delivered'), ('OPEN', 'Open'), ('DRAFT', 'Draft')], default='OPEN', max_length=20, verbose_name='Status')),
|
|
||||||
('created', models.DateTimeField(auto_now_add=True, verbose_name='Created')),
|
|
||||||
('updated', models.DateTimeField(auto_now=True, verbose_name='Updated')),
|
|
||||||
('content_type', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to='contenttypes.contenttype')),
|
|
||||||
('created_by', models.ForeignKey(on_delete=django.db.models.deletion.DO_NOTHING, related_name='emails_created', to=settings.AUTH_USER_MODEL)),
|
|
||||||
],
|
|
||||||
options={
|
|
||||||
'verbose_name': 'Email',
|
|
||||||
'verbose_name_plural': 'Emails',
|
|
||||||
},
|
|
||||||
),
|
|
||||||
migrations.CreateModel(
|
|
||||||
name='Notes',
|
|
||||||
fields=[
|
|
||||||
('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
|
|
||||||
('object_id', models.UUIDField()),
|
|
||||||
('note', models.TextField(verbose_name='Note')),
|
|
||||||
('created', models.DateTimeField(auto_now_add=True, verbose_name='Created')),
|
|
||||||
('updated', models.DateTimeField(auto_now=True, verbose_name='Updated')),
|
|
||||||
('content_type', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to='contenttypes.contenttype')),
|
|
||||||
('created_by', models.ForeignKey(on_delete=django.db.models.deletion.DO_NOTHING, related_name='notes_created', to=settings.AUTH_USER_MODEL)),
|
|
||||||
('dealer', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, related_name='notes', to='inventory.dealer')),
|
|
||||||
],
|
|
||||||
options={
|
|
||||||
'verbose_name': 'Note',
|
|
||||||
'verbose_name_plural': 'Notes',
|
|
||||||
},
|
|
||||||
),
|
|
||||||
migrations.CreateModel(
|
|
||||||
name='Notification',
|
|
||||||
fields=[
|
|
||||||
('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
|
|
||||||
('message', models.CharField(max_length=255, verbose_name='Message')),
|
|
||||||
('is_read', models.BooleanField(default=False, verbose_name='Is Read')),
|
|
||||||
('created', models.DateTimeField(auto_now_add=True, verbose_name='Created')),
|
|
||||||
('user', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, related_name='notifications', to=settings.AUTH_USER_MODEL)),
|
|
||||||
],
|
|
||||||
options={
|
|
||||||
'verbose_name': 'Notification',
|
|
||||||
'verbose_name_plural': 'Notifications',
|
|
||||||
'ordering': ['-created'],
|
|
||||||
},
|
|
||||||
),
|
|
||||||
migrations.CreateModel(
|
|
||||||
name='Organization',
|
|
||||||
fields=[
|
|
||||||
('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
|
|
||||||
('name', models.CharField(max_length=255, verbose_name='Name')),
|
|
||||||
('arabic_name', models.CharField(max_length=255, verbose_name='Arabic Name')),
|
|
||||||
('crn', models.CharField(max_length=15, verbose_name='Commercial Registration Number')),
|
|
||||||
('vrn', models.CharField(max_length=15, verbose_name='VAT Registration Number')),
|
|
||||||
('email', models.EmailField(max_length=254, verbose_name='Email')),
|
|
||||||
('phone_number', phonenumber_field.modelfields.PhoneNumberField(max_length=128, region='SA', verbose_name='Phone Number')),
|
|
||||||
('address', models.CharField(blank=True, max_length=200, null=True, verbose_name='Address')),
|
|
||||||
('logo', models.ImageField(blank=True, null=True, upload_to='logos', verbose_name='Logo')),
|
|
||||||
('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')),
|
|
||||||
('slug', models.SlugField(blank=True, editable=False, max_length=255, null=True, unique=True)),
|
|
||||||
('customer_model', models.ForeignKey(null=True, on_delete=django.db.models.deletion.SET_NULL, to='django_ledger.customermodel')),
|
|
||||||
('dealer', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, related_name='organizations', to='inventory.dealer')),
|
|
||||||
('user', models.OneToOneField(on_delete=django.db.models.deletion.CASCADE, related_name='organization_profile', to=settings.AUTH_USER_MODEL)),
|
|
||||||
],
|
|
||||||
options={
|
|
||||||
'verbose_name': 'Organization',
|
|
||||||
'verbose_name_plural': 'Organizations',
|
|
||||||
},
|
|
||||||
bases=(models.Model, inventory.mixins.LocalizedNameMixin),
|
|
||||||
),
|
|
||||||
migrations.CreateModel(
|
|
||||||
name='Lead',
|
|
||||||
fields=[
|
|
||||||
('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
|
|
||||||
('first_name', models.CharField(max_length=50, verbose_name='First Name')),
|
|
||||||
('last_name', models.CharField(max_length=50, verbose_name='Last Name')),
|
|
||||||
('email', models.EmailField(max_length=254, verbose_name='Email')),
|
|
||||||
('phone_number', phonenumber_field.modelfields.PhoneNumberField(max_length=128, region='SA', verbose_name='Phone Number')),
|
|
||||||
('lead_type', models.CharField(choices=[('customer', 'Customer'), ('organization', 'Organization')], default='customer', max_length=50, verbose_name='Lead Type')),
|
|
||||||
('year', models.PositiveSmallIntegerField(blank=True, null=True, verbose_name='Year')),
|
|
||||||
('source', models.CharField(choices=[('referrals', 'Referrals'), ('whatsapp', 'WhatsApp'), ('showroom', 'Showroom'), ('tiktok', 'TikTok'), ('instagram', 'Instagram'), ('x', 'X'), ('facebook', 'Facebook'), ('motory', 'Motory'), ('influencers', 'Influencers'), ('youtube', 'Youtube'), ('campaign', 'Campaign')], max_length=50, verbose_name='Source')),
|
|
||||||
('channel', models.CharField(choices=[('walk_in', 'Walk In'), ('toll_free', 'Toll Free'), ('website', 'Website'), ('email', 'Email'), ('form', 'Form')], max_length=50, verbose_name='Channel')),
|
|
||||||
('crn', models.CharField(blank=True, max_length=10, null=True, unique=True, verbose_name='Commercial Registration Number')),
|
|
||||||
('vrn', models.CharField(blank=True, max_length=15, null=True, unique=True, verbose_name='VAT Registration Number')),
|
|
||||||
('address', models.CharField(max_length=50, verbose_name='address')),
|
|
||||||
('priority', models.CharField(choices=[('low', 'Low'), ('medium', 'Medium'), ('high', 'High')], default='medium', max_length=10, verbose_name='Priority')),
|
|
||||||
('status', models.CharField(choices=[('new', 'New'), ('follow_up', 'Follow-up'), ('negotiation', 'Negotiation'), ('won', 'Won'), ('lost', 'Lost'), ('closed', 'Closed')], db_index=True, default='new', max_length=50, verbose_name='Status')),
|
|
||||||
('next_action', models.CharField(blank=True, max_length=255, null=True, verbose_name='Next Action')),
|
|
||||||
('next_action_date', models.DateTimeField(blank=True, null=True, verbose_name='Next Action Date')),
|
|
||||||
('is_converted', models.BooleanField(default=False)),
|
|
||||||
('converted_at', models.DateTimeField(blank=True, null=True)),
|
|
||||||
('salary', models.PositiveIntegerField(blank=True, null=True, verbose_name='Salary')),
|
|
||||||
('created', models.DateTimeField(auto_now_add=True, db_index=True, verbose_name='Created')),
|
|
||||||
('updated', models.DateTimeField(auto_now=True, verbose_name='Updated')),
|
|
||||||
('slug', models.SlugField(blank=True, null=True, unique=True)),
|
|
||||||
('customer', models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.CASCADE, related_name='customer_leads', to='inventory.customer')),
|
|
||||||
('dealer', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, related_name='leads', to='inventory.dealer')),
|
|
||||||
('id_car_make', models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.DO_NOTHING, to='inventory.carmake', verbose_name='Make')),
|
|
||||||
('id_car_model', models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.DO_NOTHING, to='inventory.carmodel', verbose_name='Model')),
|
|
||||||
('organization', models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.CASCADE, related_name='organization_leads', to='inventory.organization')),
|
|
||||||
],
|
|
||||||
options={
|
|
||||||
'verbose_name': 'Lead',
|
|
||||||
'verbose_name_plural': 'Leads',
|
|
||||||
},
|
|
||||||
),
|
|
||||||
migrations.CreateModel(
|
|
||||||
name='Refund',
|
|
||||||
fields=[
|
|
||||||
('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
|
|
||||||
('amount', models.DecimalField(decimal_places=2, max_digits=10, verbose_name='amount')),
|
|
||||||
('reason', models.TextField(blank=True, verbose_name='reason')),
|
|
||||||
('refund_date', models.DateField(auto_now_add=True, verbose_name='refund date')),
|
|
||||||
('payment', models.OneToOneField(on_delete=django.db.models.deletion.CASCADE, related_name='refund', to='inventory.payment')),
|
|
||||||
],
|
|
||||||
options={
|
|
||||||
'verbose_name': 'refund',
|
|
||||||
'verbose_name_plural': 'refunds',
|
|
||||||
},
|
|
||||||
),
|
|
||||||
migrations.CreateModel(
|
|
||||||
name='Representative',
|
|
||||||
fields=[
|
|
||||||
('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
|
|
||||||
('name', models.CharField(max_length=255, verbose_name='Name')),
|
|
||||||
('arabic_name', models.CharField(max_length=255, verbose_name='Arabic Name')),
|
|
||||||
('id_number', models.CharField(max_length=10, unique=True, verbose_name='ID Number')),
|
|
||||||
('phone_number', phonenumber_field.modelfields.PhoneNumberField(max_length=128, region='SA', verbose_name='Phone Number')),
|
|
||||||
('email', models.EmailField(max_length=255, verbose_name='Email Address')),
|
|
||||||
('address', models.CharField(blank=True, max_length=200, null=True, verbose_name='Address')),
|
|
||||||
('dealer', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, related_name='representatives', to='inventory.dealer')),
|
|
||||||
('organization', models.ManyToManyField(related_name='representatives', to='inventory.organization')),
|
|
||||||
],
|
|
||||||
options={
|
|
||||||
'verbose_name': 'Representative',
|
|
||||||
'verbose_name_plural': 'Representatives',
|
|
||||||
},
|
|
||||||
bases=(models.Model, inventory.mixins.LocalizedNameMixin),
|
|
||||||
),
|
|
||||||
migrations.CreateModel(
|
|
||||||
name='SaleOrder',
|
|
||||||
fields=[
|
|
||||||
('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
|
|
||||||
('payment_method', models.CharField(choices=[('cash', 'Cash'), ('finance', 'Finance'), ('lease', 'Lease'), ('credit_card', 'Credit Card'), ('bank_transfer', 'Bank Transfer'), ('sadad', 'SADAD')], max_length=20)),
|
|
||||||
('comments', models.TextField(blank=True, null=True)),
|
|
||||||
('formatted_order_id', models.CharField(editable=False, max_length=10, unique=True)),
|
|
||||||
('created', models.DateTimeField(auto_now_add=True)),
|
|
||||||
('estimate', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, related_name='sale_orders', to='django_ledger.estimatemodel', verbose_name='Estimate')),
|
|
||||||
('invoice', models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.CASCADE, related_name='sale_orders', to='django_ledger.invoicemodel', verbose_name='Invoice')),
|
|
||||||
],
|
|
||||||
options={
|
|
||||||
'ordering': ['-created'],
|
|
||||||
},
|
|
||||||
),
|
|
||||||
migrations.CreateModel(
|
|
||||||
name='Schedule',
|
|
||||||
fields=[
|
|
||||||
('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
|
|
||||||
('purpose', models.CharField(choices=[('product_demo', 'Product Demo'), ('follow_up_call', 'Follow-Up Call'), ('contract_discussion', 'Contract Discussion'), ('sales_meeting', 'Sales Meeting'), ('support_call', 'Support Call'), ('other', 'Other')], max_length=200)),
|
|
||||||
('scheduled_at', models.DateTimeField()),
|
|
||||||
('scheduled_type', models.CharField(choices=[('call', 'Call'), ('meeting', 'Meeting'), ('email', 'Email')], default='Call', max_length=200)),
|
|
||||||
('duration', models.DurationField(default=datetime.timedelta(seconds=300))),
|
|
||||||
('notes', models.TextField(blank=True, null=True)),
|
|
||||||
('status', models.CharField(choices=[('scheduled', 'Scheduled'), ('completed', 'Completed'), ('canceled', 'Canceled')], default='Scheduled', max_length=200)),
|
|
||||||
('created_at', models.DateTimeField(auto_now_add=True)),
|
|
||||||
('updated_at', models.DateTimeField(auto_now=True)),
|
|
||||||
('customer', models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.CASCADE, related_name='schedules', to='django_ledger.customermodel')),
|
|
||||||
('lead', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, related_name='schedules', to='inventory.lead')),
|
|
||||||
('scheduled_by', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to=settings.AUTH_USER_MODEL)),
|
|
||||||
],
|
|
||||||
options={
|
|
||||||
'ordering': ['-scheduled_at'],
|
|
||||||
},
|
|
||||||
),
|
|
||||||
migrations.CreateModel(
|
|
||||||
name='Staff',
|
|
||||||
fields=[
|
|
||||||
('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
|
|
||||||
('name', models.CharField(max_length=255, verbose_name='Name')),
|
|
||||||
('arabic_name', models.CharField(max_length=255, verbose_name='Arabic Name')),
|
|
||||||
('phone_number', phonenumber_field.modelfields.PhoneNumberField(max_length=128, region='SA', verbose_name='Phone Number')),
|
|
||||||
('staff_type', models.CharField(choices=[('inventory', 'Inventory'), ('accountant', 'Accountant'), ('sales', 'Sales')], max_length=255, verbose_name='Staff Type')),
|
|
||||||
('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')),
|
|
||||||
('slug', models.SlugField(blank=True, editable=False, max_length=255, null=True, unique=True)),
|
|
||||||
('dealer', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, related_name='staff', to='inventory.dealer')),
|
|
||||||
('staff_member', models.OneToOneField(on_delete=django.db.models.deletion.CASCADE, related_name='staff', to='appointment.staffmember')),
|
|
||||||
],
|
|
||||||
options={
|
|
||||||
'verbose_name': 'Staff',
|
|
||||||
'verbose_name_plural': 'Staff',
|
|
||||||
'permissions': [],
|
|
||||||
},
|
|
||||||
bases=(models.Model, inventory.mixins.LocalizedNameMixin),
|
|
||||||
managers=[
|
|
||||||
('objects', inventory.models.StaffUserManager()),
|
|
||||||
],
|
|
||||||
),
|
|
||||||
migrations.CreateModel(
|
|
||||||
name='Opportunity',
|
|
||||||
fields=[
|
|
||||||
('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
|
|
||||||
('stage', models.CharField(choices=[('discovery', 'Discovery'), ('proposal', 'Proposal'), ('negotiation', 'Negotiation'), ('closed_won', 'Closed Won'), ('closed_lost', 'Closed Lost')], max_length=20, verbose_name='Stage')),
|
|
||||||
('probability', models.PositiveIntegerField(validators=[inventory.models.validate_probability])),
|
|
||||||
('expected_revenue', models.DecimalField(decimal_places=2, max_digits=10, verbose_name='Expected Revenue')),
|
|
||||||
('closing_date', models.DateField(blank=True, null=True, verbose_name='Closing Date')),
|
|
||||||
('created', models.DateTimeField(auto_now_add=True, verbose_name='Created')),
|
|
||||||
('updated', models.DateTimeField(auto_now=True, verbose_name='Updated')),
|
|
||||||
('slug', models.SlugField(blank=True, help_text='Unique slug for the opportunity.', null=True, unique=True, verbose_name='Slug')),
|
|
||||||
('car', models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.SET_NULL, to='inventory.car', verbose_name='Car')),
|
|
||||||
('customer', models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.CASCADE, related_name='opportunities', to='inventory.customer')),
|
|
||||||
('dealer', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, related_name='opportunities', to='inventory.dealer')),
|
|
||||||
('estimate', models.OneToOneField(blank=True, null=True, on_delete=django.db.models.deletion.SET_NULL, related_name='opportunity', to='django_ledger.estimatemodel')),
|
|
||||||
('lead', models.OneToOneField(blank=True, null=True, on_delete=django.db.models.deletion.CASCADE, related_name='opportunity', to='inventory.lead')),
|
|
||||||
('staff', models.ForeignKey(null=True, on_delete=django.db.models.deletion.SET_NULL, related_name='owner', to='inventory.staff', verbose_name='Owner')),
|
|
||||||
],
|
|
||||||
options={
|
|
||||||
'verbose_name': 'Opportunity',
|
|
||||||
'verbose_name_plural': 'Opportunities',
|
|
||||||
},
|
|
||||||
),
|
|
||||||
migrations.CreateModel(
|
|
||||||
name='LeadStatusHistory',
|
|
||||||
fields=[
|
|
||||||
('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
|
|
||||||
('old_status', models.CharField(choices=[('new', 'New'), ('follow_up', 'Follow-up'), ('negotiation', 'Negotiation'), ('won', 'Won'), ('lost', 'Lost'), ('closed', 'Closed')], max_length=50, verbose_name='Old Status')),
|
|
||||||
('new_status', models.CharField(choices=[('new', 'New'), ('follow_up', 'Follow-up'), ('negotiation', 'Negotiation'), ('won', 'Won'), ('lost', 'Lost'), ('closed', 'Closed')], max_length=50, verbose_name='New Status')),
|
|
||||||
('changed_at', models.DateTimeField(auto_now_add=True, verbose_name='Changed At')),
|
|
||||||
('lead', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, related_name='status_history', to='inventory.lead')),
|
|
||||||
('changed_by', models.ForeignKey(on_delete=django.db.models.deletion.DO_NOTHING, related_name='status_changes', to='inventory.staff')),
|
|
||||||
],
|
|
||||||
options={
|
|
||||||
'verbose_name': 'Lead Status History',
|
|
||||||
'verbose_name_plural': 'Lead Status Histories',
|
|
||||||
},
|
|
||||||
),
|
|
||||||
migrations.AddField(
|
|
||||||
model_name='lead',
|
|
||||||
name='staff',
|
|
||||||
field=models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.SET_NULL, related_name='assigned', to='inventory.staff', verbose_name='Assigned'),
|
|
||||||
),
|
|
||||||
migrations.CreateModel(
|
|
||||||
name='Tasks',
|
|
||||||
fields=[
|
|
||||||
('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
|
|
||||||
('object_id', models.UUIDField()),
|
|
||||||
('title', models.CharField(max_length=255, verbose_name='Title')),
|
|
||||||
('description', models.TextField(blank=True, null=True, verbose_name='Description')),
|
|
||||||
('due_date', models.DateField(verbose_name='Due Date')),
|
|
||||||
('completed', models.BooleanField(default=False, verbose_name='Completed')),
|
|
||||||
('created', models.DateTimeField(auto_now_add=True, verbose_name='Created')),
|
|
||||||
('updated', models.DateTimeField(auto_now=True, verbose_name='Updated')),
|
|
||||||
('assigned_to', models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.DO_NOTHING, related_name='tasks_assigned', to=settings.AUTH_USER_MODEL)),
|
|
||||||
('content_type', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to='contenttypes.contenttype')),
|
|
||||||
('created_by', models.ForeignKey(on_delete=django.db.models.deletion.DO_NOTHING, related_name='tasks_created', to=settings.AUTH_USER_MODEL)),
|
|
||||||
('dealer', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, related_name='tasks', to='inventory.dealer')),
|
|
||||||
],
|
|
||||||
options={
|
|
||||||
'verbose_name': 'Task',
|
|
||||||
'verbose_name_plural': 'Tasks',
|
|
||||||
},
|
|
||||||
),
|
|
||||||
migrations.CreateModel(
|
|
||||||
name='UserActivityLog',
|
|
||||||
fields=[
|
|
||||||
('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
|
|
||||||
('action', models.TextField()),
|
|
||||||
('timestamp', models.DateTimeField(auto_now_add=True)),
|
|
||||||
('user', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to=settings.AUTH_USER_MODEL)),
|
|
||||||
],
|
|
||||||
options={
|
|
||||||
'verbose_name': 'User Activity Log',
|
|
||||||
'verbose_name_plural': 'User Activity Logs',
|
|
||||||
'ordering': ['-timestamp'],
|
|
||||||
},
|
|
||||||
),
|
|
||||||
migrations.CreateModel(
|
|
||||||
name='Vendor',
|
|
||||||
fields=[
|
|
||||||
('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
|
|
||||||
('crn', models.CharField(max_length=10, unique=True, verbose_name='Commercial Registration Number')),
|
|
||||||
('vrn', models.CharField(max_length=15, unique=True, verbose_name='VAT Registration Number')),
|
|
||||||
('arabic_name', models.CharField(max_length=255, verbose_name='Arabic Name')),
|
|
||||||
('name', models.CharField(max_length=255, verbose_name='English Name')),
|
|
||||||
('contact_person', models.CharField(max_length=100, verbose_name='Contact Person')),
|
|
||||||
('phone_number', phonenumber_field.modelfields.PhoneNumberField(max_length=128, region='SA', verbose_name='Phone Number')),
|
|
||||||
('email', models.EmailField(max_length=255, verbose_name='Email Address')),
|
|
||||||
('address', models.CharField(max_length=200, verbose_name='Address')),
|
|
||||||
('logo', models.ImageField(blank=True, null=True, upload_to='logos/vendors', verbose_name='Logo')),
|
|
||||||
('active', models.BooleanField(default=True, verbose_name='Active')),
|
|
||||||
('created_at', models.DateTimeField(auto_now_add=True, verbose_name='Created At')),
|
|
||||||
('slug', models.SlugField(blank=True, max_length=255, null=True, unique=True, verbose_name='Slug')),
|
|
||||||
('dealer', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, related_name='vendors', to='inventory.dealer')),
|
|
||||||
('vendor_model', models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.DO_NOTHING, to='django_ledger.vendormodel', verbose_name='Vendor Model')),
|
|
||||||
],
|
|
||||||
options={
|
|
||||||
'verbose_name': 'Vendor',
|
|
||||||
'verbose_name_plural': 'Vendors',
|
|
||||||
},
|
|
||||||
bases=(models.Model, inventory.mixins.LocalizedNameMixin),
|
|
||||||
),
|
|
||||||
migrations.AddField(
|
|
||||||
model_name='car',
|
|
||||||
name='vendor',
|
|
||||||
field=models.ForeignKey(on_delete=django.db.models.deletion.DO_NOTHING, related_name='cars', to='inventory.vendor', verbose_name='Vendor'),
|
|
||||||
),
|
|
||||||
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'],
|
|
||||||
'unique_together': {('car', 'reserved_until')},
|
|
||||||
},
|
|
||||||
),
|
|
||||||
migrations.CreateModel(
|
|
||||||
name='DealersMake',
|
|
||||||
fields=[
|
|
||||||
('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
|
|
||||||
('added_at', models.DateTimeField(auto_now_add=True)),
|
|
||||||
('car_make', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, related_name='car_dealers', to='inventory.carmake')),
|
|
||||||
('dealer', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, related_name='dealer_makes', to='inventory.dealer')),
|
|
||||||
],
|
|
||||||
options={
|
|
||||||
'unique_together': {('dealer', 'car_make')},
|
|
||||||
},
|
|
||||||
),
|
|
||||||
migrations.CreateModel(
|
|
||||||
name='CarColors',
|
|
||||||
fields=[
|
|
||||||
('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.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')],
|
|
||||||
},
|
|
||||||
),
|
|
||||||
]
|
|
||||||
@ -1,18 +0,0 @@
|
|||||||
# Generated by Django 5.1.7 on 2025-05-25 14:19
|
|
||||||
|
|
||||||
from django.db import migrations, models
|
|
||||||
|
|
||||||
|
|
||||||
class Migration(migrations.Migration):
|
|
||||||
|
|
||||||
dependencies = [
|
|
||||||
('inventory', '0001_initial'),
|
|
||||||
]
|
|
||||||
|
|
||||||
operations = [
|
|
||||||
migrations.AddField(
|
|
||||||
model_name='carfinance',
|
|
||||||
name='is_sold',
|
|
||||||
field=models.BooleanField(default=False),
|
|
||||||
),
|
|
||||||
]
|
|
||||||
@ -1,125 +0,0 @@
|
|||||||
# Generated by Django 5.1.7 on 2025-05-27 14:41
|
|
||||||
|
|
||||||
import django.db.models.deletion
|
|
||||||
import django.utils.timezone
|
|
||||||
from django.conf import settings
|
|
||||||
from django.db import migrations, models
|
|
||||||
|
|
||||||
|
|
||||||
class Migration(migrations.Migration):
|
|
||||||
|
|
||||||
dependencies = [
|
|
||||||
('inventory', '0002_carfinance_is_sold'),
|
|
||||||
migrations.swappable_dependency(settings.AUTH_USER_MODEL),
|
|
||||||
]
|
|
||||||
|
|
||||||
operations = [
|
|
||||||
migrations.AlterModelOptions(
|
|
||||||
name='saleorder',
|
|
||||||
options={'ordering': ['-order_date'], 'verbose_name': 'Sales Order', 'verbose_name_plural': 'Sales Orders'},
|
|
||||||
),
|
|
||||||
migrations.RenameField(
|
|
||||||
model_name='saleorder',
|
|
||||||
old_name='created',
|
|
||||||
new_name='created_at',
|
|
||||||
),
|
|
||||||
migrations.AddField(
|
|
||||||
model_name='saleorder',
|
|
||||||
name='actual_delivery_date',
|
|
||||||
field=models.DateTimeField(blank=True, help_text='The actual date and time the vehicle was delivered.', null=True),
|
|
||||||
),
|
|
||||||
migrations.AddField(
|
|
||||||
model_name='saleorder',
|
|
||||||
name='agreed_price',
|
|
||||||
field=models.DecimalField(decimal_places=2, default=0, help_text='The final agreed-upon selling price of the vehicle.', max_digits=12),
|
|
||||||
preserve_default=False,
|
|
||||||
),
|
|
||||||
migrations.AddField(
|
|
||||||
model_name='saleorder',
|
|
||||||
name='cancellation_reason',
|
|
||||||
field=models.TextField(blank=True, help_text='Reason for cancellation, if applicable.', null=True),
|
|
||||||
),
|
|
||||||
migrations.AddField(
|
|
||||||
model_name='saleorder',
|
|
||||||
name='cancelled_date',
|
|
||||||
field=models.DateTimeField(blank=True, help_text='The date and time the order was cancelled, if applicable.', null=True),
|
|
||||||
),
|
|
||||||
migrations.AddField(
|
|
||||||
model_name='saleorder',
|
|
||||||
name='car',
|
|
||||||
field=models.ForeignKey(default=1, help_text='The specific vehicle (VIN) being sold.', on_delete=django.db.models.deletion.PROTECT, related_name='sales_orders', to='inventory.car'),
|
|
||||||
preserve_default=False,
|
|
||||||
),
|
|
||||||
migrations.AddField(
|
|
||||||
model_name='saleorder',
|
|
||||||
name='created_by',
|
|
||||||
field=models.ForeignKey(help_text='The user who created this sales order.', null=True, on_delete=django.db.models.deletion.SET_NULL, related_name='created_sales_orders', to=settings.AUTH_USER_MODEL),
|
|
||||||
),
|
|
||||||
migrations.AddField(
|
|
||||||
model_name='saleorder',
|
|
||||||
name='customer',
|
|
||||||
field=models.ForeignKey(default=1, help_text='The customer making the purchase.', on_delete=django.db.models.deletion.PROTECT, related_name='sales_orders', to='inventory.customer'),
|
|
||||||
preserve_default=False,
|
|
||||||
),
|
|
||||||
migrations.AddField(
|
|
||||||
model_name='saleorder',
|
|
||||||
name='down_payment_amount',
|
|
||||||
field=models.DecimalField(decimal_places=2, default=0.0, help_text='The initial payment made by the customer.', max_digits=12),
|
|
||||||
),
|
|
||||||
migrations.AddField(
|
|
||||||
model_name='saleorder',
|
|
||||||
name='expected_delivery_date',
|
|
||||||
field=models.DateField(blank=True, help_text='The planned date for vehicle delivery.', null=True),
|
|
||||||
),
|
|
||||||
migrations.AddField(
|
|
||||||
model_name='saleorder',
|
|
||||||
name='last_modified_by',
|
|
||||||
field=models.ForeignKey(help_text='The user who last modified this sales order.', null=True, on_delete=django.db.models.deletion.SET_NULL, related_name='modified_sales_orders', to=settings.AUTH_USER_MODEL),
|
|
||||||
),
|
|
||||||
migrations.AddField(
|
|
||||||
model_name='saleorder',
|
|
||||||
name='loan_amount',
|
|
||||||
field=models.DecimalField(decimal_places=2, default=0.0, help_text='The amount financed by a bank or third-party lender.', max_digits=12),
|
|
||||||
),
|
|
||||||
migrations.AddField(
|
|
||||||
model_name='saleorder',
|
|
||||||
name='opportunity',
|
|
||||||
field=models.OneToOneField(default=1, help_text='The associated sales opportunity for this order.', on_delete=django.db.models.deletion.CASCADE, related_name='sales_order', to='inventory.opportunity'),
|
|
||||||
preserve_default=False,
|
|
||||||
),
|
|
||||||
migrations.AddField(
|
|
||||||
model_name='saleorder',
|
|
||||||
name='order_date',
|
|
||||||
field=models.DateTimeField(default=django.utils.timezone.now, help_text='The date and time the sales order was created.'),
|
|
||||||
),
|
|
||||||
migrations.AddField(
|
|
||||||
model_name='saleorder',
|
|
||||||
name='remaining_balance',
|
|
||||||
field=models.DecimalField(decimal_places=2, default=0.0, help_text='The remaining amount due from the customer or financing.', max_digits=12),
|
|
||||||
),
|
|
||||||
migrations.AddField(
|
|
||||||
model_name='saleorder',
|
|
||||||
name='status',
|
|
||||||
field=models.CharField(choices=[('PENDING_APPROVAL', 'Pending Approval'), ('APPROVED', 'Approved'), ('IN_FINANCING', 'In Financing'), ('PARTIALLY_PAID', 'Partially Paid'), ('FULLY_PAID', 'Fully Paid'), ('PENDING_DELIVERY', 'Pending Delivery'), ('DELIVERED', 'Delivered'), ('CANCELLED', 'Cancelled')], default='PENDING_APPROVAL', help_text='Current status of the sales order.', max_length=20),
|
|
||||||
),
|
|
||||||
migrations.AddField(
|
|
||||||
model_name='saleorder',
|
|
||||||
name='total_paid_amount',
|
|
||||||
field=models.DecimalField(decimal_places=2, default=0.0, help_text='Sum of down payment, trade-in value, and loan amount received so far.', max_digits=12),
|
|
||||||
),
|
|
||||||
migrations.AddField(
|
|
||||||
model_name='saleorder',
|
|
||||||
name='trade_in_value',
|
|
||||||
field=models.DecimalField(decimal_places=2, default=0.0, help_text='The value of any vehicle traded in by the customer.', max_digits=12),
|
|
||||||
),
|
|
||||||
migrations.AddField(
|
|
||||||
model_name='saleorder',
|
|
||||||
name='trade_in_vehicle',
|
|
||||||
field=models.ForeignKey(blank=True, help_text='The vehicle traded in by the customer, if any.', null=True, on_delete=django.db.models.deletion.SET_NULL, related_name='traded_in_on_orders', to='inventory.car'),
|
|
||||||
),
|
|
||||||
migrations.AddField(
|
|
||||||
model_name='saleorder',
|
|
||||||
name='updated_at',
|
|
||||||
field=models.DateTimeField(auto_now=True),
|
|
||||||
),
|
|
||||||
]
|
|
||||||
@ -1,19 +0,0 @@
|
|||||||
# Generated by Django 5.1.7 on 2025-05-27 14:43
|
|
||||||
|
|
||||||
import django.db.models.deletion
|
|
||||||
from django.db import migrations, models
|
|
||||||
|
|
||||||
|
|
||||||
class Migration(migrations.Migration):
|
|
||||||
|
|
||||||
dependencies = [
|
|
||||||
('inventory', '0003_alter_saleorder_options_and_more'),
|
|
||||||
]
|
|
||||||
|
|
||||||
operations = [
|
|
||||||
migrations.AlterField(
|
|
||||||
model_name='saleorder',
|
|
||||||
name='car',
|
|
||||||
field=models.ForeignKey(blank=True, help_text='The specific vehicle (VIN) being sold.', null=True, on_delete=django.db.models.deletion.PROTECT, related_name='sales_orders', to='inventory.car'),
|
|
||||||
),
|
|
||||||
]
|
|
||||||
@ -1,18 +0,0 @@
|
|||||||
# Generated by Django 5.1.7 on 2025-05-28 13:14
|
|
||||||
|
|
||||||
from django.db import migrations, models
|
|
||||||
|
|
||||||
|
|
||||||
class Migration(migrations.Migration):
|
|
||||||
|
|
||||||
dependencies = [
|
|
||||||
('inventory', '0004_alter_saleorder_car'),
|
|
||||||
]
|
|
||||||
|
|
||||||
operations = [
|
|
||||||
migrations.AlterField(
|
|
||||||
model_name='opportunity',
|
|
||||||
name='stage',
|
|
||||||
field=models.CharField(choices=[('qualification', 'Qualification'), ('test_drive', 'Test Drive'), ('quotation', 'Quotation'), ('negotiation', 'Negotiation'), ('financing', 'Financing'), ('closed_won', 'Closed Won'), ('closed_lost', 'Closed Lost'), ('on_hold', 'On Hold')], max_length=20, verbose_name='Stage'),
|
|
||||||
),
|
|
||||||
]
|
|
||||||
@ -1,45 +0,0 @@
|
|||||||
# Generated by Django 5.1.7 on 2025-05-28 13:16
|
|
||||||
|
|
||||||
import django.db.models.deletion
|
|
||||||
from django.conf import settings
|
|
||||||
from django.db import migrations, models
|
|
||||||
|
|
||||||
|
|
||||||
class Migration(migrations.Migration):
|
|
||||||
|
|
||||||
dependencies = [
|
|
||||||
('inventory', '0005_alter_opportunity_stage'),
|
|
||||||
migrations.swappable_dependency(settings.AUTH_USER_MODEL),
|
|
||||||
]
|
|
||||||
|
|
||||||
operations = [
|
|
||||||
migrations.RemoveField(
|
|
||||||
model_name='opportunity',
|
|
||||||
name='closing_date',
|
|
||||||
),
|
|
||||||
migrations.AddField(
|
|
||||||
model_name='opportunity',
|
|
||||||
name='assigned_to',
|
|
||||||
field=models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.SET_NULL, related_name='assigned_opportunities', to=settings.AUTH_USER_MODEL),
|
|
||||||
),
|
|
||||||
migrations.AddField(
|
|
||||||
model_name='opportunity',
|
|
||||||
name='expected_close_date',
|
|
||||||
field=models.DateField(blank=True, null=True),
|
|
||||||
),
|
|
||||||
migrations.AddField(
|
|
||||||
model_name='opportunity',
|
|
||||||
name='loss_reason',
|
|
||||||
field=models.CharField(blank=True, max_length=255, null=True),
|
|
||||||
),
|
|
||||||
migrations.AddField(
|
|
||||||
model_name='opportunity',
|
|
||||||
name='vehicle_of_interest_make',
|
|
||||||
field=models.CharField(blank=True, max_length=50, null=True),
|
|
||||||
),
|
|
||||||
migrations.AddField(
|
|
||||||
model_name='opportunity',
|
|
||||||
name='vehicle_of_interest_model',
|
|
||||||
field=models.CharField(blank=True, max_length=100, null=True),
|
|
||||||
),
|
|
||||||
]
|
|
||||||
@ -1,28 +0,0 @@
|
|||||||
# Generated by Django 5.1.7 on 2025-05-28 13:36
|
|
||||||
|
|
||||||
from django.db import migrations, models
|
|
||||||
|
|
||||||
|
|
||||||
class Migration(migrations.Migration):
|
|
||||||
|
|
||||||
dependencies = [
|
|
||||||
('inventory', '0006_remove_opportunity_closing_date_and_more'),
|
|
||||||
]
|
|
||||||
|
|
||||||
operations = [
|
|
||||||
migrations.AlterField(
|
|
||||||
model_name='lead',
|
|
||||||
name='status',
|
|
||||||
field=models.CharField(choices=[('new', 'New'), ('contacted', 'Contacted'), ('qualified', 'Qualified'), ('unqualified', 'Unqualified'), ('converted', 'Converted')], db_index=True, default='new', max_length=50, verbose_name='Status'),
|
|
||||||
),
|
|
||||||
migrations.AlterField(
|
|
||||||
model_name='leadstatushistory',
|
|
||||||
name='new_status',
|
|
||||||
field=models.CharField(choices=[('new', 'New'), ('contacted', 'Contacted'), ('qualified', 'Qualified'), ('unqualified', 'Unqualified'), ('converted', 'Converted')], max_length=50, verbose_name='New Status'),
|
|
||||||
),
|
|
||||||
migrations.AlterField(
|
|
||||||
model_name='leadstatushistory',
|
|
||||||
name='old_status',
|
|
||||||
field=models.CharField(choices=[('new', 'New'), ('contacted', 'Contacted'), ('qualified', 'Qualified'), ('unqualified', 'Unqualified'), ('converted', 'Converted')], max_length=50, verbose_name='Old Status'),
|
|
||||||
),
|
|
||||||
]
|
|
||||||
@ -1,37 +0,0 @@
|
|||||||
# Generated by Django 5.1.7 on 2025-05-28 13:44
|
|
||||||
|
|
||||||
from django.db import migrations
|
|
||||||
|
|
||||||
|
|
||||||
class Migration(migrations.Migration):
|
|
||||||
|
|
||||||
dependencies = [
|
|
||||||
('inventory', '0007_alter_lead_status_alter_leadstatushistory_new_status_and_more'),
|
|
||||||
]
|
|
||||||
|
|
||||||
operations = [
|
|
||||||
migrations.RemoveField(
|
|
||||||
model_name='lead',
|
|
||||||
name='address',
|
|
||||||
),
|
|
||||||
migrations.RemoveField(
|
|
||||||
model_name='lead',
|
|
||||||
name='crn',
|
|
||||||
),
|
|
||||||
migrations.RemoveField(
|
|
||||||
model_name='lead',
|
|
||||||
name='priority',
|
|
||||||
),
|
|
||||||
migrations.RemoveField(
|
|
||||||
model_name='lead',
|
|
||||||
name='salary',
|
|
||||||
),
|
|
||||||
migrations.RemoveField(
|
|
||||||
model_name='lead',
|
|
||||||
name='vrn',
|
|
||||||
),
|
|
||||||
migrations.RemoveField(
|
|
||||||
model_name='lead',
|
|
||||||
name='year',
|
|
||||||
),
|
|
||||||
]
|
|
||||||
@ -1,38 +0,0 @@
|
|||||||
# Generated by Django 5.1.7 on 2025-05-28 13:46
|
|
||||||
|
|
||||||
from django.db import migrations, models
|
|
||||||
|
|
||||||
|
|
||||||
class Migration(migrations.Migration):
|
|
||||||
|
|
||||||
dependencies = [
|
|
||||||
('inventory', '0008_remove_lead_address_remove_lead_crn_and_more'),
|
|
||||||
]
|
|
||||||
|
|
||||||
operations = [
|
|
||||||
migrations.AddField(
|
|
||||||
model_name='lead',
|
|
||||||
name='address',
|
|
||||||
field=models.CharField(blank=True, max_length=200, null=True, verbose_name='Address'),
|
|
||||||
),
|
|
||||||
migrations.AddField(
|
|
||||||
model_name='opportunity',
|
|
||||||
name='crn',
|
|
||||||
field=models.CharField(blank=True, max_length=20, null=True, verbose_name='CRN'),
|
|
||||||
),
|
|
||||||
migrations.AddField(
|
|
||||||
model_name='opportunity',
|
|
||||||
name='priority',
|
|
||||||
field=models.CharField(choices=[('high', 'High'), ('medium', 'Medium'), ('low', 'Low')], default='medium', max_length=20, verbose_name='Priority'),
|
|
||||||
),
|
|
||||||
migrations.AddField(
|
|
||||||
model_name='opportunity',
|
|
||||||
name='salary',
|
|
||||||
field=models.DecimalField(blank=True, decimal_places=2, max_digits=10, null=True, verbose_name='Salary'),
|
|
||||||
),
|
|
||||||
migrations.AddField(
|
|
||||||
model_name='opportunity',
|
|
||||||
name='vrn',
|
|
||||||
field=models.CharField(blank=True, max_length=20, null=True, verbose_name='VRN'),
|
|
||||||
),
|
|
||||||
]
|
|
||||||
@ -1,19 +0,0 @@
|
|||||||
# Generated by Django 5.1.7 on 2025-05-29 15:22
|
|
||||||
|
|
||||||
import django.db.models.deletion
|
|
||||||
from django.db import migrations, models
|
|
||||||
|
|
||||||
|
|
||||||
class Migration(migrations.Migration):
|
|
||||||
|
|
||||||
dependencies = [
|
|
||||||
('inventory', '0009_lead_address_opportunity_crn_opportunity_priority_and_more'),
|
|
||||||
]
|
|
||||||
|
|
||||||
operations = [
|
|
||||||
migrations.AlterField(
|
|
||||||
model_name='opportunity',
|
|
||||||
name='assigned_to',
|
|
||||||
field=models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.SET_NULL, related_name='assigned_opportunities', to='inventory.staff'),
|
|
||||||
),
|
|
||||||
]
|
|
||||||
@ -1,17 +0,0 @@
|
|||||||
# Generated by Django 5.1.7 on 2025-05-29 15:23
|
|
||||||
|
|
||||||
from django.db import migrations
|
|
||||||
|
|
||||||
|
|
||||||
class Migration(migrations.Migration):
|
|
||||||
|
|
||||||
dependencies = [
|
|
||||||
('inventory', '0010_alter_opportunity_assigned_to'),
|
|
||||||
]
|
|
||||||
|
|
||||||
operations = [
|
|
||||||
migrations.RemoveField(
|
|
||||||
model_name='opportunity',
|
|
||||||
name='assigned_to',
|
|
||||||
),
|
|
||||||
]
|
|
||||||
@ -1,19 +0,0 @@
|
|||||||
# Generated by Django 5.1.7 on 2025-05-29 16:12
|
|
||||||
|
|
||||||
import django.db.models.deletion
|
|
||||||
from django.db import migrations, models
|
|
||||||
|
|
||||||
|
|
||||||
class Migration(migrations.Migration):
|
|
||||||
|
|
||||||
dependencies = [
|
|
||||||
('inventory', '0011_remove_opportunity_assigned_to'),
|
|
||||||
]
|
|
||||||
|
|
||||||
operations = [
|
|
||||||
migrations.AddField(
|
|
||||||
model_name='opportunity',
|
|
||||||
name='organization',
|
|
||||||
field=models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.SET_NULL, to='inventory.organization', verbose_name='Organization'),
|
|
||||||
),
|
|
||||||
]
|
|
||||||
@ -1,23 +0,0 @@
|
|||||||
# Generated by Django 5.1.7 on 2025-05-29 23:00
|
|
||||||
|
|
||||||
from django.db import migrations, models
|
|
||||||
|
|
||||||
|
|
||||||
class Migration(migrations.Migration):
|
|
||||||
|
|
||||||
dependencies = [
|
|
||||||
('inventory', '0012_opportunity_organization'),
|
|
||||||
]
|
|
||||||
|
|
||||||
operations = [
|
|
||||||
migrations.AddField(
|
|
||||||
model_name='opportunity',
|
|
||||||
name='amount',
|
|
||||||
field=models.DecimalField(blank=True, decimal_places=2, max_digits=10, null=True, verbose_name='Amount'),
|
|
||||||
),
|
|
||||||
migrations.AlterField(
|
|
||||||
model_name='opportunity',
|
|
||||||
name='expected_revenue',
|
|
||||||
field=models.DecimalField(blank=True, decimal_places=2, max_digits=10, null=True, verbose_name='Expected Revenue'),
|
|
||||||
),
|
|
||||||
]
|
|
||||||
@ -1,19 +0,0 @@
|
|||||||
# Generated by Django 5.1.7 on 2025-05-29 23:02
|
|
||||||
|
|
||||||
from django.db import migrations, models
|
|
||||||
|
|
||||||
|
|
||||||
class Migration(migrations.Migration):
|
|
||||||
|
|
||||||
dependencies = [
|
|
||||||
('inventory', '0013_opportunity_amount_and_more'),
|
|
||||||
]
|
|
||||||
|
|
||||||
operations = [
|
|
||||||
migrations.AlterField(
|
|
||||||
model_name='opportunity',
|
|
||||||
name='amount',
|
|
||||||
field=models.DecimalField(decimal_places=2, default=0, max_digits=10, verbose_name='Amount'),
|
|
||||||
preserve_default=False,
|
|
||||||
),
|
|
||||||
]
|
|
||||||
@ -281,6 +281,7 @@ urlpatterns = [
|
|||||||
path(
|
path(
|
||||||
"cars/<slug:slug>/add-color/", views.CarColorCreate.as_view(), name="add_color"
|
"cars/<slug:slug>/add-color/", views.CarColorCreate.as_view(), name="add_color"
|
||||||
),
|
),
|
||||||
|
path('car/colors/<slug:slug>/update/', views.CarColorsUpdateView.as_view(), name='car_colors_update'),
|
||||||
path(
|
path(
|
||||||
"cars/<slug:slug>/location/add/",
|
"cars/<slug:slug>/location/add/",
|
||||||
views.CarLocationCreateView.as_view(),
|
views.CarLocationCreateView.as_view(),
|
||||||
@ -855,6 +856,9 @@ path(
|
|||||||
path('management/user_management/', views.user_management, name='user_management'),
|
path('management/user_management/', views.user_management, name='user_management'),
|
||||||
path('management/<str:content_type>/<slug:slug>/activate_account/', views.activate_account, name='activate_account'),
|
path('management/<str:content_type>/<slug:slug>/activate_account/', views.activate_account, name='activate_account'),
|
||||||
path('management/<str:content_type>/<slug:slug>/permenant_delete_account/', views.permenant_delete_account, name='permenant_delete_account'),
|
path('management/<str:content_type>/<slug:slug>/permenant_delete_account/', views.permenant_delete_account, name='permenant_delete_account'),
|
||||||
|
path('management/audit_log_dashboard/', views.AuditLogDashboardView, name='audit_log_dashboard'),
|
||||||
|
|
||||||
|
|
||||||
#########
|
#########
|
||||||
# Purchase Order
|
# Purchase Order
|
||||||
path('purchase_orders/', views.PurchaseOrderListView.as_view(), name='purchase_order_list'),
|
path('purchase_orders/', views.PurchaseOrderListView.as_view(), name='purchase_order_list'),
|
||||||
|
|||||||
@ -31,7 +31,7 @@ from django.forms import HiddenInput, ValidationError
|
|||||||
from django.shortcuts import HttpResponse
|
from django.shortcuts import HttpResponse
|
||||||
|
|
||||||
from django.db.models import Sum, F, Count
|
from django.db.models import Sum, F, Count
|
||||||
from django.core.paginator import Paginator
|
from django.core.paginator import Paginator,EmptyPage, PageNotAnInteger
|
||||||
from django.contrib.auth.models import User
|
from django.contrib.auth.models import User
|
||||||
from django.contrib.auth.models import Group
|
from django.contrib.auth.models import Group
|
||||||
from django.db.models import Value
|
from django.db.models import Value
|
||||||
@ -60,6 +60,7 @@ from django.views.generic import (
|
|||||||
ArchiveIndexView,
|
ArchiveIndexView,
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
# Django Ledger
|
# Django Ledger
|
||||||
from django_ledger.io import roles
|
from django_ledger.io import roles
|
||||||
from django_ledger.utils import accruable_net_summary
|
from django_ledger.utils import accruable_net_summary
|
||||||
@ -172,6 +173,10 @@ from .utils import (
|
|||||||
)
|
)
|
||||||
from .tasks import create_accounts_for_make, send_email
|
from .tasks import create_accounts_for_make, send_email
|
||||||
|
|
||||||
|
#djago easy audit log
|
||||||
|
from easyaudit.models import RequestEvent, CRUDEvent, LoginEvent
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
logger = logging.getLogger(__name__)
|
logger = logging.getLogger(__name__)
|
||||||
logging.basicConfig(level=logging.INFO)
|
logging.basicConfig(level=logging.INFO)
|
||||||
@ -967,37 +972,50 @@ class CarColorCreate(LoginRequiredMixin, PermissionRequiredMixin, CreateView):
|
|||||||
context["car"] = get_object_or_404(models.Car, slug=self.kwargs["slug"])
|
context["car"] = get_object_or_404(models.Car, slug=self.kwargs["slug"])
|
||||||
return context
|
return context
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
class CarColorsUpdateView( LoginRequiredMixin, PermissionRequiredMixin, SuccessMessageMixin, UpdateView):
|
class CarColorsUpdateView( LoginRequiredMixin, PermissionRequiredMixin, SuccessMessageMixin, UpdateView):
|
||||||
model = models.CarColors
|
model = models.CarColors
|
||||||
form_class = forms.CarColorsForm
|
form_class = forms.CarColorsForm
|
||||||
template_name = "inventory/add_colors.html"
|
template_name = "inventory/add_colors.html"
|
||||||
success_message = _("Car finance details updated successfully")
|
success_message = _("Car Colors details updated successfully")
|
||||||
permission_required = ["inventory.change_carfinance"]
|
permission_required = ["inventory.change_car"]
|
||||||
|
|
||||||
|
|
||||||
|
def get_object(self, queryset=None):
|
||||||
|
"""
|
||||||
|
Retrieves the CarColors instance associated with the Car slug from the URL.
|
||||||
|
This ensures we are updating the colors for the correct car.
|
||||||
|
"""
|
||||||
|
|
||||||
|
# Get the car_slug from the URL keywords arguments
|
||||||
|
slug = self.kwargs.get('slug')
|
||||||
|
|
||||||
|
# If no car_slug is provided, it's an invalid request
|
||||||
|
if not slug:
|
||||||
|
# You might want to raise Http404 or a more specific error here
|
||||||
|
raise ValueError("Car slug is required to identify the colors to update.")
|
||||||
|
|
||||||
|
|
||||||
|
return get_object_or_404(models.CarColors, car__slug=slug)
|
||||||
|
|
||||||
def get_success_url(self):
|
def get_success_url(self):
|
||||||
|
"""
|
||||||
|
Redirects to the car's detail page using its slug after a successful update.
|
||||||
|
"""
|
||||||
|
# self.object refers to the CarColors instance that was just updated.
|
||||||
|
# self.object.car then refers to the associated Car instance.
|
||||||
return reverse("car_detail", kwargs={"slug": self.object.car.slug})
|
return reverse("car_detail", kwargs={"slug": self.object.car.slug})
|
||||||
|
|
||||||
def get_form_kwargs(self):
|
def get_context_data(self, **kwargs):
|
||||||
kwargs = super().get_form_kwargs()
|
"""
|
||||||
kwargs["instance"] = self.get_object()
|
Adds the related Car object to the template context.
|
||||||
return kwargs
|
"""
|
||||||
|
context = super().get_context_data(**kwargs)
|
||||||
def get_initial(self):
|
# self.object is already available here from get_object()
|
||||||
initial = super().get_initial()
|
context['car'] = self.object.car
|
||||||
instance = self.get_object()
|
context['page_title'] = _("Update Colors for %(car_name)s") % {'car_name': context['car']}
|
||||||
dealer = get_user_type(self.request)
|
return context
|
||||||
selected_items = instance.additional_services.filter(dealer=dealer)
|
|
||||||
initial["additional_finances"] = selected_items
|
|
||||||
return initial
|
|
||||||
|
|
||||||
def get_form(self, form_class=None):
|
|
||||||
form = super().get_form(form_class)
|
|
||||||
dealer = get_user_type(self.request)
|
|
||||||
form.fields[
|
|
||||||
"additional_finances"
|
|
||||||
].queryset = models.AdditionalServices.objects.filter(dealer=dealer)
|
|
||||||
return form
|
|
||||||
|
|
||||||
|
|
||||||
class CarListView(LoginRequiredMixin, PermissionRequiredMixin, ListView):
|
class CarListView(LoginRequiredMixin, PermissionRequiredMixin, ListView):
|
||||||
"""
|
"""
|
||||||
@ -8341,6 +8359,123 @@ def user_management(request):
|
|||||||
return render(request, "admin_management/user_management.html", context)
|
return render(request, "admin_management/user_management.html", context)
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
def AuditLogDashboardView(request):
|
||||||
|
"""
|
||||||
|
Displays audit logs (User Actions, Login Events, Request Events) with pagination.
|
||||||
|
Log type is determined by the 'q' query parameter (e.g., ?q=userActions).
|
||||||
|
Pagination page number is passed as a query parameter (e.g., ?page=2).
|
||||||
|
"""
|
||||||
|
q = request.GET.get('q') # Get the log type from the 'q' query parameter
|
||||||
|
current_pagination_page = request.GET.get('page', 1)
|
||||||
|
context = {}
|
||||||
|
template_name = None
|
||||||
|
logs_per_page = 30 # Define logs per page once
|
||||||
|
|
||||||
|
# --- Determine Data Source and Template based on 'q' parameter ---
|
||||||
|
if q=='userRequests': # This block handles cases where 'q' is 'requestEvents', None, or any other invalid value.
|
||||||
|
# It defaults to Request Logs if 'q' is not 'userActions' or 'loginEvents'.
|
||||||
|
template_name = 'admin_management/request_logs.html'
|
||||||
|
context['title'] = 'Request Logs Dashboard'
|
||||||
|
request_events = RequestEvent.objects.all().order_by('-datetime')
|
||||||
|
paginator = Paginator(request_events, logs_per_page)
|
||||||
|
try:
|
||||||
|
page_obj = paginator.page(current_pagination_page)
|
||||||
|
except PageNotAnInteger:
|
||||||
|
page_obj = paginator.page(1)
|
||||||
|
except EmptyPage:
|
||||||
|
page_obj = paginator.page(paginator.num_pages)
|
||||||
|
|
||||||
|
|
||||||
|
elif q == 'loginEvents':
|
||||||
|
template_name = 'admin_management/auth_logs.html'
|
||||||
|
context['title'] = 'Login Events Dashboard'
|
||||||
|
auth_events = LoginEvent.objects.all().order_by('-datetime')
|
||||||
|
paginator = Paginator(auth_events, logs_per_page)
|
||||||
|
try:
|
||||||
|
page_obj = paginator.page(current_pagination_page)
|
||||||
|
except PageNotAnInteger:
|
||||||
|
page_obj = paginator.page(1)
|
||||||
|
except EmptyPage:
|
||||||
|
page_obj = paginator.page(paginator.num_pages)
|
||||||
|
|
||||||
|
else:
|
||||||
|
template_name = 'admin_management/model_logs.html'
|
||||||
|
context['title'] = 'User Actions Dashboard'
|
||||||
|
|
||||||
|
# OPTIMIZATION: Get the QuerySet but don't evaluate it yet
|
||||||
|
model_events_queryset = CRUDEvent.objects.all().order_by('-datetime')
|
||||||
|
|
||||||
|
# 1. Paginate the raw QuerySet FIRST
|
||||||
|
paginator = Paginator(model_events_queryset, logs_per_page)
|
||||||
|
|
||||||
|
try:
|
||||||
|
# Get the page object, which contains only the raw QuerySet objects for the current page
|
||||||
|
page_obj_raw = paginator.page(current_pagination_page)
|
||||||
|
except PageNotAnInteger:
|
||||||
|
page_obj_raw = paginator.page(1)
|
||||||
|
except EmptyPage:
|
||||||
|
page_obj_raw = paginator.page(paginator.num_pages)
|
||||||
|
|
||||||
|
# 2. Now, process 'field_changes' ONLY for the events on the current page
|
||||||
|
processed_model_events_for_page = []
|
||||||
|
for event in page_obj_raw.object_list: # Loop only through the current page's items
|
||||||
|
event_data = {
|
||||||
|
'datetime': event.datetime,
|
||||||
|
'user': event.user,
|
||||||
|
'event_type_display': event.get_event_type_display(),
|
||||||
|
'model_name': event.content_type.model,
|
||||||
|
'object_id': event.object_id,
|
||||||
|
'object_repr': event.object_repr,
|
||||||
|
'field_changes': []
|
||||||
|
}
|
||||||
|
|
||||||
|
if event.changed_fields:
|
||||||
|
try:
|
||||||
|
changes = json.loads(event.changed_fields)
|
||||||
|
if isinstance(changes, dict):
|
||||||
|
for field_name, values in changes.items():
|
||||||
|
old_value = values[0] if isinstance(values, list) and len(values) > 0 else None
|
||||||
|
new_value = values[1] if isinstance(values, list) and len(values) > 1 else None
|
||||||
|
event_data['field_changes'].append({
|
||||||
|
'field': field_name,
|
||||||
|
'old': old_value,
|
||||||
|
'new': new_value
|
||||||
|
})
|
||||||
|
elif changes is None:
|
||||||
|
event_data['field_changes'].append({
|
||||||
|
'field': 'Info',
|
||||||
|
'old': '',
|
||||||
|
'new': 'No specific field changes recorded (JSON was null)'
|
||||||
|
})
|
||||||
|
else: # Handle valid JSON but not a dictionary (e.g., "[]", 123)
|
||||||
|
event_data['field_changes'].append({
|
||||||
|
'field': 'Error',
|
||||||
|
'old': '',
|
||||||
|
'new': f'Unexpected JSON format: {type(changes).__name__}'
|
||||||
|
})
|
||||||
|
except json.JSONDecodeError:
|
||||||
|
# Handle invalid JSON; you might log this error
|
||||||
|
event_data['field_changes'].append({
|
||||||
|
'field': 'Error',
|
||||||
|
'old': '',
|
||||||
|
'new': 'Invalid JSON in changed_fields'
|
||||||
|
})
|
||||||
|
processed_model_events_for_page.append(event_data)
|
||||||
|
|
||||||
|
# 3. Replace the object_list of the original page_obj with the processed data
|
||||||
|
# This keeps all pagination properties (has_next, number, etc.) intact.
|
||||||
|
page_obj_raw.object_list = processed_model_events_for_page
|
||||||
|
page_obj = page_obj_raw # This will be passed to the context
|
||||||
|
|
||||||
|
# Pass the final page object to the context
|
||||||
|
context['page_obj'] = page_obj
|
||||||
|
|
||||||
|
return render(request, template_name, context)
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
def activate_account(request, content_type, slug):
|
def activate_account(request, content_type, slug):
|
||||||
try:
|
try:
|
||||||
model = apps.get_model(f"inventory.{content_type}")
|
model = apps.get_model(f"inventory.{content_type}")
|
||||||
|
|||||||
@ -94,3 +94,4 @@ urllib3==2.3.0
|
|||||||
wcwidth==0.2.13
|
wcwidth==0.2.13
|
||||||
langchain
|
langchain
|
||||||
langchain_ollama
|
langchain_ollama
|
||||||
|
django-easy-audit==1.3.7
|
||||||
Binary file not shown.
|
After Width: | Height: | Size: 3.0 MiB |
1356
staticfiles/icons/HaikalAi.ai
Normal file
1356
staticfiles/icons/HaikalAi.ai
Normal file
File diff suppressed because one or more lines are too long
BIN
staticfiles/images/favicons/haikalbot_v1.png
Normal file
BIN
staticfiles/images/favicons/haikalbot_v1.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 34 KiB |
BIN
staticfiles/images/favicons/haikalbot_v2.png
Normal file
BIN
staticfiles/images/favicons/haikalbot_v2.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 65 KiB |
BIN
staticfiles/images/logos/users/li-yang-5h_dMuX_7RE-unsplash.jpg
Normal file
BIN
staticfiles/images/logos/users/li-yang-5h_dMuX_7RE-unsplash.jpg
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 3.0 MiB |
BIN
staticfiles/images/logos/users/pexels-eberhardgross-443446.jpg
Normal file
BIN
staticfiles/images/logos/users/pexels-eberhardgross-443446.jpg
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 2.9 MiB |
75
templates/admin_management/auth_logs.html
Normal file
75
templates/admin_management/auth_logs.html
Normal file
@ -0,0 +1,75 @@
|
|||||||
|
|
||||||
|
{% extends "base.html" %}
|
||||||
|
{% load i18n custom_filters %}
|
||||||
|
{% block title %}{% trans "Accounts" %}{% endblock title %}
|
||||||
|
{% block accounts %}
|
||||||
|
<a class="nav-link active fw-bold">
|
||||||
|
{% trans "Accounts"|capfirst %}
|
||||||
|
<span class="visually-hidden">(current)</span>
|
||||||
|
</a>
|
||||||
|
{% endblock %}
|
||||||
|
{% block content %}
|
||||||
|
<div class="row mt-4">
|
||||||
|
|
||||||
|
<div class="d-flex justify-content-between mb-2">
|
||||||
|
<h3 class=""><i class="fa-solid fa-book"></i> {% trans "Audit Log Dashboard" %}</h3>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- Log Type Tabs -->
|
||||||
|
<div class="mb-4">
|
||||||
|
{% include 'admin_management/nav.html' %}
|
||||||
|
|
||||||
|
<div class="tab-content p-3 border border-top-0 rounded-bottom" id="accountTypeTabsContent">
|
||||||
|
<!-- modellogs Tab -->
|
||||||
|
|
||||||
|
{% if page_obj %}
|
||||||
|
<div class="table-responsive px-1 scrollbar mt-3">
|
||||||
|
<table class= "table align-items-center table-flush table-hover">
|
||||||
|
<thead>
|
||||||
|
<tr class="bg-body-highlight">
|
||||||
|
<th class="sort white-space-nowrap align-middle" scope="col">{{ _("Timestamp") |capfirst }}</th>
|
||||||
|
<th class="sort white-space-nowrap align-middle" scope="col">{{ _("User") |capfirst }}</th>
|
||||||
|
<th class="sort white-space-nowrap align-middle" scope="col">{{ _("Event Type") }}</th>
|
||||||
|
<th class="sort white-space-nowrap align-middle" scope="col">{{ _("username") |capfirst }}</th>
|
||||||
|
<th class="sort white-space-nowrap align-middle"scope="col">{{ _("IP Address") |capfirst }}</th>
|
||||||
|
</tr>
|
||||||
|
</thead>
|
||||||
|
<tbody class="list">
|
||||||
|
{% for event in page_obj.object_list %}
|
||||||
|
<tr class="hover-actions-trigger btn-reveal-trigger position-static">
|
||||||
|
|
||||||
|
<td class="align-middle product white-space-nowrap">{{event.datetime}}</td>
|
||||||
|
<td class="align-middle product white-space-nowrap">{{ event.user.username|default:"N/A" }}</td>
|
||||||
|
<td class="align-middle product white-space-nowrap">{{ event.get_login_type_display}}</td>
|
||||||
|
<td class="align-middle product white-space-nowrap">{{ event.username}}</td>
|
||||||
|
<td class="align-middle product white-space-nowrap">{{ event.remote_ip}}</td>
|
||||||
|
|
||||||
|
</tr>
|
||||||
|
{% endfor %}
|
||||||
|
</tbody>
|
||||||
|
</table>
|
||||||
|
</div>
|
||||||
|
<div class="d-flex justify-content-end mt-3">
|
||||||
|
<div class="d-flex">
|
||||||
|
{% if is_paginated %}
|
||||||
|
{% include 'partials/pagination.html' %}
|
||||||
|
{% endif %}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
{% include 'partials/pagination_audit.html' with q='loginEvents' %}
|
||||||
|
|
||||||
|
{% else %}
|
||||||
|
<p>No authentication audit events found.</p>
|
||||||
|
{% endif %}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
{% endblock %}
|
||||||
|
|
||||||
@ -2,17 +2,28 @@
|
|||||||
{% load i18n %}
|
{% load i18n %}
|
||||||
{%block title%} {%trans 'Admin Management' %} {%endblock%}
|
{%block title%} {%trans 'Admin Management' %} {%endblock%}
|
||||||
{% block content %}
|
{% block content %}
|
||||||
<h1 class="mt-4"><i class="fas fa-tools me-2"> </i>{{ _("Admin Management")}}</h1>
|
<div class="row row-cols-1 row-cols-sm-2 row-cols-md-4 g-4 mt-10">
|
||||||
<div class="row row-cols-1 row-cols-md-4 g-4 mt-10">
|
|
||||||
<div class="col">
|
<div class="col">
|
||||||
<a href="{% url 'user_management' %}">
|
<a href="{% url 'user_management' %}">
|
||||||
<div class="card h-100">
|
<div class="card h-100">
|
||||||
<div class="card-header text-center">
|
<div class="card-header text-center">
|
||||||
<h5 class="card-title">{{ _("User Management")}}</h5>
|
<h5 class="card-title">{{ _("User Management")}}</h5>
|
||||||
<span class="me-2"><i class="fas fa-user fa-2x"></i></span>
|
<span class="me-2"><i class="fas fa-user fa-2x"></i></span>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</a>
|
</a>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
<div class="col">
|
||||||
|
<a href="{% url 'audit_log_dashboard' %}">
|
||||||
|
<div class="card h-100">
|
||||||
|
<div class="card-header text-center">
|
||||||
|
<h5 class="card-title">{{ _("Audit Log Dashboard")}}</h5>
|
||||||
|
<span class="me-2"><i class="fas fa-user fa-2x"></i></span>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</a>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
|
||||||
{% endblock content %}
|
{% endblock content %}
|
||||||
128
templates/admin_management/model_logs.html
Normal file
128
templates/admin_management/model_logs.html
Normal file
@ -0,0 +1,128 @@
|
|||||||
|
|
||||||
|
{% extends "base.html" %}
|
||||||
|
{% load i18n custom_filters %}
|
||||||
|
{% block title %}{% trans "Accounts" %}{% endblock title %}
|
||||||
|
{% block accounts %}
|
||||||
|
<a class="nav-link active fw-bold">
|
||||||
|
{% trans "Accounts"|capfirst %}
|
||||||
|
<span class="visually-hidden">(current)</span>
|
||||||
|
</a>
|
||||||
|
{% endblock %}
|
||||||
|
{% block content %}
|
||||||
|
<div class="row mt-4">
|
||||||
|
|
||||||
|
<div class="d-flex justify-content-between mb-2">
|
||||||
|
<h3 class=""><i class="fa-solid fa-book"></i> {% trans "Audit Log Dashboard" %}</h3>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- Log Type Tabs -->
|
||||||
|
<div class="mb-4">
|
||||||
|
{% include 'admin_management/nav.html' %}
|
||||||
|
|
||||||
|
<div class="tab-content p-3 border border-top-0 rounded-bottom" id="accountTypeTabsContent">
|
||||||
|
<!-- modellogs Tab -->
|
||||||
|
|
||||||
|
{% if page_obj %}
|
||||||
|
<div class="table-responsive px-1 scrollbar mt-3">
|
||||||
|
<table class="table align-items-center table-flush table-hover mt-3">
|
||||||
|
<thead>
|
||||||
|
<tr class="bg-body-highlight">
|
||||||
|
<th>{% trans "Timestamp" %}</th>
|
||||||
|
<th>{% trans "User" %}</th>
|
||||||
|
<th>{% trans "Action" %}</th>
|
||||||
|
<th>{% trans "Model" %}</th>
|
||||||
|
<th>{% trans "Object ID" %}</th>
|
||||||
|
<th>{% trans "Object Representation" %}</th>
|
||||||
|
<th>{% trans "Field" %}</th> {# Dedicated column for field name #}
|
||||||
|
<th>{% trans "Old Value" %}</th> {# Dedicated column for old value #}
|
||||||
|
<th>{% trans "New Value" %}</th> {# Dedicated column for new value #}
|
||||||
|
</tr>
|
||||||
|
</thead>
|
||||||
|
<tbody>
|
||||||
|
{% for event in page_obj.object_list %}
|
||||||
|
{% if event.field_changes %}
|
||||||
|
{# Loop through each individual field change for this event #}
|
||||||
|
{% for change in event.field_changes %}
|
||||||
|
<tr>
|
||||||
|
{# Display common event details using rowspan for the first change #}
|
||||||
|
{% if forloop.first %}
|
||||||
|
<td rowspan="{{ event.field_changes|length }}">
|
||||||
|
{{ event.datetime|date:"Y-m-d H:i:s" }}
|
||||||
|
</td>
|
||||||
|
<td rowspan="{{ event.field_changes|length }}">
|
||||||
|
{{ event.user.username|default:"Anonymous" }}
|
||||||
|
</td>
|
||||||
|
<td rowspan="{{ event.field_changes|length }}">
|
||||||
|
{{ event.event_type_display }}
|
||||||
|
</td>
|
||||||
|
<td rowspan="{{ event.field_changes|length }}">
|
||||||
|
{{ event.model_name|title }}
|
||||||
|
</td>
|
||||||
|
<td rowspan="{{ event.field_changes|length }}">
|
||||||
|
{{ event.object_id }}
|
||||||
|
</td>
|
||||||
|
<td rowspan="{{ event.field_changes|length }}">
|
||||||
|
{{ event.object_repr }}
|
||||||
|
</td>
|
||||||
|
{% endif %}
|
||||||
|
|
||||||
|
{# Display the specific field change details in their own columns #}
|
||||||
|
<td><strong>{{ change.field }}</strong></td>
|
||||||
|
<td>
|
||||||
|
{% if change.old is not None %}
|
||||||
|
<pre style="white-space: pre-wrap; word-break: break-all; font-size: 0.85em; background-color: #f8f9fa; padding: 5px; border-radius: 3px;">{{ change.old }}</pre>
|
||||||
|
{% else %}
|
||||||
|
(None)
|
||||||
|
{% endif %}
|
||||||
|
</td>
|
||||||
|
<td>
|
||||||
|
{% if change.new is not None %}
|
||||||
|
<pre style="white-space: pre-wrap; word-break: break-all; font-size: 0.85em; background-color: #f8f9fa; padding: 5px; border-radius: 3px;">{{ change.new }}</pre>
|
||||||
|
{% else %}
|
||||||
|
(None)
|
||||||
|
{% endif %}
|
||||||
|
</td>
|
||||||
|
</tr>
|
||||||
|
{% endfor %}
|
||||||
|
{% else %}
|
||||||
|
{# Fallback for events with no specific field changes (e.g., CREATE, DELETE) #}
|
||||||
|
<tr>
|
||||||
|
<td>{{ event.datetime|date:"Y-m-d H:i:s" }}</td>
|
||||||
|
<td>{{ event.user.username|default:"Anonymous" }}</td>
|
||||||
|
<td>{{ event.event_type_display }}</td>
|
||||||
|
<td>{{ event.model_name|title }}</td>
|
||||||
|
<td>{{ event.object_id }}</td>
|
||||||
|
<td>{{ event.object_repr }}</td>
|
||||||
|
{# Span the 'Field', 'Old Value', 'New Value' columns #}
|
||||||
|
<td>
|
||||||
|
{% if event.event_type_display == "Create" %}
|
||||||
|
{% trans "Object created." %}
|
||||||
|
{% elif event.event_type_display == "Delete" %}
|
||||||
|
{% trans "Object deleted." %}
|
||||||
|
{% else %}
|
||||||
|
{% trans "No specific field changes recorded." %}
|
||||||
|
{% endif %}
|
||||||
|
</td>
|
||||||
|
</tr>
|
||||||
|
{% endif %}
|
||||||
|
{% endfor %}
|
||||||
|
</tbody>
|
||||||
|
</table>
|
||||||
|
|
||||||
|
</div>
|
||||||
|
{% include 'partials/pagination_audit.html' with q='userActions' %}
|
||||||
|
|
||||||
|
{% else %}
|
||||||
|
<p>{% trans "No model change audit events found." %}</p>
|
||||||
|
{% endif %}
|
||||||
|
|
||||||
|
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
{% endblock %}
|
||||||
|
|
||||||
19
templates/admin_management/nav.html
Normal file
19
templates/admin_management/nav.html
Normal file
@ -0,0 +1,19 @@
|
|||||||
|
{% load i18n %}
|
||||||
|
<ul class="nav nav-tabs" id="accountTypeTabs" role="tablist">
|
||||||
|
|
||||||
|
<li class="nav-item me-3" role="presentation">
|
||||||
|
<a href="{% url 'audit_log_dashboard' %}?q=userActions">
|
||||||
|
<i class="fas fa-history me-2"></i>{% trans "User Actions" %}
|
||||||
|
</a>
|
||||||
|
</li>
|
||||||
|
<li class="nav-item me-3" role="presentation">
|
||||||
|
<a href="{% url 'audit_log_dashboard' %}?q=loginEvents">
|
||||||
|
<i class="fas fa-right-to-bracket me-2"></i>{% trans "User Login Events" %}
|
||||||
|
</a>
|
||||||
|
</li>
|
||||||
|
<li class="nav-item" role="presentation">
|
||||||
|
<a href="{% url 'audit_log_dashboard' %}?q=userRequests">
|
||||||
|
<i class="fas fa-file-alt me-2"></i>{% trans "User Page Requests" %}
|
||||||
|
</a>
|
||||||
|
</li>
|
||||||
|
</ul>
|
||||||
64
templates/admin_management/request_logs.html
Normal file
64
templates/admin_management/request_logs.html
Normal file
@ -0,0 +1,64 @@
|
|||||||
|
|
||||||
|
{% extends "base.html" %}
|
||||||
|
{% load i18n custom_filters %}
|
||||||
|
{% block title %}{% trans "Accounts" %}{% endblock title %}
|
||||||
|
{% block accounts %}
|
||||||
|
<a class="nav-link active fw-bold">
|
||||||
|
{% trans "Accounts"|capfirst %}
|
||||||
|
<span class="visually-hidden">(current)</span>
|
||||||
|
</a>
|
||||||
|
{% endblock %}
|
||||||
|
{% block content %}
|
||||||
|
<div class="row mt-4">
|
||||||
|
|
||||||
|
<div class="d-flex justify-content-between mb-2">
|
||||||
|
<h3 class=""><i class="fa-solid fa-book"></i> {% trans "Audit Log Dashboard" %}</h3>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- Log Type Tabs -->
|
||||||
|
<div class="mb-4">
|
||||||
|
{% include 'admin_management/nav.html' %}
|
||||||
|
|
||||||
|
<div class="tab-content p-3 border border-top-0 rounded-bottom" id="accountTypeTabsContent">
|
||||||
|
<!-- modellogs Tab -->
|
||||||
|
{% if page_obj %}
|
||||||
|
<div class="table-responsive px-1 scrollbar mt-3">
|
||||||
|
<table class= "table align-items-center table-flush table-hover">
|
||||||
|
<thead>
|
||||||
|
<tr class="bg-body-highlight">
|
||||||
|
<th class="sort white-space-nowrap align-middle" scope="col">{{ _("Timestamp") |capfirst }}</th>
|
||||||
|
<th class="sort white-space-nowrap align-middle" scope="col">{{ _("User") |capfirst }}</th>
|
||||||
|
<th class="sort white-space-nowrap align-middle" scope="col">{{ _("URL") }}</th>
|
||||||
|
<th class="sort white-space-nowrap align-middle" scope="col">{{ _("Method") |capfirst }}</th>
|
||||||
|
<th class="sort white-space-nowrap align-middle"scope="col">{{ _("IP Address") |capfirst }}</th>
|
||||||
|
</tr>
|
||||||
|
</thead>
|
||||||
|
<tbody class="list">
|
||||||
|
{% for event in page_obj.object_list %}
|
||||||
|
<tr class="hover-actions-trigger btn-reveal-trigger position-static">
|
||||||
|
|
||||||
|
<td class="align-middle product white-space-nowrap">{{event.datetime}}</td>
|
||||||
|
<td class="align-middle product white-space-nowrap">{{ event.user.username|default:"Anonymous" }}</td>
|
||||||
|
<td class="align-middle product white-space-nowrap">{{ event.url }}</td>
|
||||||
|
<td class="align-middle product white-space-nowrap">{{ event.method}}</td>
|
||||||
|
<td class="align-middle product white-space-nowrap">{{ event.remote_ip}}</td>
|
||||||
|
|
||||||
|
</tr>
|
||||||
|
{% endfor %}
|
||||||
|
</tbody>
|
||||||
|
</table>
|
||||||
|
</div>
|
||||||
|
{% include 'partials/pagination_audit.html' with q='userRequests' %}
|
||||||
|
{% else %}
|
||||||
|
<p>No request audit events found.</p>
|
||||||
|
{% endif %}
|
||||||
|
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
{% endblock %}
|
||||||
|
|
||||||
@ -1,14 +1,6 @@
|
|||||||
{% extends 'base.html' %}
|
{% extends 'base.html' %}
|
||||||
{% load i18n static humanize %}
|
{% load i18n static humanize %}
|
||||||
{% block title %}{{ _("Opportunity Detail") }}{% endblock title %}
|
{% block title %}{{ _("Opportunity Detail") }}{% endblock title %}
|
||||||
{% block customCSS %}
|
|
||||||
<style>
|
|
||||||
.completed-task {
|
|
||||||
text-decoration: line-through;
|
|
||||||
opacity: 0.7;
|
|
||||||
}
|
|
||||||
</style>
|
|
||||||
{% endblock customCSS %}
|
|
||||||
{% block content %}
|
{% block content %}
|
||||||
<div class="row align-items-center justify-content-between g-3 mb-4">
|
<div class="row align-items-center justify-content-between g-3 mb-4">
|
||||||
<div class="col-12 col-md-auto">
|
<div class="col-12 col-md-auto">
|
||||||
@ -87,12 +79,12 @@
|
|||||||
<div class="col-12 overflow-auto" style="max-height: 200px;">
|
<div class="col-12 overflow-auto" style="max-height: 200px;">
|
||||||
<ul class="list-group list-group-flush">
|
<ul class="list-group list-group-flush">
|
||||||
{% for event in opportunity.get_schedules %}
|
{% for event in opportunity.get_schedules %}
|
||||||
<li class="list-group-item d-flex justify-content-between ">
|
<li class="list-group-item d-flex justify-content-between align-items-center">
|
||||||
|
<div class="d-flex align-items-center">
|
||||||
<span class="badge rounded-pill bg-phoenix-primary text-primary me-2 fs-9">{{ event.scheduled_type|capfirst }}</span>
|
<span class="badge rounded-pill bg-phoenix-primary text-primary me-2 fs-9">{{ event.scheduled_type|capfirst }}</span>
|
||||||
<div class="fs-9 d-flex flex-column">
|
<span class="fs-9">{{ event.purpose }}</span>
|
||||||
<span class="fs-9">{{ event.purpose|capfirst }}</span>
|
</div>
|
||||||
<span><span class="badge rounded-pill bg-phoenix-primary text-secondary"><span class="d-inline-block lh-sm me-1" data-feather="clock" style="height:16px;width:16px;"></span> {{ event.scheduled_at|naturaltime|capfirst }}</span></span>
|
<div class="fs-9">{{ event.scheduled_at|naturaltime|capfirst }}</div>
|
||||||
</div>
|
|
||||||
</li>
|
</li>
|
||||||
{% empty %}
|
{% empty %}
|
||||||
<li class="list-group-item text-center fs-9">{{ _("No upcoming events") }}</li>
|
<li class="list-group-item text-center fs-9">{{ _("No upcoming events") }}</li>
|
||||||
@ -107,22 +99,12 @@
|
|||||||
<h4 class="mb-5 d-flex align-items-center"><span class="d-inline-block lh-sm me-1" data-feather="link" style="height:16px;width:16px;"></span> {{ _("Related Records")}}</h4>
|
<h4 class="mb-5 d-flex align-items-center"><span class="d-inline-block lh-sm me-1" data-feather="link" style="height:16px;width:16px;"></span> {{ _("Related Records")}}</h4>
|
||||||
<div class="row g-3">
|
<div class="row g-3">
|
||||||
<div class="col-12">
|
<div class="col-12">
|
||||||
<div class="mb-4">
|
|
||||||
<div class="d-flex flex-wrap justify-content-between mb-2">
|
|
||||||
<h5 class="mb-0 text-body-highlight me-2">{{ _("Lead") }}</h5>
|
|
||||||
</div>
|
|
||||||
{% if opportunity.lead %}
|
|
||||||
<span class="badge rounded-pill bg-phoenix-primary text-primary"><a class="dropdown-item d-flex align-items-center" href="{% url 'lead_detail' opportunity.lead.slug %}" target="_blank">{{ _("View Lead")}}  <span class="d-inline-block lh-sm me-2" data-feather="external-link" style="height:16px;width:16px;"></span></a></span>
|
|
||||||
{% else %}
|
|
||||||
<p>{{ _("No Lead") }}</p>
|
|
||||||
{% endif %}
|
|
||||||
</div>
|
|
||||||
<div class="mb-4">
|
<div class="mb-4">
|
||||||
<div class="d-flex flex-wrap justify-content-between mb-2">
|
<div class="d-flex flex-wrap justify-content-between mb-2">
|
||||||
<h5 class="mb-0 text-body-highlight me-2">{{ _("Estimate") }}</h5>
|
<h5 class="mb-0 text-body-highlight me-2">{{ _("Estimate") }}</h5>
|
||||||
</div>
|
</div>
|
||||||
{% if opportunity.estimate %}
|
{% if opportunity.estimate %}
|
||||||
<span class="badge rounded-pill bg-phoenix-primary text-primary"><a class="dropdown-item" href="{% url 'estimate_detail' opportunity.estimate.pk %}">{{ _("View Quotation")}}</a></span>
|
<a class="dropdown-item" href="{% url 'estimate_detail' opportunity.estimate.pk %}">{{ _("View Quotation")}}</a>
|
||||||
{% else %}
|
{% else %}
|
||||||
<p>{{ _("No Estimate") }}</p>
|
<p>{{ _("No Estimate") }}</p>
|
||||||
{% endif %}
|
{% endif %}
|
||||||
@ -132,7 +114,7 @@
|
|||||||
<h5 class="mb-0 text-body-highlight me-2">{{ _("Invoice") }}</h5>
|
<h5 class="mb-0 text-body-highlight me-2">{{ _("Invoice") }}</h5>
|
||||||
</div>
|
</div>
|
||||||
{% if opportunity.estimate.invoice %}
|
{% if opportunity.estimate.invoice %}
|
||||||
<span class="badge rounded-pill bg-phoenix-primary text-primary"><a class="dropdown-item" href="{% url 'invoice_detail' opportunity.estimate.invoice.pk %}">{{ _("View Invoice")}}</a></span>
|
<a class="dropdown-item" href="{% url 'invoice_detail' opportunity.estimate.invoice.pk %}">{{ _("View Invoice")}}</a>
|
||||||
{% else %}
|
{% else %}
|
||||||
<p>{{ _("No Invoice") }}</p>
|
<p>{{ _("No Invoice") }}</p>
|
||||||
{% endif %}
|
{% endif %}
|
||||||
@ -252,7 +234,7 @@
|
|||||||
</div>
|
</div>
|
||||||
</td>
|
</td>
|
||||||
<td class="py-2 d-none d-sm-block pe-sm-2">:</td>
|
<td class="py-2 d-none d-sm-block pe-sm-2">:</td>
|
||||||
<td class="py-2"><a class="ps-6 ps-sm-0 fw-semibold mb-0 pb-3 pb-sm-0 text-body" href="tel:{{ opportunity.customer.phone_number }}">{{ opportunity.lead.phone_number }}</a></td>
|
<td class="py-2"><a class="ps-6 ps-sm-0 fw-semibold mb-0 pb-3 pb-sm-0 text-body" href="tel:{{ opportunity.customer.phone_number }}">{{ opportunity.customer.phone_number }}</a></td>
|
||||||
</tr>
|
</tr>
|
||||||
<tr>
|
<tr>
|
||||||
<td class="py-2">
|
<td class="py-2">
|
||||||
@ -262,7 +244,7 @@
|
|||||||
</div>
|
</div>
|
||||||
</td>
|
</td>
|
||||||
<td class="py-2 d-none d-sm-block pe-sm-2">:</td>
|
<td class="py-2 d-none d-sm-block pe-sm-2">:</td>
|
||||||
<td class="py-2"><a class="ps-6 ps-sm-0 fw-semibold mb-0 text-body" href="mailto:{{ opportunity.customer.email}}">{{ opportunity.lead.email}}</a></td>
|
<td class="py-2"><a class="ps-6 ps-sm-0 fw-semibold mb-0 text-body" href="mailto:{{ opportunity.customer.email}}">{{ opportunity.customer.email}}</a></td>
|
||||||
</tr>
|
</tr>
|
||||||
</table>
|
</table>
|
||||||
</div>
|
</div>
|
||||||
@ -450,7 +432,7 @@
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div class="row g-3">
|
<div class="row g-3">
|
||||||
{% for metting in opportunity.get_meetings %}
|
{% for metting in opportunity.lead.get_meetings %}
|
||||||
<div class="col-xxl-6">
|
<div class="col-xxl-6">
|
||||||
<div class="card h-100">
|
<div class="card h-100">
|
||||||
<div class="card-body">
|
<div class="card-body">
|
||||||
@ -472,10 +454,12 @@
|
|||||||
<div class="col-auto d-flex flex-1">
|
<div class="col-auto d-flex flex-1">
|
||||||
<h2 class="mb-0">Call</h2>
|
<h2 class="mb-0">Call</h2>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class="col-auto">
|
<div class="col-auto">
|
||||||
<a href="{% url 'schedule_lead' opportunity.lead.slug %}" class="btn btn-primary"><span class="fa-solid fa-plus me-2"></span>Add Call</a>
|
<a href="{% url 'schedule_lead' opportunity.lead.slug %}" class="btn btn-primary"><span class="fa-solid fa-plus me-2"></span>Add Call</a>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
<pre>{{opportunity.get_all_notes}}</pre>
|
||||||
<div class="border-top border-bottom border-translucent" id="leadDetailsTable" data-list='{"valueNames":["name","description","create_date","create_by","last_activity"],"page":5,"pagination":true}'>
|
<div class="border-top border-bottom border-translucent" id="leadDetailsTable" data-list='{"valueNames":["name","description","create_date","create_by","last_activity"],"page":5,"pagination":true}'>
|
||||||
<div class="table-responsive scrollbar mx-n1 px-1">
|
<div class="table-responsive scrollbar mx-n1 px-1">
|
||||||
<table class="table fs-9 mb-0">
|
<table class="table fs-9 mb-0">
|
||||||
@ -487,7 +471,7 @@
|
|||||||
</tr>
|
</tr>
|
||||||
</thead>
|
</thead>
|
||||||
<tbody class="list" id="lead-details-table-body">
|
<tbody class="list" id="lead-details-table-body">
|
||||||
{% for call in opportunity.get_calls %}
|
{% for call in opportunity.lead.get_calls %}
|
||||||
<tr class="hover-actions-trigger btn-reveal-trigger position-static">
|
<tr class="hover-actions-trigger btn-reveal-trigger position-static">
|
||||||
<td class="description align-middle white-space-nowrap text-start fw-bold text-body-tertiary py-2 pe-6">{{call.purpose}}</td>
|
<td class="description align-middle white-space-nowrap text-start fw-bold text-body-tertiary py-2 pe-6">{{call.purpose}}</td>
|
||||||
<td class="create_date text-end align-middle white-space-nowrap text-body py-2">{{call.scheduled_by}}</td>
|
<td class="create_date text-end align-middle white-space-nowrap text-body py-2">{{call.scheduled_by}}</td>
|
||||||
@ -559,7 +543,7 @@
|
|||||||
</tr>
|
</tr>
|
||||||
</thead>
|
</thead>
|
||||||
<tbody class="list" id="all-email-table-body">
|
<tbody class="list" id="all-email-table-body">
|
||||||
{% for email in opportunity.get_emails %}
|
{% for email in opportunity.lead.get_emails %}
|
||||||
<tr class="hover-actions-trigger btn-reveal-trigger position-static">
|
<tr class="hover-actions-trigger btn-reveal-trigger position-static">
|
||||||
<td class="fs-9 align-middle px-0 py-3">
|
<td class="fs-9 align-middle px-0 py-3">
|
||||||
<div class="form-check mb-0 fs-8">
|
<div class="form-check mb-0 fs-8">
|
||||||
|
|||||||
@ -1,5 +1,6 @@
|
|||||||
{% extends "base.html" %}
|
{% extends "base.html" %}
|
||||||
{% load i18n %}
|
{% load i18n %}
|
||||||
|
{%block title%} {%trans 'Add Colors'%} {% endblock%}
|
||||||
{% block content %}
|
{% block content %}
|
||||||
<div class="row mt-4">
|
<div class="row mt-4">
|
||||||
<h5 class="text-center">{% trans "Add Colors" %}</h5>
|
<h5 class="text-center">{% trans "Add Colors" %}</h5>
|
||||||
@ -18,7 +19,8 @@
|
|||||||
<input class="color-radio"
|
<input class="color-radio"
|
||||||
type="radio"
|
type="radio"
|
||||||
name="exterior"
|
name="exterior"
|
||||||
value="{{ color.id }}">
|
value="{{ color.id }}" {% if color.id == form.instance.exterior.id %}checked{% endif %}>
|
||||||
|
|
||||||
<div class="card-body color-display"
|
<div class="card-body color-display"
|
||||||
style="background-color: rgb({{ color.rgb }})">
|
style="background-color: rgb({{ color.rgb }})">
|
||||||
<div class="">
|
<div class="">
|
||||||
@ -38,7 +40,7 @@
|
|||||||
<input class="color-radio"
|
<input class="color-radio"
|
||||||
type="radio"
|
type="radio"
|
||||||
name="interior"
|
name="interior"
|
||||||
value="{{ color.id }}">
|
value="{{ color.id }}" {% if color.id == form.instance.interior.id %}checked{% endif %}>
|
||||||
<div class="card-body color-display"
|
<div class="card-body color-display"
|
||||||
style="background-color: rgb({{ color.rgb }})">
|
style="background-color: rgb({{ color.rgb }})">
|
||||||
<div class="">
|
<div class="">
|
||||||
|
|||||||
@ -1,4 +1,4 @@
|
|||||||
{% extends 'base.html' %}
|
{% extends 'base.html' %}
|
||||||
{% load i18n static custom_filters %}
|
{% load i18n static custom_filters %}
|
||||||
{% block title %}{{ _("Car Details") }}{% endblock %}
|
{% block title %}{{ _("Car Details") }}{% endblock %}
|
||||||
{% block customCSS %}
|
{% block customCSS %}
|
||||||
@ -17,14 +17,24 @@
|
|||||||
{% endblock customCSS %}
|
{% endblock customCSS %}
|
||||||
|
|
||||||
{% block content %}
|
{% block content %}
|
||||||
{% if not car.ready %}
|
|
||||||
|
{% if not car.ready and not car.status == 'sold' %}
|
||||||
<div class="alert alert-outline-warning d-flex align-items-center"
|
<div class="alert alert-outline-warning d-flex align-items-center"
|
||||||
role="alert">
|
role="alert">
|
||||||
<i class="fa-solid fa-circle-info fs-6"></i>
|
<i class="fa-solid fa-circle-info fs-6"></i>
|
||||||
|
{%if not car.finances and not car.colors%}
|
||||||
<p class="mb-0 flex-1">
|
<p class="mb-0 flex-1">
|
||||||
{{ _("This car information is not complete , please add colors and finances before making it ready for sale .") }}<a class="ms-3 text-body-primary fs-9"
|
{{ _("This car information is not complete , please add colors and finances both before making it ready for sale .") }}
|
||||||
href="{% url 'add_color' car.slug %}">{{ _("Add Color") }}</a>
|
|
||||||
</p>
|
</p>
|
||||||
|
{% elif car.finances and not car.colors %}
|
||||||
|
<p class="mb-0 flex-1">
|
||||||
|
{{ _("This car information is not complete , please add colors before making it ready for sale .") }}
|
||||||
|
</p>
|
||||||
|
{%else%}
|
||||||
|
<p class="mb-0 flex-1">
|
||||||
|
{{ _("This car information is not complete , please add finances before making it ready for sale .") }}
|
||||||
|
</p>
|
||||||
|
{%endif%}
|
||||||
<button class="btn-close"
|
<button class="btn-close"
|
||||||
type="button"
|
type="button"
|
||||||
data-bs-dismiss="alert"
|
data-bs-dismiss="alert"
|
||||||
@ -237,7 +247,7 @@
|
|||||||
</tr>
|
</tr>
|
||||||
<tr>
|
<tr>
|
||||||
<th>{% trans "Discount Amount"|capfirst %}</th>
|
<th>{% trans "Discount Amount"|capfirst %}</th>
|
||||||
<td>{{ car.finances.discount_amount|floatformat:2 }} -</td>
|
<td>{{ car.finances.discount_amount|floatformat:2 }}</td>
|
||||||
</tr>
|
</tr>
|
||||||
<tr>
|
<tr>
|
||||||
<th>{% trans "Additional Fee"|capfirst %}</th>
|
<th>{% trans "Additional Fee"|capfirst %}</th>
|
||||||
@ -285,6 +295,8 @@
|
|||||||
<div class="card-body">
|
<div class="card-body">
|
||||||
<div class="table-responsive scrollbar mb-3">
|
<div class="table-responsive scrollbar mb-3">
|
||||||
<table class="table table-sm fs-9 mb-0 overflow-hidden">
|
<table class="table table-sm fs-9 mb-0 overflow-hidden">
|
||||||
|
|
||||||
|
<!--test-->
|
||||||
{% if car.colors %}
|
{% if car.colors %}
|
||||||
<tr>
|
<tr>
|
||||||
<th>{% trans 'Exterior' %}</th>
|
<th>{% trans 'Exterior' %}</th>
|
||||||
@ -309,32 +321,27 @@
|
|||||||
|
|
||||||
<tr>
|
<tr>
|
||||||
<td colspan="2">
|
<td colspan="2">
|
||||||
{% comment %} {% if not car.get_transfer %}
|
{% if not car.get_transfer %}
|
||||||
<a href="{% url 'car_finance_update' car.finances.pk %}"
|
<a href="{% url 'car_colors_update' car.slug %}"
|
||||||
class="btn btn-phoenix-warning btn-sm mb-3">{% trans "Edit" %}</a>
|
class="btn btn-phoenix-warning btn-sm mb-3">{% trans "Edit" %}</a>
|
||||||
{% else %}
|
{% else %}
|
||||||
<span class="badge bg-danger">{% trans "Cannot Edit, Car in Transfer." %}</span>
|
<span class="badge bg-danger">{% trans "Cannot Edit, Car in Transfer." %}</span>
|
||||||
{% endif %}
|
{% endif %}
|
||||||
{% else %}
|
|
||||||
<p>{% trans "No finance details available." %}</p>
|
|
||||||
{% if perms.inventory.add_carfinance %}
|
|
||||||
<a href="{% url 'car_finance_create' car.slug %}"
|
|
||||||
class="btn btn-phoenix-success btn-sm mb-3">{% trans "Add" %}</a>
|
|
||||||
{% endif %} {% endcomment %}
|
|
||||||
</td>
|
</td>
|
||||||
</tr>
|
</tr>
|
||||||
{% comment %} <tr>
|
{% else %}
|
||||||
<td colspan="2">{% trans "No colors available for this car." %}</td>
|
|
||||||
</tr>
|
|
||||||
<tr>
|
<tr>
|
||||||
<td colspan="2">
|
|
||||||
{% if perms.inventory.change_carcolors %}
|
<td colspan="2">
|
||||||
<a href="{% url 'add_color' car.slug %}"
|
<p>{% trans "No color details available." %}</p>
|
||||||
class="btn btn-phoenix-success btn-sm">{% trans "Add" %}</a>
|
{% if perms.inventory.add_carcolors %}
|
||||||
|
<a class="btn btn-phoenix-success btn-sm mb-3" href="{% url 'add_color' car.slug %}">{{ _("Add Color") }}</a>
|
||||||
{% endif %}
|
{% endif %}
|
||||||
</td>
|
</td>
|
||||||
</tr> {% endcomment %}
|
</tr>
|
||||||
{% endif %}
|
{% endif %}
|
||||||
|
<!--test-->
|
||||||
|
|
||||||
</table>
|
</table>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
32
templates/partials/pagination_audit.html
Normal file
32
templates/partials/pagination_audit.html
Normal file
@ -0,0 +1,32 @@
|
|||||||
|
{% load i18n static %}
|
||||||
|
|
||||||
|
<div class="row align-items-center justify-content-center py-4 pe-0 fs-9">
|
||||||
|
<div class="col-auto d-flex">
|
||||||
|
{# Previous Button #}
|
||||||
|
{% if page_obj.has_previous %}
|
||||||
|
<a class="page-link" href="?page={{ page_obj.previous_page_number }}{% if q %}&q={{q}}{% endif %}" aria-label="{% trans 'Previous' %}">
|
||||||
|
<span class="fas fa-chevron-{% if LANGUAGE_CODE == 'ar' %}right{% else %}left{% endif %}" aria-hidden="true"></span>
|
||||||
|
<span>{% trans "Previous" %}</span>
|
||||||
|
</a>
|
||||||
|
{% else %}
|
||||||
|
<span class="page-link disabled" aria-disabled="true">
|
||||||
|
<span class="fas fa-chevron-{% if LANGUAGE_CODE == 'ar' %}right{% else %}left{% endif %}" aria-hidden="true"></span>
|
||||||
|
<span>{% trans "Previous" %}</span>
|
||||||
|
</span>
|
||||||
|
{% endif %}
|
||||||
|
|
||||||
|
|
||||||
|
{# Next Button #}
|
||||||
|
{% if page_obj.has_next %}
|
||||||
|
<a class="page-link" href="?page={{ page_obj.next_page_number }}{% if q %}&q={{q}}{% endif %}" aria-label="{% trans 'Next' %}">
|
||||||
|
<span>{% trans "Next" %}</span>
|
||||||
|
<span class="fas fa-chevron-{% if LANGUAGE_CODE == 'ar' %}left{% else %}right{% endif %}" aria-hidden="true"></span>
|
||||||
|
</a>
|
||||||
|
{% else %}
|
||||||
|
<span class="page-link disabled" aria-disabled="true">
|
||||||
|
<span>{% trans "Next" %}</span>
|
||||||
|
<span class="fas fa-chevron-{% if LANGUAGE_CODE == 'ar' %}left{% else %}right{% endif %}" aria-hidden="true"></span>
|
||||||
|
</span>
|
||||||
|
{% endif %}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
@ -15,7 +15,7 @@ document.addEventListener('DOMContentLoaded', function() {
|
|||||||
|
|
||||||
document.getElementById('POModalBody').innerHTML = `
|
document.getElementById('POModalBody').innerHTML = `
|
||||||
<div class="d-flex justify-content-center gap-3 py-3">
|
<div class="d-flex justify-content-center gap-3 py-3">
|
||||||
<a class="btn btn-primary px-4" href="${actionUrl}">
|
<a class="btn btn-outline-primary px-4" href="${actionUrl}">
|
||||||
<i class="fas fa-check-circle me-2"></i>${buttonText}
|
<i class="fas fa-check-circle me-2"></i>${buttonText}
|
||||||
</a>
|
</a>
|
||||||
<button class="btn btn-outline-secondary" data-bs-dismiss="modal">
|
<button class="btn btn-outline-secondary" data-bs-dismiss="modal">
|
||||||
@ -45,7 +45,7 @@ document.addEventListener('DOMContentLoaded', function() {
|
|||||||
|
|
||||||
<div class="card-body">
|
<div class="card-body">
|
||||||
<h3 class="h4 mb-4">
|
<h3 class="h4 mb-4">
|
||||||
<span class="badge bg-{% if po_model.is_draft %}secondary{% elif po_model.is_approved %}success{% elif po_model.is_fulfilled %}primary{% else %}warning{% endif %}">
|
<span class="badge bg-{% if po_model.is_draft %}success{% elif po_model.is_approved %}success{% elif po_model.is_fulfilled %}primary{% else %}warning{% endif %}">
|
||||||
{{ po_model.get_po_status_display }}
|
{{ po_model.get_po_status_display }}
|
||||||
</span>
|
</span>
|
||||||
</h3>
|
</h3>
|
||||||
@ -214,7 +214,7 @@ document.addEventListener('DOMContentLoaded', function() {
|
|||||||
{% endif %}
|
{% endif %}
|
||||||
|
|
||||||
{% if po_model.can_cancel %}
|
{% if po_model.can_cancel %}
|
||||||
<button class="btn btn-outline-danger"
|
<button class="btn btn-outline-secondary"
|
||||||
onclick="djLedger.toggleModal('{{ po_model.get_mark_as_canceled_html_id }}')">
|
onclick="djLedger.toggleModal('{{ po_model.get_mark_as_canceled_html_id }}')">
|
||||||
<i class="fas fa-window-close me-2"></i>{% trans 'Cancel' %}
|
<i class="fas fa-window-close me-2"></i>{% trans 'Cancel' %}
|
||||||
</button>
|
</button>
|
||||||
|
|||||||
@ -95,7 +95,7 @@
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div class="col-12">
|
<div class="col-12">
|
||||||
<button class="btn btn-primary">{% trans 'Save' %}</button>
|
<button class="btn btn-outline-success">{% trans 'Save' %}</button>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</form>
|
</form>
|
||||||
@ -2,7 +2,7 @@
|
|||||||
{% load static i18n crispy_forms_tags %}
|
{% load static i18n crispy_forms_tags %}
|
||||||
|
|
||||||
{% block content %}
|
{% block content %}
|
||||||
<div class="container-fluid m-0">
|
<div class="container-fluid mt-4">
|
||||||
<form action="" method="post">
|
<form action="" method="post">
|
||||||
{% csrf_token %}
|
{% csrf_token %}
|
||||||
<div class="form-group">
|
<div class="form-group">
|
||||||
@ -13,11 +13,11 @@
|
|||||||
<label for="account">Account</label>
|
<label for="account">Account</label>
|
||||||
<select class="form-control" name="account" id="account">
|
<select class="form-control" name="account" id="account">
|
||||||
{% for account in inventory_accounts %}
|
{% for account in inventory_accounts %}
|
||||||
<option value="{{ account.pk }}">{{ account }}"></option>
|
<option value="{{ account.pk }}">{{ account }}</option>
|
||||||
{% endfor %}
|
{% endfor %}
|
||||||
</select>
|
</select>
|
||||||
</div>
|
</div>
|
||||||
<button type="submit" class="btn btn-primary">Add New Item To Inventory</button>
|
<button type="submit" class="btn btn-primary mt-2">Add New Item To Inventory</button>
|
||||||
</form>
|
</form>
|
||||||
</div>
|
</div>
|
||||||
{% endblock content %}
|
{% endblock content %}
|
||||||
@ -1,74 +1,76 @@
|
|||||||
<!-- po_list.html -->
|
<!-- po_list.html -->
|
||||||
{% extends "base.html" %}
|
{% extends "base.html" %}
|
||||||
|
{% load i18n static %}
|
||||||
{% block title %}
|
{% block title %}
|
||||||
Purchase Orders - {{ block.super }}
|
Purchase Orders - {{ block.super }}
|
||||||
{% endblock %}
|
{% endblock %}
|
||||||
|
|
||||||
{% block content %}
|
{% block content %}
|
||||||
<div class="container mt-4">
|
<div class="row mt-4">
|
||||||
<h2 class="mb-4">Purchase Orders</h2>
|
<!-- Success Message -->
|
||||||
|
{% if messages %}
|
||||||
<!-- Success Message -->
|
{% for message in messages %}
|
||||||
{% if messages %}
|
|
||||||
{% for message in messages %}
|
|
||||||
<div class="alert alert-success">{{ message }}</div>
|
<div class="alert alert-success">{{ message }}</div>
|
||||||
{% endfor %}
|
{% endfor %}
|
||||||
{% endif %}
|
{% endif %}
|
||||||
|
<!-- Add New PO Button -->
|
||||||
|
<div class="d-flex justify-content-between mb-2">
|
||||||
|
<h3 class="">
|
||||||
|
{{ _("Purchase Orders") |capfirst }}
|
||||||
|
</h2>
|
||||||
|
<a href="{% url 'purchase_order_create' %}"
|
||||||
|
class="btn btn-md btn-phoenix-primary"><i class="fa fa-plus me-2"></i>{{ _("Create New PO") }}</a>
|
||||||
|
</div>
|
||||||
|
{% include "partials/search_box.html" %}
|
||||||
|
|
||||||
<!-- Add New PO Button -->
|
<div class="table-responsive px-1 scrollbar mt-3">
|
||||||
<div class="d-flex justify-content-between align-items-center mb-3">
|
<table class= "table align-items-center table-flush table-hover">
|
||||||
<a href="{% url 'purchase_order_create' %}" class="btn btn-primary">
|
<thead>
|
||||||
<i class="bi bi-plus-lg"></i> Create New PO
|
<tr class="bg-body-highlight">
|
||||||
</a>
|
<th class="sort white-space-nowrap align-middle" scope="col" style="width:15%">PO Number</th>
|
||||||
</div>
|
<th class="sort white-space-nowrap align-middle" scope="col" style="width:40%">Description</th>
|
||||||
<!-- PO Table -->
|
<th class="sort white-space-nowrap align-middle" scope="col" style="width:15%">Status</th>
|
||||||
<div class="table-responsive">
|
<th class="sort white-space-nowrap align-middle" scope="col" style="width:15%">Created At</th>
|
||||||
<table class="table table-striped table-hover align-middle">
|
<th class="sort white-space-nowrap align-middle" scope="col" style="width:15%">Actions</th>
|
||||||
<thead class="table-dark">
|
</tr>
|
||||||
<tr>
|
</thead>
|
||||||
<th scope="col">PO Number</th>
|
<tbody class="list">
|
||||||
<th scope="col">Description</th>
|
|
||||||
<th scope="col">Status</th>
|
|
||||||
<th scope="col">Created At</th>
|
|
||||||
<th scope="col">Actions</th>
|
|
||||||
</tr>
|
|
||||||
</thead>
|
|
||||||
<tbody>
|
|
||||||
{% if purchase_orders %}
|
{% if purchase_orders %}
|
||||||
{% for po in purchase_orders %}
|
{% for po in purchase_orders %}
|
||||||
<tr>
|
|
||||||
<td>{{ po.po_number }}</td>
|
<tr class="hover-actions-trigger btn-reveal-trigger position-static">
|
||||||
<td>{{ po.po_title }}</td>
|
<td class="align-middle product white-space-nowrap">{{ po.po_number }}</td>
|
||||||
<td>
|
<td class="align-middle product white-space-nowrap">{{ po.po_title }}</td>
|
||||||
|
<td class="align-middle product white-space-nowrap">
|
||||||
<span class="">
|
<span class="">
|
||||||
{{ po.po_status|capfirst }}
|
{{ po.po_status|capfirst }}
|
||||||
</span>
|
</span>
|
||||||
</td>
|
</td>
|
||||||
<td>{{ po.created|date:"M d, Y" }}</td>
|
<td class="align-middle product white-space-nowrap">{{ po.created|date:"M d, Y" }}</td>
|
||||||
<td>
|
<td class="align-middle product white-space-nowrap">
|
||||||
<a href="{% url 'purchase_order_detail' po.pk %}" class="btn btn-sm btn-info me-1">
|
<a href="{% url 'purchase_order_detail' po.pk %}"
|
||||||
View
|
class="btn btn-sm btn-phoenix-success">
|
||||||
</a>
|
<i class="fa-regular fa-eye me-1"></i>
|
||||||
{% comment %} <a href="{% url 'purchase_order_detail' po.id %}" class="btn btn-sm btn-info me-1">
|
{% trans "view"|capfirst %}
|
||||||
View
|
|
||||||
</a>
|
</a>
|
||||||
<a href="{% url 'purchase_order_update' po.id %}" class="btn btn-sm btn-warning me-1">
|
|
||||||
Edit
|
|
||||||
</a>
|
|
||||||
<a href="{% url 'purchase_order_delete' po.id %}" class="btn btn-sm btn-danger">
|
|
||||||
Delete
|
|
||||||
</a> {% endcomment %}
|
|
||||||
</td>
|
</td>
|
||||||
</tr>
|
</tr>
|
||||||
{% endfor %}
|
{%endfor%}
|
||||||
{% else %}
|
{% else%}
|
||||||
<tr>
|
<tr>
|
||||||
<td colspan="6" class="text-center">No purchase orders found.</td>
|
<td colspan="6" class="text-center">No purchase orders found.</td>
|
||||||
</tr>
|
</tr>
|
||||||
{% endif %}
|
{% endif %}
|
||||||
</tbody>
|
</tbody>
|
||||||
</table>
|
</table>
|
||||||
|
</div>
|
||||||
|
<div class="d-flex justify-content-end mt-3">
|
||||||
|
<div class="d-flex">
|
||||||
|
{% if is_paginated %}
|
||||||
|
{% include 'partials/pagination.html' %}
|
||||||
|
{% endif %}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
{% include 'modal/delete_modal.html' %}
|
||||||
{% endblock %}
|
{% endblock %}
|
||||||
@ -24,12 +24,13 @@
|
|||||||
{% csrf_token %}
|
{% csrf_token %}
|
||||||
{{ form|crispy }}
|
{{ form|crispy }}
|
||||||
<button type="submit"
|
<button type="submit"
|
||||||
class="btn btn-primary w-100 my-2">{% trans 'Save PO' %}
|
class="btn btn-outline-success w-100 my-2">{% trans 'Save PO' %}
|
||||||
</button>
|
</button>
|
||||||
<a href="{% url 'purchase_order_detail' po_model.uuid %}"
|
<a href="{% url 'purchase_order_detail' po_model.uuid %}"
|
||||||
class="btn btn-dark w-100 my-2">{% trans 'Back to PO Detail' %}</a>
|
class="btn btn-outline-secondary w-100 my-2">{% trans 'Back to PO Detail' %}</a>
|
||||||
<a href="{% url 'purchase_order_list' %}"
|
<a href="{% url 'purchase_order_list' %}"
|
||||||
class="btn btn-info w-100 my-2">{% trans 'PO List' %}</a>
|
class="btn btn-outline-info
|
||||||
|
info w-100 my-2">{% trans 'PO List' %}</a>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</form>
|
</form>
|
||||||
|
|||||||
@ -4,7 +4,7 @@
|
|||||||
<div class="table-container">
|
<div class="table-container">
|
||||||
<table class="table is-fullwidth is-striped is-narrow is-bordered">
|
<table class="table is-fullwidth is-striped is-narrow is-bordered">
|
||||||
<thead>
|
<thead>
|
||||||
<tr class="has-text-centered">
|
<tr class="has-text-centered bg-body-highlight">
|
||||||
<th>{% trans 'Item' %}</th>
|
<th>{% trans 'Item' %}</th>
|
||||||
<th>{% trans 'Unit Cost' %}</th>
|
<th>{% trans 'Unit Cost' %}</th>
|
||||||
<th>{% trans 'PO Qty' %}</th>
|
<th>{% trans 'PO Qty' %}</th>
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user