This commit is contained in:
Marwan Alwali 2024-12-29 13:21:15 +03:00
parent a24e5bc82b
commit b48d0600f4
57 changed files with 48818 additions and 1913 deletions

2
.gitignore vendored
View File

@ -4,7 +4,7 @@
*.pyc
__pycache__
**/*__pycache__
db.sqlite3
db.sqlite
media
car_inventory/settings.py
# Backup files #

View File

@ -1,4 +1,4 @@
# Generated by Django 5.1.4 on 2024-12-26 10:09
# Generated by Django 5.1.4 on 2024-12-26 16:17
from django.db import migrations, models

BIN
db.sqlite Normal file

Binary file not shown.

View File

@ -1,4 +1,4 @@
# Generated by Django 4.2.17 on 2024-12-26 10:12
# Generated by Django 5.1.4 on 2024-12-26 16:17
from django.db import migrations, models

View File

@ -1,7 +1,7 @@
# Generated by Django 4.2.17 on 2024-12-26 10:12
# Generated by Django 5.1.4 on 2024-12-26 16:17
from django.db import migrations, models
import django.db.models.deletion
from django.db import migrations, models
class Migration(migrations.Migration):

View File

@ -43,7 +43,8 @@ class PaymentForm(forms.ModelForm):
class UserForm(forms.ModelForm):
class Meta:
model = Dealer
fields = ['name', 'arabic_name', 'phone_number', 'address','dealer_type']
fields = ['name', 'arabic_name', 'phone_number', 'address','dealer_type']
# Dealer Form
class DealerForm(forms.ModelForm):
class Meta:
@ -265,134 +266,125 @@ class CarSelectionTable(tables.Table):
class WizardForm1(forms.Form):
name = forms.CharField(
label="Name",
widget=forms.TextInput(attrs={
'class': 'form-control',
'placeholder': 'Name',
'required': 'required',
}),
error_messages={
'required': 'Please choose a username.',
}
)
arabic_name = forms.CharField(
label="Arabic Name",
widget=forms.TextInput(attrs={
'class': 'form-control',
'placeholder': 'Arabic Name',
'required': 'required',
}),
error_messages={
'required': 'Please choose an Arabic name.',
}
)
email = forms.EmailField(
label="Email*",
widget=forms.EmailInput(attrs={
'class': 'form-control',
'placeholder': 'Email address',
'pattern': '^([a-zA-Z0-9_.-])+@(([a-zA-Z0-9-])+.)+([a-zA-Z0-9]{2,4})+$',
'required': 'required',
}),
error_messages={
'required': 'You must add an email.',
'required': _('You must add an email.'),
}
)
password = forms.CharField(
widget=forms.PasswordInput(attrs={
'class': 'form-control',
'placeholder': _('Password'),
'required': 'required',
}),
error_messages={
'required': _('This field is required.'),
}
)
confirm_password = forms.CharField(
widget=forms.PasswordInput(attrs={
'class': 'form-control',
'placeholder': _('Confirm Password'),
'required': 'required',
}),
error_messages={
'required': _('This field is required.'),
}
)
terms = forms.BooleanField(
label="I accept the <a href='#!'>terms</a> and <a href='#!'>privacy policy</a>",
widget=forms.CheckboxInput(attrs={
'class': 'form-check-input',
'required': 'required',
'checked': 'checked',
}),
error_messages={
'required': 'You must accept the terms and privacy policy.',
'required': _('You must accept the terms and privacy policy.'),
}
)
class WizardForm2(forms.Form):
# Phone field with SA region validation
name = forms.CharField(
widget=forms.TextInput(attrs={
'class': 'form-control',
'placeholder': _('English Name'),
'required': 'required',
}),
error_messages={
'required': _('Please enter an English Name.'),
}
)
arabic_name = forms.CharField(
widget=forms.TextInput(attrs={
'class': 'form-control',
'placeholder': _('Arabic Name'),
'required': 'required',
}),
error_messages={
'required': _('Please enter an Arabic name.'),
}
)
phone_number = PhoneNumberField(
label="Phone",
min_length=10,
max_length=10,
widget=forms.TextInput(attrs={
'class': 'form-control',
'placeholder': 'Phone',
'placeholder': _('Phone'),
'required': 'required',
}),
region='SA', # Enforces SA region validation
region='SA',
error_messages={
'required': 'This field is required.',
'invalid': 'Phone number must be in the format +966XXXXXXXXX (Saudi Arabia).',
'required': _('This field is required.'),
'invalid': _('Phone number must be in the format 05xxxxxxxx'),
}
)
class WizardForm3(forms.Form):
# CRN field with max length of 10
crn = forms.CharField(
label="CRN",
widget=forms.TextInput(attrs={
'class': 'form-control',
'placeholder': 'CRN',
'placeholder': _("Commercial Registration Number"),
'required': 'required',
'maxlength': '10', # HTML maxlength attribute
'maxlength': '10',
}),
max_length=10, # Django max_length validation
max_length=10,
error_messages={
'required': 'This field is required.',
'max_length': 'CRN must be at most 10 characters long.',
'max_length': 'Commercial Registration Number must be 10 characters.',
}
)
# VRN field with max length of 15
vrn = forms.CharField(
label="VRN",
widget=forms.TextInput(attrs={
'class': 'form-control',
'placeholder': 'VRN',
'placeholder': _("VAT Registration Number"),
'required': 'required',
'maxlength': '15', # HTML maxlength attribute
'maxlength': '15',
}),
max_length=15, # Django max_length validation
max_length=15, #
error_messages={
'required': 'This field is required.',
'max_length': 'VRN must be at most 15 characters long.',
'required': _('This field is required.'),
'max_length': _('VAT Registration Number must be 15 characters.'),
}
)
address = forms.CharField(
label="Address",
widget=forms.Textarea(attrs={
'class': 'form-control',
'rows': '4',
'rows': '3',
'required': 'required',
}),
error_messages={
'required': 'This field is required.',
}
)
class WizardForm3(forms.Form):
password = forms.CharField(
label="Password*",
widget=forms.PasswordInput(attrs={
'class': 'form-control',
'placeholder': 'Password',
'required': 'required',
}),
error_messages={
'required': 'This field is required.',
}
)
confirm_password = forms.CharField(
label="Confirm Password*",
widget=forms.PasswordInput(attrs={
'class': 'form-control',
'placeholder': 'Confirm Password',
'required': 'required',
}),
error_messages={
'required': 'This field is required.',
'required': _('This field is required.'),
}
)

View File

@ -1,11 +1,11 @@
# Generated by Django 4.2.17 on 2024-12-26 10:12
# Generated by Django 5.1.4 on 2024-12-26 16:17
from decimal import Decimal
from django.conf import settings
from django.db import migrations, models
import django.db.models.deletion
import inventory.mixins
import phonenumber_field.modelfields
from decimal import Decimal
from django.conf import settings
from django.db import migrations, models
class Migration(migrations.Migration):
@ -13,8 +13,8 @@ class Migration(migrations.Migration):
initial = True
dependencies = [
migrations.swappable_dependency(settings.AUTH_USER_MODEL),
('django_ledger', '0017_alter_accountmodel_unique_together_and_more'),
migrations.swappable_dependency(settings.AUTH_USER_MODEL),
]
operations = [
@ -66,90 +66,6 @@ class Migration(migrations.Migration):
},
bases=(models.Model, inventory.mixins.LocalizedNameMixin),
),
migrations.CreateModel(
name='CarModel',
fields=[
('id_car_model', models.AutoField(primary_key=True, serialize=False)),
('name', models.CharField(max_length=255)),
('arabic_name', models.CharField(max_length=255)),
('id_car_make', models.ForeignKey(db_column='id_car_make', on_delete=django.db.models.deletion.DO_NOTHING, to='inventory.carmake')),
],
options={
'verbose_name': 'Model',
},
bases=(models.Model, inventory.mixins.LocalizedNameMixin),
),
migrations.CreateModel(
name='CarSerie',
fields=[
('id_car_serie', models.AutoField(primary_key=True, serialize=False)),
('name', models.CharField(max_length=255)),
('arabic_name', models.CharField(max_length=255)),
('year_begin', models.IntegerField(blank=True, null=True)),
('year_end', models.IntegerField(blank=True, null=True)),
('id_car_model', models.ForeignKey(db_column='id_car_model', on_delete=django.db.models.deletion.DO_NOTHING, to='inventory.carmodel')),
],
options={
'verbose_name': 'Series',
},
bases=(models.Model, inventory.mixins.LocalizedNameMixin),
),
migrations.CreateModel(
name='CarSpecification',
fields=[
('id_car_specification', models.AutoField(primary_key=True, serialize=False)),
('name', models.CharField(max_length=255)),
('arabic_name', models.CharField(max_length=255)),
('id_parent', models.ForeignKey(blank=True, db_column='id_parent', null=True, on_delete=django.db.models.deletion.DO_NOTHING, to='inventory.carspecification')),
],
options={
'verbose_name': 'Specification',
},
bases=(models.Model, inventory.mixins.LocalizedNameMixin),
),
migrations.CreateModel(
name='Customer',
fields=[
('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
('first_name', models.CharField(max_length=50, verbose_name='First Name')),
('middle_name', models.CharField(blank=True, max_length=50, null=True, verbose_name='Middle Name')),
('last_name', models.CharField(max_length=50, verbose_name='Last Name')),
('email', models.EmailField(max_length=254, unique=True, verbose_name='Email')),
('national_id', models.CharField(max_length=10, unique=True, verbose_name='National ID')),
('phone_number', phonenumber_field.modelfields.PhoneNumberField(max_length=128, region='SA', unique=True, verbose_name='Phone Number')),
('address', models.CharField(blank=True, max_length=200, null=True, verbose_name='Address')),
('created', models.DateTimeField(auto_now_add=True, verbose_name='Created')),
],
options={
'verbose_name': 'Customer',
'verbose_name_plural': 'Customers',
},
),
migrations.CreateModel(
name='Dealer',
fields=[
('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
('crn', models.CharField(blank=True, max_length=10, null=True, verbose_name='Commercial Registration Number')),
('vrn', models.CharField(blank=True, max_length=15, null=True, verbose_name='VAT Registration Number')),
('arabic_name', models.CharField(max_length=255, verbose_name='Arabic Name')),
('name', models.CharField(max_length=255, verbose_name='English Name')),
('phone_number', phonenumber_field.modelfields.PhoneNumberField(max_length=128, region='SA', verbose_name='Phone Number')),
('address', models.CharField(blank=True, max_length=200, null=True, verbose_name='Address')),
('logo', models.ImageField(blank=True, null=True, upload_to='logos/users', verbose_name='Logo')),
('joined_at', models.DateTimeField(auto_now_add=True, verbose_name='Joined At')),
('email', models.EmailField(max_length=254, unique=True, verbose_name='Email')),
('dealer_type', models.CharField(choices=[('Owner', 'Owner'), ('Inventory', 'Inventory'), ('Accountent', 'Accountent'), ('sales', 'Sales')], default='Owner', max_length=255, verbose_name='Dealer Type')),
('entity', models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.SET_NULL, to='django_ledger.entitymodel')),
('parent_dealer', models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.SET_NULL, related_name='sub_dealers', to='inventory.dealer', verbose_name='Parent Dealer')),
('user', models.OneToOneField(on_delete=django.db.models.deletion.CASCADE, related_name='dealer', to=settings.AUTH_USER_MODEL)),
],
options={
'verbose_name': 'Dealer',
'verbose_name_plural': 'Dealers',
'permissions': [('change_dealer_type', 'Can change dealer type')],
},
bases=(models.Model, inventory.mixins.LocalizedNameMixin),
),
migrations.CreateModel(
name='ExteriorColors',
fields=[
@ -178,25 +94,6 @@ class Migration(migrations.Migration):
},
bases=(models.Model, inventory.mixins.LocalizedNameMixin),
),
migrations.CreateModel(
name='Organization',
fields=[
('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
('name', models.CharField(max_length=255, verbose_name='Name')),
('arabic_name', models.CharField(max_length=255, verbose_name='Arabic Name')),
('crn', models.CharField(max_length=15, verbose_name='Commercial Registration Number')),
('vrn', models.CharField(max_length=15, verbose_name='VAT Registration Number')),
('phone_number', phonenumber_field.modelfields.PhoneNumberField(max_length=128, region='SA', verbose_name='Phone Number')),
('address', models.CharField(blank=True, max_length=200, null=True, verbose_name='Address')),
('logo', models.ImageField(blank=True, null=True, upload_to='logos', verbose_name='Logo')),
('dealer', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, related_name='organizations', to='inventory.dealer')),
],
options={
'verbose_name': 'Organization',
'verbose_name_plural': 'Organizations',
},
bases=(models.Model, inventory.mixins.LocalizedNameMixin),
),
migrations.CreateModel(
name='Payment',
fields=[
@ -211,30 +108,6 @@ class Migration(migrations.Migration):
'verbose_name_plural': 'payments',
},
),
migrations.CreateModel(
name='SaleQuotation',
fields=[
('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
('quotation_number', models.CharField(max_length=10, unique=True)),
('amount', models.DecimalField(decimal_places=2, default=Decimal('0.00'), max_digits=10, verbose_name='Amount')),
('remarks', models.TextField(blank=True, null=True, verbose_name='Remarks')),
('is_approved', models.BooleanField(default=False)),
('status', models.CharField(choices=[('Draft', 'Draft'), ('Approved', 'Approved'), ('In Review', 'In Review'), ('Paid', 'Paid')], default='Draft', max_length=10, verbose_name='Status')),
('created_at', models.DateTimeField(auto_now_add=True, verbose_name='Created At')),
('updated_at', models.DateTimeField(auto_now=True, verbose_name='Updated At')),
('posted', models.BooleanField(default=False)),
('payment_id', models.CharField(blank=True, max_length=255, null=True, verbose_name='Payment ID')),
('is_paid', models.BooleanField(default=False)),
('date_draft', models.DateTimeField(blank=True, null=True, verbose_name='Draft Date')),
('date_in_review', models.DateTimeField(blank=True, null=True, verbose_name='In Review Date')),
('date_approved', models.DateTimeField(blank=True, null=True, verbose_name='Approved Date')),
('date_paid', models.DateTimeField(blank=True, null=True, verbose_name='Paid Date')),
('date_void', models.DateTimeField(blank=True, null=True, verbose_name='Void Date')),
('date_canceled', models.DateTimeField(blank=True, null=True, verbose_name='Canceled Date')),
('customer', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, related_name='quotations', to='inventory.customer', verbose_name='Customer')),
('dealer', models.ForeignKey(null=True, on_delete=django.db.models.deletion.CASCADE, related_name='sales', to='inventory.dealer')),
],
),
migrations.CreateModel(
name='Subscription',
fields=[
@ -257,110 +130,91 @@ class Migration(migrations.Migration):
],
),
migrations.CreateModel(
name='Vendor',
name='CarFinance',
fields=[
('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
('crn', models.CharField(max_length=10, unique=True, verbose_name='Commercial Registration Number')),
('vrn', models.CharField(max_length=15, unique=True, verbose_name='VAT Registration Number')),
('arabic_name', models.CharField(max_length=255, verbose_name='Arabic Name')),
('name', models.CharField(max_length=255, verbose_name='English Name')),
('contact_person', models.CharField(max_length=100, verbose_name='Contact Person')),
('phone_number', phonenumber_field.modelfields.PhoneNumberField(max_length=128, region='SA', verbose_name='Phone Number')),
('address', models.CharField(blank=True, max_length=200, null=True, verbose_name='Address')),
('logo', models.ImageField(blank=True, null=True, upload_to='logos/vendors', verbose_name='Logo')),
('dealer', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, related_name='vendors', to='inventory.dealer')),
('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': 'Vendor',
'verbose_name_plural': 'Vendors',
'verbose_name': 'Car Financial Details',
'verbose_name_plural': 'Car Financial Details',
},
),
migrations.AddField(
model_name='car',
name='id_car_make',
field=models.ForeignKey(blank=True, db_column='id_car_make', null=True, on_delete=django.db.models.deletion.DO_NOTHING, to='inventory.carmake', verbose_name='Make'),
),
migrations.CreateModel(
name='CarModel',
fields=[
('id_car_model', models.AutoField(primary_key=True, serialize=False)),
('name', models.CharField(max_length=255)),
('arabic_name', models.CharField(max_length=255)),
('id_car_make', models.ForeignKey(db_column='id_car_make', on_delete=django.db.models.deletion.DO_NOTHING, to='inventory.carmake')),
],
options={
'verbose_name': 'Model',
},
bases=(models.Model, inventory.mixins.LocalizedNameMixin),
),
migrations.CreateModel(
name='SubscriptionUser',
fields=[
('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
('subscription', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to='inventory.subscription')),
('user', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to=settings.AUTH_USER_MODEL)),
],
),
migrations.AddField(
model_name='subscription',
name='users',
field=models.ManyToManyField(through='inventory.SubscriptionUser', to=settings.AUTH_USER_MODEL),
model_name='car',
name='id_car_model',
field=models.ForeignKey(blank=True, db_column='id_car_model', null=True, on_delete=django.db.models.deletion.DO_NOTHING, to='inventory.carmodel', verbose_name='Model'),
),
migrations.CreateModel(
name='SalesOrder',
name='CarRegistration',
fields=[
('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
('created_at', models.DateTimeField(auto_now_add=True, verbose_name='Created At')),
('total_amount', models.DecimalField(decimal_places=2, max_digits=14, verbose_name='Total Amount')),
('quotation', models.OneToOneField(on_delete=django.db.models.deletion.CASCADE, related_name='sales_order', to='inventory.salequotation', verbose_name='Quotation')),
],
),
migrations.CreateModel(
name='SaleQuotationCar',
fields=[
('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
('quantity', models.PositiveIntegerField(default=1, verbose_name='Quantity')),
('car', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to='inventory.car', verbose_name='Car')),
('quotation', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, related_name='quotation_cars', to='inventory.salequotation', verbose_name='Quotation')),
],
),
migrations.CreateModel(
name='Representative',
fields=[
('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
('name', models.CharField(max_length=255, verbose_name='Name')),
('arabic_name', models.CharField(max_length=255, verbose_name='Arabic Name')),
('id_number', models.CharField(max_length=10, verbose_name='ID Number')),
('phone_number', phonenumber_field.modelfields.PhoneNumberField(max_length=128, region='SA', verbose_name='Phone Number')),
('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')),
('plate_number', models.IntegerField(verbose_name='Plate Number')),
('text1', models.CharField(max_length=1, verbose_name='Text 1')),
('text2', models.CharField(max_length=1, verbose_name='Text 2')),
('text3', models.CharField(max_length=1, verbose_name='Text 3')),
('registration_date', models.DateTimeField(verbose_name='Registration Date')),
('car', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, related_name='registrations', to='inventory.car', verbose_name='Car')),
],
options={
'verbose_name': 'Representative',
'verbose_name_plural': 'Representatives',
'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(max_length=255)),
('arabic_name', models.CharField(max_length=255)),
('year_begin', models.IntegerField(blank=True, null=True)),
('year_end', models.IntegerField(blank=True, null=True)),
('id_car_model', models.ForeignKey(db_column='id_car_model', on_delete=django.db.models.deletion.DO_NOTHING, to='inventory.carmodel')),
],
options={
'verbose_name': 'Series',
},
bases=(models.Model, inventory.mixins.LocalizedNameMixin),
),
migrations.CreateModel(
name='Refund',
fields=[
('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
('amount', models.DecimalField(decimal_places=2, max_digits=10, verbose_name='amount')),
('reason', models.TextField(blank=True, verbose_name='reason')),
('refund_date', models.DateField(auto_now_add=True, verbose_name='refund date')),
('payment', models.OneToOneField(on_delete=django.db.models.deletion.CASCADE, related_name='refund', to='inventory.payment')),
],
options={
'verbose_name': 'refund',
'verbose_name_plural': 'refunds',
},
),
migrations.AddField(
model_name='payment',
name='quotation',
field=models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, related_name='payments', to='inventory.salequotation'),
),
migrations.AddField(
model_name='customer',
name='dealer',
field=models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, related_name='customers', to='inventory.dealer'),
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='CustomCard',
name='CarSpecification',
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')),
('id_car_specification', models.AutoField(primary_key=True, serialize=False)),
('name', models.CharField(max_length=255)),
('arabic_name', models.CharField(max_length=255)),
('id_parent', models.ForeignKey(blank=True, db_column='id_parent', null=True, on_delete=django.db.models.deletion.DO_NOTHING, to='inventory.carspecification')),
],
options={
'verbose_name': 'Custom Card',
'verbose_name_plural': 'Custom Cards',
'verbose_name': 'Specification',
},
bases=(models.Model, inventory.mixins.LocalizedNameMixin),
),
migrations.CreateModel(
name='CarTrim',
@ -391,20 +245,66 @@ class Migration(migrations.Migration):
'verbose_name': 'Specification Value',
},
),
migrations.AddField(
model_name='car',
name='id_car_trim',
field=models.ForeignKey(blank=True, db_column='id_car_trim', null=True, on_delete=django.db.models.deletion.DO_NOTHING, to='inventory.cartrim', verbose_name='Trim'),
),
migrations.CreateModel(
name='CarRegistration',
name='CustomCard',
fields=[
('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
('plate_number', models.IntegerField(verbose_name='Plate Number')),
('text1', models.CharField(max_length=1, verbose_name='Text 1')),
('text2', models.CharField(max_length=1, verbose_name='Text 2')),
('text3', models.CharField(max_length=1, verbose_name='Text 3')),
('registration_date', models.DateTimeField(verbose_name='Registration Date')),
('car', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, related_name='registrations', to='inventory.car', verbose_name='Car')),
('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': 'Registration',
'verbose_name_plural': 'Registrations',
'verbose_name': 'Custom Card',
'verbose_name_plural': 'Custom Cards',
},
),
migrations.CreateModel(
name='Dealer',
fields=[
('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
('crn', models.CharField(blank=True, max_length=10, null=True, verbose_name='Commercial Registration Number')),
('vrn', models.CharField(blank=True, max_length=15, null=True, verbose_name='VAT Registration Number')),
('arabic_name', models.CharField(max_length=255, verbose_name='Arabic Name')),
('name', models.CharField(max_length=255, verbose_name='English Name')),
('phone_number', phonenumber_field.modelfields.PhoneNumberField(max_length=128, region='SA', unique=True, verbose_name='Phone Number')),
('address', models.CharField(blank=True, max_length=200, null=True, verbose_name='Address')),
('logo', models.ImageField(blank=True, null=True, upload_to='logos/users', verbose_name='Logo')),
('joined_at', models.DateTimeField(auto_now_add=True, verbose_name='Joined At')),
('email', models.EmailField(max_length=100, unique=True, verbose_name='Email')),
('dealer_type', models.CharField(choices=[('Owner', 'Owner'), ('Inventory', 'Inventory'), ('Accountent', 'Accountent'), ('sales', 'Sales')], default='Owner', max_length=255, verbose_name='Dealer Type')),
('entity', models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.SET_NULL, to='django_ledger.entitymodel')),
('parent_dealer', models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.SET_NULL, related_name='sub_dealers', to='inventory.dealer', verbose_name='Parent Dealer')),
('user', models.OneToOneField(on_delete=django.db.models.deletion.CASCADE, related_name='dealer', to=settings.AUTH_USER_MODEL)),
],
options={
'verbose_name': 'Dealer',
'verbose_name_plural': 'Dealers',
'permissions': [('change_dealer_type', 'Can change dealer type')],
},
bases=(models.Model, inventory.mixins.LocalizedNameMixin),
),
migrations.CreateModel(
name='Customer',
fields=[
('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
('first_name', models.CharField(max_length=50, verbose_name='First Name')),
('middle_name', models.CharField(blank=True, max_length=50, null=True, verbose_name='Middle Name')),
('last_name', models.CharField(max_length=50, verbose_name='Last Name')),
('email', models.EmailField(max_length=254, unique=True, verbose_name='Email')),
('national_id', models.CharField(max_length=10, unique=True, verbose_name='National ID')),
('phone_number', phonenumber_field.modelfields.PhoneNumberField(max_length=128, region='SA', unique=True, verbose_name='Phone Number')),
('address', models.CharField(blank=True, max_length=200, null=True, verbose_name='Address')),
('created', models.DateTimeField(auto_now_add=True, verbose_name='Created')),
('dealer', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, related_name='customers', to='inventory.dealer')),
],
options={
'verbose_name': 'Customer',
'verbose_name_plural': 'Customers',
},
),
migrations.CreateModel(
@ -423,56 +323,152 @@ class Migration(migrations.Migration):
'verbose_name_plural': 'Car Locations',
},
),
migrations.CreateModel(
name='CarFinance',
fields=[
('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
('cost_price', models.DecimalField(decimal_places=2, max_digits=14, verbose_name='Cost Price')),
('selling_price', models.DecimalField(decimal_places=2, max_digits=14, verbose_name='Selling Price')),
('discount_amount', models.DecimalField(decimal_places=2, default=Decimal('0.00'), max_digits=14, verbose_name='Discount Amount')),
('additional_services', models.ManyToManyField(blank=True, related_name='additional_finances', to='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.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='car',
name='id_car_make',
field=models.ForeignKey(blank=True, db_column='id_car_make', null=True, on_delete=django.db.models.deletion.DO_NOTHING, to='inventory.carmake', verbose_name='Make'),
model_name='additionalservices',
name='dealer',
field=models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to='inventory.dealer', verbose_name='Dealer'),
),
migrations.CreateModel(
name='Organization',
fields=[
('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
('name', models.CharField(max_length=255, verbose_name='Name')),
('arabic_name', models.CharField(max_length=255, verbose_name='Arabic Name')),
('crn', models.CharField(max_length=15, verbose_name='Commercial Registration Number')),
('vrn', models.CharField(max_length=15, verbose_name='VAT Registration Number')),
('phone_number', phonenumber_field.modelfields.PhoneNumberField(max_length=128, region='SA', verbose_name='Phone Number')),
('address', models.CharField(blank=True, max_length=200, null=True, verbose_name='Address')),
('logo', models.ImageField(blank=True, null=True, upload_to='logos', verbose_name='Logo')),
('dealer', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, related_name='organizations', to='inventory.dealer')),
],
options={
'verbose_name': 'Organization',
'verbose_name_plural': 'Organizations',
},
bases=(models.Model, inventory.mixins.LocalizedNameMixin),
),
migrations.CreateModel(
name='Refund',
fields=[
('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
('amount', models.DecimalField(decimal_places=2, max_digits=10, verbose_name='amount')),
('reason', models.TextField(blank=True, verbose_name='reason')),
('refund_date', models.DateField(auto_now_add=True, verbose_name='refund date')),
('payment', models.OneToOneField(on_delete=django.db.models.deletion.CASCADE, related_name='refund', to='inventory.payment')),
],
options={
'verbose_name': 'refund',
'verbose_name_plural': 'refunds',
},
),
migrations.CreateModel(
name='Representative',
fields=[
('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
('name', models.CharField(max_length=255, verbose_name='Name')),
('arabic_name', models.CharField(max_length=255, verbose_name='Arabic Name')),
('id_number', models.CharField(max_length=10, verbose_name='ID Number')),
('phone_number', phonenumber_field.modelfields.PhoneNumberField(max_length=128, region='SA', verbose_name='Phone Number')),
('address', models.CharField(blank=True, max_length=200, null=True, verbose_name='Address')),
('dealer', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, related_name='representatives', to='inventory.dealer')),
('organization', models.ManyToManyField(related_name='representatives', to='inventory.organization')),
],
options={
'verbose_name': 'Representative',
'verbose_name_plural': 'Representatives',
},
bases=(models.Model, inventory.mixins.LocalizedNameMixin),
),
migrations.CreateModel(
name='SaleQuotation',
fields=[
('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
('quotation_number', models.CharField(max_length=10, unique=True)),
('amount', models.DecimalField(decimal_places=2, default=Decimal('0.00'), max_digits=10, verbose_name='Amount')),
('remarks', models.TextField(blank=True, null=True, verbose_name='Remarks')),
('is_approved', models.BooleanField(default=False)),
('status', models.CharField(choices=[('Draft', 'Draft'), ('Approved', 'Approved'), ('In Review', 'In Review'), ('Paid', 'Paid')], default='Draft', max_length=10, verbose_name='Status')),
('created_at', models.DateTimeField(auto_now_add=True, verbose_name='Created At')),
('updated_at', models.DateTimeField(auto_now=True, verbose_name='Updated At')),
('posted', models.BooleanField(default=False)),
('payment_id', models.CharField(blank=True, max_length=255, null=True, verbose_name='Payment ID')),
('is_paid', models.BooleanField(default=False)),
('date_draft', models.DateTimeField(blank=True, null=True, verbose_name='Draft Date')),
('date_in_review', models.DateTimeField(blank=True, null=True, verbose_name='In Review Date')),
('date_approved', models.DateTimeField(blank=True, null=True, verbose_name='Approved Date')),
('date_paid', models.DateTimeField(blank=True, null=True, verbose_name='Paid Date')),
('date_void', models.DateTimeField(blank=True, null=True, verbose_name='Void Date')),
('date_canceled', models.DateTimeField(blank=True, null=True, verbose_name='Canceled Date')),
('customer', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, related_name='quotations', to='inventory.customer', verbose_name='Customer')),
('dealer', models.ForeignKey(null=True, on_delete=django.db.models.deletion.CASCADE, related_name='sales', to='inventory.dealer')),
],
),
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'),
model_name='payment',
name='quotation',
field=models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, related_name='payments', to='inventory.salequotation'),
),
migrations.CreateModel(
name='SaleQuotationCar',
fields=[
('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
('quantity', models.PositiveIntegerField(default=1, verbose_name='Quantity')),
('car', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to='inventory.car', verbose_name='Car')),
('quotation', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, related_name='quotation_cars', to='inventory.salequotation', verbose_name='Quotation')),
],
),
migrations.CreateModel(
name='SalesOrder',
fields=[
('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
('created_at', models.DateTimeField(auto_now_add=True, verbose_name='Created At')),
('total_amount', models.DecimalField(decimal_places=2, max_digits=14, verbose_name='Total Amount')),
('quotation', models.OneToOneField(on_delete=django.db.models.deletion.CASCADE, related_name='sales_order', to='inventory.salequotation', verbose_name='Quotation')),
],
),
migrations.CreateModel(
name='SubscriptionUser',
fields=[
('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
('subscription', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to='inventory.subscription')),
('user', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to=settings.AUTH_USER_MODEL)),
],
),
migrations.AddField(
model_name='car',
name='id_car_serie',
field=models.ForeignKey(blank=True, db_column='id_car_serie', null=True, on_delete=django.db.models.deletion.DO_NOTHING, to='inventory.carserie', verbose_name='Series'),
model_name='subscription',
name='users',
field=models.ManyToManyField(through='inventory.SubscriptionUser', to=settings.AUTH_USER_MODEL),
),
migrations.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='Vendor',
fields=[
('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
('crn', models.CharField(max_length=10, unique=True, verbose_name='Commercial Registration Number')),
('vrn', models.CharField(max_length=15, unique=True, verbose_name='VAT Registration Number')),
('arabic_name', models.CharField(max_length=255, verbose_name='Arabic Name')),
('name', models.CharField(max_length=255, verbose_name='English Name')),
('contact_person', models.CharField(max_length=100, verbose_name='Contact Person')),
('phone_number', phonenumber_field.modelfields.PhoneNumberField(max_length=128, region='SA', verbose_name='Phone Number')),
('address', models.CharField(blank=True, max_length=200, null=True, verbose_name='Address')),
('logo', models.ImageField(blank=True, null=True, upload_to='logos/vendors', verbose_name='Logo')),
('dealer', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, related_name='vendors', to='inventory.dealer')),
],
options={
'verbose_name': 'Vendor',
'verbose_name_plural': 'Vendors',
},
bases=(models.Model, inventory.mixins.LocalizedNameMixin),
),
migrations.AddField(
model_name='car',
name='vendor',
field=models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.DO_NOTHING, related_name='cars', to='inventory.vendor', verbose_name='Vendor'),
),
migrations.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='CarReservation',
fields=[

View File

@ -1,21 +0,0 @@
# Generated by Django 4.2.17 on 2024-12-26 14:19
from django.conf import settings
from django.db import migrations, models
import django.db.models.deletion
class Migration(migrations.Migration):
dependencies = [
migrations.swappable_dependency(settings.AUTH_USER_MODEL),
('inventory', '0001_initial'),
]
operations = [
migrations.AlterField(
model_name='dealer',
name='user',
field=models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, related_name='dealer', to=settings.AUTH_USER_MODEL),
),
]

View File

@ -0,0 +1,17 @@
# Generated by Django 5.1.4 on 2024-12-26 17:33
from django.db import migrations
class Migration(migrations.Migration):
dependencies = [
('inventory', '0001_initial'),
]
operations = [
migrations.RemoveField(
model_name='dealer',
name='email',
),
]

View File

@ -1,18 +0,0 @@
# Generated by Django 4.2.17 on 2024-12-26 15:44
from django.db import migrations, models
class Migration(migrations.Migration):
dependencies = [
('inventory', '0002_alter_dealer_user'),
]
operations = [
migrations.AlterField(
model_name='dealer',
name='email',
field=models.EmailField(max_length=100, null=True, unique=True, verbose_name='Email'),
),
]

View File

@ -0,0 +1,19 @@
# Generated by Django 5.1.4 on 2024-12-26 18:27
import phonenumber_field.modelfields
from django.db import migrations
class Migration(migrations.Migration):
dependencies = [
('inventory', '0002_remove_dealer_email'),
]
operations = [
migrations.AlterField(
model_name='dealer',
name='phone_number',
field=phonenumber_field.modelfields.PhoneNumberField(max_length=128, region='SA', verbose_name='Phone Number'),
),
]

View File

@ -1,25 +0,0 @@
# Generated by Django 4.2.17 on 2024-12-26 15:51
from django.db import migrations, models
import phonenumber_field.modelfields
class Migration(migrations.Migration):
dependencies = [
('inventory', '0003_alter_dealer_email'),
]
operations = [
migrations.AlterField(
model_name='dealer',
name='email',
field=models.EmailField(default='a@a.com', max_length=100, unique=True, verbose_name='Email'),
preserve_default=False,
),
migrations.AlterField(
model_name='dealer',
name='phone_number',
field=phonenumber_field.modelfields.PhoneNumberField(max_length=128, region='SA', unique=True, verbose_name='Phone Number'),
),
]

View File

@ -0,0 +1,24 @@
# Generated by Django 5.1.4 on 2024-12-26 23:03
import inventory.models
from django.db import migrations
class Migration(migrations.Migration):
dependencies = [
('inventory', '0003_alter_dealer_phone_number'),
]
operations = [
migrations.AlterModelManagers(
name='dealer',
managers=[
('objects', inventory.models.DealerUserManager()),
],
),
migrations.RemoveField(
model_name='dealer',
name='entity',
),
]

View File

@ -1,21 +0,0 @@
# Generated by Django 4.2.17 on 2024-12-26 15:57
from django.conf import settings
from django.db import migrations, models
import django.db.models.deletion
class Migration(migrations.Migration):
dependencies = [
migrations.swappable_dependency(settings.AUTH_USER_MODEL),
('inventory', '0004_alter_dealer_email_alter_dealer_phone_number'),
]
operations = [
migrations.AlterField(
model_name='dealer',
name='user',
field=models.OneToOneField(on_delete=django.db.models.deletion.CASCADE, related_name='dealer', to=settings.AUTH_USER_MODEL),
),
]

View File

@ -0,0 +1,20 @@
# Generated by Django 5.1.4 on 2024-12-26 23:57
import django.db.models.deletion
from django.db import migrations, models
class Migration(migrations.Migration):
dependencies = [
('django_ledger', '0017_alter_accountmodel_unique_together_and_more'),
('inventory', '0004_alter_dealer_managers_remove_dealer_entity'),
]
operations = [
migrations.AddField(
model_name='dealer',
name='entity',
field=models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.SET_NULL, to='django_ledger.entitymodel'),
),
]

View File

@ -33,11 +33,11 @@ class LocalizedNameMixin:
return getattr(self, 'name', None)
class AddDealerInstanceMixin:
def form_valid(self, form):
if form.is_valid():
form.instance.dealer = self.request.user.dealer.get_root_dealer
form.save()
return super().form_valid(form)
else:
return form.errors
# class AddDealerInstanceMixin:
# def form_valid(self, form):
# if form.is_valid():
# form.instance.dealer = self.request.user.dealer.get_root_dealer
# form.save()
# return super().form_valid(form)
# else:
# return form.errors

View File

@ -3,7 +3,7 @@ from uuid import uuid4
from django.conf import settings
from django.db import models, transaction
from django.db.models import Sum, F, Count
from django.contrib.auth.models import User
from django.contrib.auth.models import User, UserManager
from django.db.models.signals import pre_save, post_save
from django.dispatch import receiver
from django.utils.translation import gettext_lazy as _
@ -29,6 +29,15 @@ from django.db.models import FloatField
from .mixins import LocalizedNameMixin
from django_ledger.models import EntityModel
class DealerUserManager(UserManager):
def create_user_with_dealer(self, email, password, dealer_name, arabic_name, crn, vrn, address, **extra_fields):
user = self.create_user(email=email, password=password, **extra_fields)
Dealer.objects.create(user=user, name=dealer_name, )
return user
UNIT_CHOICES = (
("Kg", _("Kg")),
("L", _("L")),
@ -147,10 +156,10 @@ class CarStockTypeChoices(models.TextChoices):
class DEALER_TYPES(models.TextChoices):
Owner = "Owner", _("Owner")
Inventory = "Inventory", _("Inventory")
Accountent = "Accountent", _("Accountent")
Sales = "sales", _("Sales")
OWNER = "Owner", _("Owner")
INVENTORY = "Inventory", _("Inventory")
ACCOUNTANT = "Accountent", _("Accountent")
SALES = "sales", _("Sales")
class AdditionalServices(models.Model, LocalizedNameMixin):
@ -482,12 +491,15 @@ class Subscription(models.Model):
is_active = models.BooleanField(default=True)
def __str__(self):
return self.plan
class SubscriptionUser(models.Model):
subscription = models.ForeignKey(Subscription, on_delete=models.CASCADE)
user = models.ForeignKey(User, on_delete=models.CASCADE)
def __str__(self):
return f"{self.subscription} - {self.user}"
class SubscriptionPlan(models.Model):
name = models.CharField(max_length=255)
description = models.TextField()
@ -495,41 +507,44 @@ class SubscriptionPlan(models.Model):
max_users = models.IntegerField() # maximum number of users per account
def __str__(self):
return f"{self.name} - {self.price}"
# Dealer Model
class Dealer(models.Model, LocalizedNameMixin):
user = models.OneToOneField(User, on_delete=models.CASCADE, related_name="dealer")
crn = models.CharField(
max_length=10, verbose_name=_("Commercial Registration Number"),null=True,blank=True
)
vrn = models.CharField(max_length=15, verbose_name=_("VAT Registration Number"),null=True,blank=True)
crn = models.CharField(max_length=10,
verbose_name=_("Commercial Registration Number")
,null=True
,blank=True)
vrn = models.CharField(max_length=15,
verbose_name=_("VAT Registration Number"),
null=True,
blank=True)
arabic_name = models.CharField(max_length=255, verbose_name=_("Arabic Name"))
name = models.CharField(max_length=255, verbose_name=_("English Name"))
phone_number = PhoneNumberField(region="SA", verbose_name=_("Phone Number"),unique=True)
address = models.CharField(
max_length=200, blank=True, null=True, verbose_name=_("Address")
)
logo = models.ImageField(
upload_to="logos/users", blank=True, null=True, verbose_name=_("Logo")
)
phone_number = PhoneNumberField(region="SA", verbose_name=_("Phone Number"))
address = models.CharField(max_length=200,
blank=True,
null=True,
verbose_name=_("Address"))
logo = models.ImageField(upload_to="logos/users",
blank=True,
null=True,
verbose_name=_("Logo"))
entity = models.ForeignKey(EntityModel, on_delete=models.SET_NULL, null=True, blank=True)
joined_at = models.DateTimeField(auto_now_add=True, verbose_name=_("Joined At"))
email = models.EmailField(verbose_name="Email", unique=True, max_length=100)
parent_dealer = models.ForeignKey(
"self",
on_delete=models.SET_NULL,
blank=True,
null=True,
verbose_name=_("Parent Dealer"),
related_name="sub_dealers",
)
dealer_type = models.CharField(
max_length=255,
choices=DEALER_TYPES.choices,
verbose_name=_("Dealer Type"),
default=DEALER_TYPES.Owner,
)
parent_dealer = models.ForeignKey( "self",
on_delete=models.SET_NULL,
blank=True,
null=True,
verbose_name=_("Parent Dealer"),
related_name="sub_dealers",)
dealer_type = models.CharField(max_length=255,
choices=DEALER_TYPES.choices,
verbose_name=_("Dealer Type"),
default=DEALER_TYPES.OWNER,)
objects = DealerUserManager()
@property
def get_active_plan(self):
try:
@ -549,9 +564,6 @@ class Dealer(models.Model, LocalizedNameMixin):
return subscription_plan
return None
class Meta:
verbose_name = _("Dealer")
verbose_name_plural = _("Dealers")
@ -564,21 +576,47 @@ class Dealer(models.Model, LocalizedNameMixin):
@property
def get_sub_dealers(self):
if self.dealer_type == "Owner":
if self.dealer_type == "OWNER":
return self.sub_dealers.all()
return None
@property
def is_parent(self):
return self.dealer_type == "Owner"
return self.dealer_type == "OWNER"
@property
def get_root_dealer(self):
return self.parent_dealer if self.parent_dealer else self
@receiver(post_save, sender=User)
def create_dealer(instance, created, *args, **kwargs):
if created:
Dealer.objects.create(user=instance)
# @receiver(post_save, sender=User)
# def create_dealer(instance, created, *args, **kwargs):
# if created:
# Dealer.objects.create(user=instance)
# class STAFF_TYPES(models.TextChoices):
# # Owner = "Owner", _("Owner")
# MANAGER = "manager", _("Manager")
# INVENTORY = "inventory", _("Inventory")
# ACCOUNTANT = "accountant", _("Accountant")
# SALES = "sales", _("Sales")
# RECEPTIONIST = "receptionist", _("Receptionist")
# TECHNICIAN = "technician", _("Technician")
# DRIVER = "driver", _("Driver")
#
#
# class Staff(models.Model, LocalizedNameMixin):
# user = models.OneToOneField(User, on_delete=models.CASCADE, related_name="staff")
# dealer = models.ForeignKey(Dealer, on_delete=models.SET_NULL, null=True, blank=True)
# name = models.CharField(max_length=255, verbose_name=_("Name"))
# arabic_name = models.CharField(max_length=255, verbose_name=_("Arabic Name"))
# staff_type = models.CharField(choices=STAFF_TYPES.choices, max_length=255, verbose_name=_("Staff Type"))
# created_at = models.DateTimeField(auto_now_add=True, verbose_name=_("Created At"))
# updated_at = models.DateTimeField(auto_now=True, verbose_name=_("Updated At"))
#
# class Meta:
# verbose_name = _("Staff")
# verbose_name_plural = _("Staff")
# permissions = []
# Vendor Model
@ -611,9 +649,9 @@ class Vendor(models.Model, LocalizedNameMixin):
# Customer Model
class Customer(models.Model):
dealer = models.ForeignKey(
Dealer, on_delete=models.CASCADE, related_name="customers"
)
dealer = models.ForeignKey(Dealer,
on_delete=models.CASCADE,
related_name="customers")
first_name = models.CharField(max_length=50, verbose_name=_("First Name"))
middle_name = models.CharField(
max_length=50, blank=True, null=True, verbose_name=_("Middle Name")
@ -644,8 +682,6 @@ class Customer(models.Model):
return f"{self.first_name} {self.middle_name} {self.last_name}"
class Organization(models.Model, LocalizedNameMixin):
dealer = models.ForeignKey(Dealer, on_delete=models.CASCADE, related_name='organizations')
name = models.CharField(max_length=255, verbose_name=_("Name"))

View File

@ -80,6 +80,9 @@ def elm(vin):
def get_ledger_data(request):
entity = EntityModel.objects.filter(name="Marwan2").first()
data = entity.get_items_all()
print(data)
data = {}
entity = EntityModel.objects.filter(name=request.user.dealer.name).first()
data['bills'] = entity.get_bills()
data['invoices'] = entity.get_invoices()
data['income'] = entity.get_income_statement()
return data

View File

@ -1,9 +1,5 @@
from random import randint
from django.db.models.signals import post_save, post_delete, pre_delete, pre_save
from django.dispatch import receiver
from django.utils import timezone
from django_ledger.models import EntityModel
from django.utils.translation import gettext_lazy as _
from django.contrib.auth import get_user_model
from django_ledger.io import roles
@ -24,14 +20,14 @@ User = get_user_model()
# user = instance.user
# if user:
# user.delete()
#
# @receiver(post_save, sender=User)
# def create_dealer(instance, created, *args, **kwargs):
# if created:
# if created:
# if not instance.dealer:
#
# models.Dealer.objects.create(user=instance,name=instance.username,email=instance.email)
#
# @receiver(post_save, sender=models.Dealer)
# def create_user_account(sender, instance, created, **kwargs):
# if created:
@ -45,6 +41,7 @@ User = get_user_model()
# instance.user = user
# instance.save()
@receiver(post_save, sender=models.Car)
def create_car_location(sender, instance, created, **kwargs):
"""
@ -83,114 +80,108 @@ def update_car_status_on_reservation_delete(sender, instance, **kwargs):
# Create Entity
# @receiver(post_save, sender=models.Dealer)
# def create_ledger_entity(sender, instance, created, **kwargs):
# if created:
# root_dealer = instance.get_root_dealer
# if not root_dealer.entity:
# entity_name = f"{root_dealer.name}-{root_dealer.joined_at.date()}"
# entity = EntityModel.create_entity(
# name=entity_name,
# admin=root_dealer.user,
# use_accrual_method=False,
# fy_start_month=1,
# )
@receiver(post_save, sender=models.Dealer)
def create_ledger_entity(sender, instance, created, **kwargs):
if created:
root_dealer = instance.get_root_dealer
if not root_dealer.entity:
entity_name = f"{root_dealer.name}-{root_dealer.joined_at.date()}"
entity = EntityModel.create_entity(
name=entity_name,
admin=root_dealer.user,
use_accrual_method=False,
fy_start_month=1,
)
# if entity:
# instance.entity = entity
# instance.save()
# coa = entity.create_chart_of_accounts(
# assign_as_default=True, commit=True, coa_name=_(f"{entity_name}-COA")
# )
# if coa:
# # entity.populate_default_coa(activate_accounts=True, coa_model=coa)
# print(f"Ledger entity created for Dealer: {instance.name}")
if entity:
instance.entity = entity
instance.save()
coa = entity.create_chart_of_accounts(
assign_as_default=True, commit=True, coa_name=_(f"{entity_name}-COA")
)
if coa:
# entity.populate_default_coa(activate_accounts=True, coa_model=coa)
print(f"Ledger entity created for Dealer: {instance.name}")
# # Create Cash Account
# entity.create_account(
# coa_model=coa,
# code="1010",
# role=roles.ASSET_CA_CASH,
# name=_("Cash"),
# balance_type="debit",
# active=True,
# )
# Create Cash Account
entity.create_account(
coa_model=coa,
code="1010",
role=roles.ASSET_CA_CASH,
name=_("Cash"),
balance_type="debit",
active=True,
)
# # Create Accounts Receivable Account
# entity.create_account(
# coa_model=coa,
# code="1020",
# role=roles.ASSET_CA_RECEIVABLES,
# name=_("Accounts Receivable"),
# balance_type="debit",
# active=True,
# )
# Create Accounts Receivable Account
entity.create_account(
coa_model=coa,
code="1020",
role=roles.ASSET_CA_RECEIVABLES,
name=_("Accounts Receivable"),
balance_type="debit",
active=True,
)
# # Create Inventory Account
# entity.create_account(
# coa_model=coa,
# code="1030",
# role=roles.ASSET_CA_INVENTORY,
# name=_("Inventory"),
# balance_type="debit",
# active=True,
# )
# Create Inventory Account
entity.create_account(
coa_model=coa,
code="1030",
role=roles.ASSET_CA_INVENTORY,
name=_("Inventory"),
balance_type="debit",
active=True,
)
# # Create Accounts Payable Account
# entity.create_account(
# coa_model=coa,
# code="2010",
# role=roles.LIABILITY_CL_ACC_PAYABLE,
# name=_("Accounts Payable"),
# balance_type="credit",
# active=True,
# )
# Create Accounts Payable Account
entity.create_account(
coa_model=coa,
code="2010",
role=roles.LIABILITY_CL_ACC_PAYABLE,
name=_("Accounts Payable"),
balance_type="credit",
active=True,
)
# # Create Sales Revenue Account
# entity.create_account(
# coa_model=coa,
# code="4010",
# role=roles.INCOME_OPERATIONAL,
# name=_("Sales Revenue"),
# balance_type="credit",
# active=True,
# )
# Create Sales Revenue Account
entity.create_account(
coa_model=coa,
code="4010",
role=roles.INCOME_OPERATIONAL,
name=_("Sales Revenue"),
balance_type="credit",
active=True,
)
# # Create Cost of Goods Sold Account
# entity.create_account(
# coa_model=coa,
# code="5010",
# role=roles.COGS,
# name=_("Cost of Goods Sold"),
# balance_type="debit",
# active=True,
# )
# Create Cost of Goods Sold Account
entity.create_account(
coa_model=coa,
code="5010",
role=roles.COGS,
name=_("Cost of Goods Sold"),
balance_type="debit",
active=True,
)
# # Create Rent Expense Account
# entity.create_account(
# coa_model=coa,
# code="6010",
# role=roles.EXPENSE_OPERATIONAL,
# name=_("Rent Expense"),
# balance_type="debit",
# active=True,
# )
# # Create Utilities Expense Account
# entity.create_account(
# coa_model=coa,
# code="6020",
# role=roles.EXPENSE_OPERATIONAL,
# name=_("Utilities Expense"),
# balance_type="debit",
# active=True,
# )
# uom_name = _("Unit")
# unit_abbr = _("U")
#
# entity.create_uom(uom_name, unit_abbr)
# Create Rent Expense Account
entity.create_account(
coa_model=coa,
code="6010",
role=roles.EXPENSE_OPERATIONAL,
name=_("Rent Expense"),
balance_type="debit",
active=True,
)
# Create Utilities Expense Account
entity.create_account(
coa_model=coa,
code="6020",
role=roles.EXPENSE_OPERATIONAL,
name=_("Utilities Expense"),
balance_type="debit",
active=True,
)
# Create Vendor

View File

@ -3,13 +3,10 @@ from django import template
register = template.Library()
@register.filter
@register.filter(name='percentage')
def percentage(value):
try:
value = float(value) * 100
return f'{value:.0f}%'
except (ValueError, TypeError):
return value
if value is not None:
return '{0:,.2f}%'.format(value * 100)
@register.filter

View File

@ -14,7 +14,7 @@ urlpatterns = [
path('login/', allauth_views.LoginView.as_view(template_name='account/login.html'), name='account_login'),
path('logout/', allauth_views.LogoutView.as_view(template_name='account/logout.html'), name='account_logout'),
# path('signup/', allauth_views.SignupView.as_view(template_name='account/signup.html'), name='account_signup'),
path('signup/', views.SignupView, name='account_signup'),
path('signup/', views.dealer_signup, name='account_signup'),
path('password/change/',
allauth_views.PasswordChangeView.as_view(template_name='account/password_change.html'),
name='account_change_password'),
@ -27,11 +27,9 @@ urlpatterns = [
path('login/code/', allauth_views.RequestLoginCodeView.as_view(template_name='account/request_login_code.html')),
# Dealer URLs
path('dealers/', views.DealerListView.as_view(), name='dealer_list'),
path('dealers/<int:pk>/', views.DealerDetailView.as_view(), name='dealer_detail'),
path('dealers/create/', views.DealerCreateView.as_view(), name='dealer_create'),
path('dealers/<int:pk>/update/', views.DealerUpdateView.as_view(), name='dealer_update'),
path('dealers/<int:pk>/delete/', views.DealerDeleteView.as_view(), name='dealer_delete'),
# path('dealers/<int:pk>/delete/', views.DealerDeleteView.as_view(), name='dealer_delete'),
# Customer URLs
path('customers/', views.CustomerListView.as_view(), name='customer_list'),

View File

@ -2,15 +2,8 @@ from django_ledger.models import EntityModel, InvoiceModel
import logging
import json
import datetime
from decimal import Decimal
from django_ledger.models import TransactionModel, AccountModel,JournalEntryModel
from django.shortcuts import HttpResponse
from django.template.loader import render_to_string
# from weasyprint import HTML
# from weasyprint.fonts import FontConfiguration
from django.views.decorators.csrf import csrf_exempt
from vin import VIN
from django.contrib.auth.mixins import LoginRequiredMixin
from django.contrib.auth.decorators import login_required
from django.http import JsonResponse
@ -29,14 +22,11 @@ from django.views.generic import (
from django.utils import timezone, translation
from django.conf import settings
from urllib.parse import urlparse, urlunparse
from django.forms import ChoiceField, ModelForm, RadioSelect
from django.urls import reverse, reverse_lazy
from django.contrib import messages
from django.db.models import Sum, F, Count
from django.db import transaction
from inventory.mixins import AddDealerInstanceMixin
from .services import elm, decodevin, get_make, get_model, normalize_name
from .services import (
elm,
decodevin,
@ -46,12 +36,9 @@ from .services import (
get_ledger_data,
)
from . import models, forms
from django_tables2.export.views import ExportMixin
from django.contrib.auth.mixins import PermissionRequiredMixin
from django.contrib.auth.decorators import user_passes_test
from django.contrib.messages.views import SuccessMessageMixin
from django.contrib.auth.models import Group
from django.contrib.auth import get_user_model
from .utils import get_calculations
from django.contrib.auth.models import User
@ -94,64 +81,62 @@ def switch_language(request):
logger.warning(f"Invalid language code: {language}")
return redirect("/")
def SignupView(request):
def dealer_signup(request, *args, **kwargs):
if request.method == "POST":
data = json.loads(request.body)
wf1 = data.get("wizardValidationForm1")
wf1 = data.get("wizardValidationForm1")
wf2 = data.get("wizardValidationForm2")
wf3 = data.get("wizardValidationForm3")
name = wf1.get("name")
arabic_name = wf1.get("arabic_name")
email = wf1.get("email")
email = wf1.get("email")
phone = wf2.get("phone_number")
crn = wf2.get("crn")
vrn = wf2.get("vrn")
address = wf2.get("address")
password = wf3.get("password")
password_confirm = wf3.get("confirm_password")
if password != password_confirm:
return JsonResponse({"error": "Passwords do not match."}, status=400)
try:
with transaction.atomic():
user = User.objects.create(username=name, email=email)
user = User.objects.create(email=email, password=password)
user.set_password(password)
user.save()
models.Dealer.objects.create_or_update(
user=user,
name=name,
arabic_name=arabic_name,
crn=crn,
vrn=vrn,
email=email,
phone_number=phone,
address=address,
dealer_type="Owner",
)
print("User created successfully.")
models.Dealer.objects.create(user=user,
name=name,
arabic_name=arabic_name,
crn=crn,
vrn=vrn,
phone_number=phone,
address=address,
dealer_type="OWNER",)
return JsonResponse({"message": "User created successfully."}, status=200)
except Exception as e:
print("Error creating user:", e)
return JsonResponse({"error": str(e)}, status=400)
form1 = forms.WizardForm1()
form2 = forms.WizardForm2()
form3 = forms.WizardForm3()
return render(request, "account/signup-wizard.html",{"form1": form1, "form2": form2, "form3": form3})
class HomeView(LoginRequiredMixin, TemplateView):
template_name = "dashboards/accounting.html"
def dispatch(self, request, *args, **kwargs):
if (
not any(hasattr(request.user, attr) for attr in ["dealer", "subdealer"])
or not request.user.is_authenticated
not any(hasattr(request.user, attr) for attr in ["dealer", "subdealer"])
or not request.user.is_authenticated
):
messages.error(request, _("You are not associated with any dealer."))
# messages.error(request, _("You are not associated with any dealer."))
return redirect("welcome")
return super().dispatch(request, *args, **kwargs)
def get_context_data(self, **kwargs):
context = super().get_context_data(**kwargs)
@ -534,7 +519,7 @@ class CarLocationCreateView(CreateView):
def form_valid(self, form):
form.instance.car = get_object_or_404(models.Car, pk=self.kwargs["car_pk"])
form.instance.owner = self.request.user.dealer
form.instance.OWNER = self.request.user.dealer
form.save()
messages.success(self.request, "Car saved successfully.")
return super().form_valid(form)
@ -621,26 +606,12 @@ def manage_reservation(request, reservation_id):
)
class DealerListView(LoginRequiredMixin, ListView):
model = models.Dealer
template_name = "dealer_list.html"
context_object_name = "dealers"
class DealerDetailView(LoginRequiredMixin, DetailView):
model = models.Dealer
template_name = "dealers/dealer_detail.html"
context_object_name = "dealer"
class DealerCreateView(LoginRequiredMixin, SuccessMessageMixin, CreateView):
model = models.Dealer
form_class = forms.DealerForm
template_name = "dealer_form.html"
success_url = reverse_lazy("dealer_list")
success_message = _("Dealer created successfully.")
class DealerUpdateView(LoginRequiredMixin, SuccessMessageMixin, UpdateView):
model = models.Dealer
form_class = forms.DealerForm
@ -658,19 +629,12 @@ class DealerUpdateView(LoginRequiredMixin, SuccessMessageMixin, UpdateView):
return form
def get_form_class(self):
if self.request.user.dealer.dealer_type == "Owner":
if self.request.user.dealer.dealer_type == "OWNER":
return forms.DealerForm
else:
return forms.UserForm
class DealerDeleteView(LoginRequiredMixin, SuccessMessageMixin, DeleteView):
model = models.Dealer
template_name = "dealer_confirm_delete.html"
success_url = reverse_lazy("dealer_list")
success_message = _("Dealer deleted successfully.")
class CustomerListView(LoginRequiredMixin, PermissionRequiredMixin, ListView):
model = models.Customer
home_label = _("customers")
@ -710,7 +674,6 @@ class CustomerCreateView(
LoginRequiredMixin,
PermissionRequiredMixin,
SuccessMessageMixin,
AddDealerInstanceMixin,
CreateView,
):
model = models.Customer
@ -725,7 +688,6 @@ class CustomerUpdateView(
LoginRequiredMixin,
PermissionRequiredMixin,
SuccessMessageMixin,
AddDealerInstanceMixin,
UpdateView,
):
model = models.Customer
@ -762,7 +724,6 @@ class VendorCreateView(
LoginRequiredMixin,
PermissionRequiredMixin,
SuccessMessageMixin,
AddDealerInstanceMixin,
CreateView,
):
model = models.Vendor
@ -777,7 +738,6 @@ class VendorUpdateView(
LoginRequiredMixin,
PermissionRequiredMixin,
SuccessMessageMixin,
AddDealerInstanceMixin,
UpdateView,
):
model = models.Vendor
@ -1168,7 +1128,6 @@ class UserCreateView(
LoginRequiredMixin,
PermissionRequiredMixin,
SuccessMessageMixin,
AddDealerInstanceMixin,
CreateView,
):
model = models.Dealer
@ -1181,7 +1140,7 @@ class UserCreateView(
def get_form(self, form_class=None):
form = super().get_form(form_class)
form.fields["dealer_type"].choices = [
t for t in form.fields["dealer_type"].choices if t[0] != "Owner"
t for t in form.fields["dealer_type"].choices if t[0] != "OWNER"
]
return form
@ -1211,7 +1170,6 @@ class UserUpdateView(
LoginRequiredMixin,
PermissionRequiredMixin,
SuccessMessageMixin,
AddDealerInstanceMixin,
UpdateView,
):
model = models.Dealer
@ -1398,7 +1356,8 @@ def download_quotation_pdf(request, quotation_id):
return response
except models.SaleQuotation.DoesNotExist:
return HttpResponse("Quotation not found", status=404)
@login_required
def invoice_detail(request,pk):
quotation = get_object_or_404(models.SaleQuotation, pk=pk)
@ -1410,6 +1369,8 @@ def invoice_detail(request,pk):
invoice = invoice_model.filter(customer=customer,date_draft=quotation.date_draft).first()
return redirect('quotation_detail', pk=pk)
@login_required
def payment_invoice(request,pk):
quotation = get_object_or_404(models.SaleQuotation, pk=pk)
@ -1451,7 +1412,7 @@ def payment_create(request, pk):
insatnce = form.save()
dealer = request.user.dealer.get_root_dealer
entity = dealer.entity
entity = dealer.entity
customer = entity.get_customers().filter(customer_name=quotation.customer.get_full_name).first()
coa_qs, coa_map = entity.get_all_coa_accounts()
cash_account = coa_qs.first().get_coa_accounts().filter(name="Cash")

Binary file not shown.

File diff suppressed because it is too large Load Diff

BIN
static/.DS_Store vendored

Binary file not shown.

1
static/css/sweetalert2.min.css vendored Normal file

File diff suppressed because one or more lines are too long

View File

@ -22988,7 +22988,7 @@ input:-webkit-autofill {
box-shadow: 0 0 0 30px var(--phoenix-emphasis-bg) inset;
}
input::-webkit-contacts-auto-fill-button {
input:-webkit-contacts-auto-fill-button {
background-color: var(--phoenix-body-color);
}

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.0 MiB

View File

@ -112,7 +112,7 @@
return config;
}));
//# sourceMappingURL=config.js.map
var phoenixIsRTL = window.config.config.phoenixIsRTL;
if (phoenixIsRTL) {
@ -127,3 +127,4 @@
linkRTL.setAttribute('disabled', true);
userLinkRTL.setAttribute('disabled', true);
}
}));

File diff suppressed because one or more lines are too long

6
static/js/sweetalert2.all.min.js vendored Normal file

File diff suppressed because one or more lines are too long

View File

@ -754,7 +754,7 @@
color:
getItemFromStore('phoenixTheme') === 'dark'
? getColor('primary')
: getColor('primary-light')
: getColor('primary')
},
data: profitData[0]
},
@ -772,7 +772,7 @@
color:
getItemFromStore('phoenixTheme') === 'dark'
? getColor('success')
: getColor('success-light')
: getColor('success')
},
data: revenueData[0]
},
@ -788,7 +788,7 @@
color:
getItemFromStore('phoenixTheme') === 'dark'
? getColor('info')
: getColor('info-light')
: getColor('info')
},
data: expansesData[0]
}

File diff suppressed because one or more lines are too long

View File

@ -6,16 +6,16 @@
{% block title %}{{ _("Sign In") }}{% endblock title %}
{% block content %}
<div class="container">
<div class="row flex-center min-vh-100 py-5">
<div class="col-sm-10 col-md-8 col-lg-5 col-xl-5 col-xxl-3"><a class="d-flex flex-center text-decoration-none mb-4" href="{% url 'landing_page' %}">
<div class="d-flex align-items-center fw-bolder fs-3 d-inline-block"><img src="{% static 'images/logos/logo.png' %}" alt="{% trans 'Haikal' %}" width="58" />
</div>
</a>
<div class="text-center">
<div class="container">
<div class="row flex-center min-vh-50">
<div class="col-sm-10 col-md-8 col-lg-5 col-xl-5 col-xxl-3">
<a class="d-flex flex-center text-decoration-none mb-4" href="{% url 'landing_page' %}">
<div class="d-flex align-items-center fw-bolder fs-3 d-inline-block"><img src="{% static 'images/logos/logo.png' %}" alt="{% trans 'home' %}" width="58" /></div>
</a>
<div class="text-center">
<h3 class="text-body-highlight">{{ _("Sign In") }}</h3>
{% if SOCIALACCOUNT_ENABLED %}
<p class="text-body-tertiary">Get access to your account</p>
<p class="text-body-tertiary">Login to your account</p>
</div>
{% include "socialaccount/snippets/login.html" with page_layout="entrance" %}
@ -34,7 +34,6 @@
</div>
</div>
</div>
{% endblock content %}
{% block extra_body %}

View File

@ -1,38 +1,34 @@
{% extends "base.html" %}
{% load crispy_forms_filters %}
{% load i18n %}
{% load i18n static crispy_forms_filters %}
{% block title %}{{ _("Change Password") }}{% endblock title %}
{% block content %}
<div class="container my-5">
<div class="row justify-content-center">
<div class="col-md-6">
<!-- Page Header -->
<div class="text-center mb-4">
<h1 class="fw-bold">{{ _("Change Password") }}</h1>
<p class="text-muted">{{ _("Ensure your account is using a strong, unique password.") }}</p>
</div>
<!-- Change Password Form -->
<form method="post" action="{% url 'account_change_password' %}" class="needs-validation" novalidate>
{% csrf_token %}
{{ redirect_field }}
{{ form|crispy }}
<div class="d-grid gap-2 mt-3">
<button type="submit" class="btn btn-primary btn-lg">
<i class="bi bi-key"></i> {{ _("Change Password") }}
</button>
</div>
</form>
<!-- Forgot Password Link -->
<div class="text-center mt-3">
<a href="{% url 'account_reset_password' %}" class="text-decoration-none">
{{ _("Forgot Password?") }}
</a>
</div>
<div class="container">
<div class="row min-vh-100">
<div class="col-sm-10 col-md-8 col-lg-5 col-xl-5 col-xxl-3">
<a class="d-flex flex-center text-decoration-none mb-4" href="{% url 'landing_page' %}">
<div class="d-flex align-items-center fw-bolder fs-3 d-inline-block"><img src="{% static 'images/logos/logo.png' %}" alt="{% trans 'home' %}" width="58" /></div>
</a>
<div class="text-center">
<h1 class="fw-bold">{{ _("Change Password") }}</h1>
<p class="text-muted">{{ _("Ensure your account is using a strong, unique password.") }}</p>
</div>
<form method="post" action="{% url 'account_change_password' %}" class="needs-validation" novalidate>
{% csrf_token %}
{{ redirect_field }}
{{ form|crispy }}
<div class="text-start">
<a href="{% url 'account_reset_password' %}" class="fs-sm-9">
{{ _("Forgot Password?") }}
</a>
</div>
<div class="d-grid gap-2 mt-3">
<button type="submit" class="btn btn-primary w-100 mb-3"><i class="bi bi-key"></i> {{ _("Change Password") }}</button>
</div>
</form>
</div>
</div>
</div>
</div>
{% endblock %}

View File

@ -5,12 +5,12 @@
{% block content %}
<div class="container">
<div class="row flex-center min-vh-100 py-5">
<div class="row min-vh-100">
<div class="col-sm-10 col-md-8 col-lg-5 col-xl-5 col-xxl-3"><a class="d-flex flex-center text-decoration-none mb-4" href="{% url 'landing_page' %}">
<div class="d-flex align-items-center fw-bolder fs-3 d-inline-block"><img src="{% static 'images/logos/logo.png' %}" alt="{% trans 'home' %}" width="58" />
</div>
</a>
<div class="text-center mb-6">
<div class="text-center">
<h4 class="text-body-highlight">{{ _("Password Reset") }}</h4>
<p class="text-body-tertiary">{{ _("Type your new password") }}</p>
<form method="post" action="{% url 'account_reset_password' %}" class="needs-validation" novalidate>
@ -28,9 +28,9 @@
</div>
<button type="submit" class="btn btn-primary w-100 mb-3">{{ _("Reset My Password") }}</button>
</form>
<p class="text-muted">
<small class="text-muted mt-4">
{{ _("Please contact us if you have any trouble resetting your password.") }}
</p>
</small>
</div>
</div>
</div>

View File

@ -1,23 +1,36 @@
{% extends "base.html" %}
{% load crispy_forms_filters %}
{% load i18n %}
{% load i18n static %}
{% block content %}
<div class="card theme-wizard mb-5" data-theme-wizard="data-theme-wizard">
<div class="card-header bg-body-highlight pt-3 pb-2 border-bottom-0">
<div class="container">
<div class="row d-flex-center min-vh-50">
<div class="col-12 "><a class="d-flex flex-center text-decoration-none mb-4" href="{% url 'landing_page' %}">
<div class="d-flex align-items-center fw-bolder fs-3 d-inline-block">
<img class="d-dark-none" src="{% static 'images/logos/logo.png' %}" alt="{% trans 'home' %}" width="58" />
<img class="d-light-none" src="{% static 'images/logos/logo-d.png' %}" alt="{% trans 'home' %}" width="58" />
</div>
</a>
<div class="text-center">
<h3 class="text-body-highlight">{% trans 'Sign Up' %}</h3>
<p class="text-body-tertiary">{% trans 'Create your account today' %}</p>
</div>
<div class="text-start">
<div class="card theme-wizard" data-theme-wizard="data-theme-wizard">
<div class="card-header pt-3 pb-2 border-bottom-0">
<ul class="nav justify-content-between nav-wizard nav-wizard-success" role="tablist">
<li class="nav-item" role="presentation"><a class="nav-link active fw-semibold" href="#bootstrap-wizard-validation-tab1" data-bs-toggle="tab" data-wizard-step="1" aria-selected="true" role="tab">
<div class="text-center d-inline-block"><span class="nav-item-circle-parent"><span class="nav-item-circle"><svg class="svg-inline--fa fa-lock" aria-hidden="true" focusable="false" data-prefix="fas" data-icon="lock" role="img" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 448 512" data-fa-i2svg=""><path fill="currentColor" d="M144 144v48H304V144c0-44.2-35.8-80-80-80s-80 35.8-80 80zM80 192V144C80 64.5 144.5 0 224 0s144 64.5 144 144v48h16c35.3 0 64 28.7 64 64V448c0 35.3-28.7 64-64 64H64c-35.3 0-64-28.7-64-64V256c0-35.3 28.7-64 64-64H80z"></path></svg><!-- <span class="fas fa-lock"></span> Font Awesome fontawesome.com --></span></span><span class="d-none d-md-block mt-1 fs-9">Account</span></div>
<div class="text-center d-inline-block"><span class="nav-item-circle-parent"><span class="nav-item-circle"><svg class="svg-inline--fa fa-lock" aria-hidden="true" focusable="false" data-prefix="fas" data-icon="lock" role="img" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 448 512" data-fa-i2svg=""><path fill="currentColor" d="M144 144v48H304V144c0-44.2-35.8-80-80-80s-80 35.8-80 80zM80 192V144C80 64.5 144.5 0 224 0s144 64.5 144 144v48h16c35.3 0 64 28.7 64 64V448c0 35.3-28.7 64-64 64H64c-35.3 0-64-28.7-64-64V256c0-35.3 28.7-64 64-64H80z"></path></svg></span></span><span class="d-none d-md-block mt-1 fs-9">{% trans 'Access' %}</span></div>
</a></li>
<li class="nav-item" role="presentation"><a class="nav-link fw-semibold" href="#bootstrap-wizard-validation-tab2" data-bs-toggle="tab" data-wizard-step="2" aria-selected="false" tabindex="-1" role="tab">
<div class="text-center d-inline-block"><span class="nav-item-circle-parent"><span class="nav-item-circle"><svg class="svg-inline--fa fa-user" aria-hidden="true" focusable="false" data-prefix="fas" data-icon="user" role="img" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 448 512" data-fa-i2svg=""><path fill="currentColor" d="M224 256A128 128 0 1 0 224 0a128 128 0 1 0 0 256zm-45.7 48C79.8 304 0 383.8 0 482.3C0 498.7 13.3 512 29.7 512H418.3c16.4 0 29.7-13.3 29.7-29.7C448 383.8 368.2 304 269.7 304H178.3z"></path></svg><!-- <span class="fas fa-user"></span> Font Awesome fontawesome.com --></span></span><span class="d-none d-md-block mt-1 fs-9">Personal</span></div>
<div class="text-center d-inline-block"><span class="nav-item-circle-parent"><span class="nav-item-circle"><svg class="svg-inline--fa fa-user" aria-hidden="true" focusable="false" data-prefix="fas" data-icon="user" role="img" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 448 512" data-fa-i2svg=""><path fill="currentColor" d="M224 256A128 128 0 1 0 224 0a128 128 0 1 0 0 256zm-45.7 48C79.8 304 0 383.8 0 482.3C0 498.7 13.3 512 29.7 512H418.3c16.4 0 29.7-13.3 29.7-29.7C448 383.8 368.2 304 269.7 304H178.3z"></path></svg></span></span><span class="d-none d-md-block mt-1 fs-9">{% trans 'Account' %}</span></div>
</a></li>
<li class="nav-item" role="presentation"><a class="nav-link fw-semibold" href="#bootstrap-wizard-validation-tab3" data-bs-toggle="tab" data-wizard-step="3" aria-selected="false" tabindex="-1" role="tab">
<div class="text-center d-inline-block"><span class="nav-item-circle-parent"><span class="nav-item-circle"><svg class="svg-inline--fa fa-file-lines" aria-hidden="true" focusable="false" data-prefix="fas" data-icon="file-lines" role="img" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 384 512" data-fa-i2svg=""><path fill="currentColor" d="M64 0C28.7 0 0 28.7 0 64V448c0 35.3 28.7 64 64 64H320c35.3 0 64-28.7 64-64V160H256c-17.7 0-32-14.3-32-32V0H64zM256 0V128H384L256 0zM112 256H272c8.8 0 16 7.2 16 16s-7.2 16-16 16H112c-8.8 0-16-7.2-16-16s7.2-16 16-16zm0 64H272c8.8 0 16 7.2 16 16s-7.2 16-16 16H112c-8.8 0-16-7.2-16-16s7.2-16 16-16zm0 64H272c8.8 0 16 7.2 16 16s-7.2 16-16 16H112c-8.8 0-16-7.2-16-16s7.2-16 16-16z"></path></svg><!-- <span class="fas fa-file-alt"></span> Font Awesome fontawesome.com --></span></span><span class="d-none d-md-block mt-1 fs-9">Password</span></div>
<div class="text-center d-inline-block"><span class="nav-item-circle-parent"><span class="nav-item-circle"><svg class="svg-inline--fa fa-file-lines" aria-hidden="true" focusable="false" data-prefix="fas" data-icon="file-lines" role="img" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 384 512" data-fa-i2svg=""><path fill="currentColor" d="M64 0C28.7 0 0 28.7 0 64V448c0 35.3 28.7 64 64 64H320c35.3 0 64-28.7 64-64V160H256c-17.7 0-32-14.3-32-32V0H64zM256 0V128H384L256 0zM112 256H272c8.8 0 16 7.2 16 16s-7.2 16-16 16H112c-8.8 0-16-7.2-16-16s7.2-16 16-16zm0 64H272c8.8 0 16 7.2 16 16s-7.2 16-16 16H112c-8.8 0-16-7.2-16-16s7.2-16 16-16zm0 64H272c8.8 0 16 7.2 16 16s-7.2 16-16 16H112c-8.8 0-16-7.2-16-16s7.2-16 16-16z"></path></svg></span></span><span class="d-none d-md-block mt-1 fs-9">{% trans 'Extra' %}</span></div>
</a></li>
<li class="nav-item" role="presentation"><a class="nav-link fw-semibold" href="#bootstrap-wizard-validation-tab4" data-bs-toggle="tab" data-wizard-step="4" aria-selected="false" tabindex="-1" role="tab">
<div class="text-center d-inline-block"><span class="nav-item-circle-parent"><span class="nav-item-circle"><svg class="svg-inline--fa fa-check" aria-hidden="true" focusable="false" data-prefix="fas" data-icon="check" role="img" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 448 512" data-fa-i2svg=""><path fill="currentColor" d="M438.6 105.4c12.5 12.5 12.5 32.8 0 45.3l-256 256c-12.5 12.5-32.8 12.5-45.3 0l-128-128c-12.5-12.5-12.5-32.8 0-45.3s32.8-12.5 45.3 0L160 338.7 393.4 105.4c12.5-12.5 32.8-12.5 45.3 0z"></path></svg><!-- <span class="fas fa-check"></span> Font Awesome fontawesome.com --></span></span><span class="d-none d-md-block mt-1 fs-9">Done</span></div>
<div class="text-center d-inline-block"><span class="nav-item-circle-parent"><span class="nav-item-circle"><svg class="svg-inline--fa fa-check" aria-hidden="true" focusable="false" data-prefix="fas" data-icon="check" role="img" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 448 512" data-fa-i2svg=""><path fill="currentColor" d="M438.6 105.4c12.5 12.5 12.5 32.8 0 45.3l-256 256c-12.5 12.5-32.8 12.5-45.3 0l-128-128c-12.5-12.5-12.5-32.8 0-45.3s32.8-12.5 45.3 0L160 338.7 393.4 105.4c12.5-12.5 32.8-12.5 45.3 0z"></path></svg></span></span><span class="d-none d-md-block mt-1 fs-9">{% trans 'Done' %}</span></div>
</a></li>
</ul>
</div>
@ -41,12 +54,12 @@
<div class="tab-pane" role="tabpanel" aria-labelledby="bootstrap-wizard-validation-tab4" id="bootstrap-wizard-validation-tab4">
<div class="row flex-center pb-8 pt-4 gx-3 gy-4">
<div class="col-12 col-sm-auto">
<div class="text-center text-sm-start"><img class="d-dark-none" src="../../assets/img/spot-illustrations/38.webp" alt="" width="220"><img class="d-light-none" src="../../assets/img/spot-illustrations/dark_38.webp" alt="" width="220"></div>
<div class="text-center text-sm-start"><img class="d-dark-none" src="{% static 'images/spot-illustrations/38.webp' %}" alt="" width="220"><img class="d-light-none" src="{% static 'images/spot-illustrations/dark_38.webp' %}" alt="" width="220"></div>
</div>
<div class="col-12 col-sm-auto">
<div class="text-center text-sm-start">
<h5 class="mb-3">You are all set!</h5>
<p class="text-body-emphasis fs-9">Now you can access your account<br>anytime anywhere</p><button class="btn btn-primary px-6" id='submit_btn'>Submit</button>
<h5 class="mb-3">{% trans 'You are all set!' %}</h5>
<p class="text-body-emphasis fs-9">{% trans 'Now you can access your account' %}<br>{% trans 'anytime' %} {% trans 'anywhere' %}</p><button class="btn btn-primary px-6" id='submit_btn'>{% trans 'Submit' %}</button>
</div>
</div>
</div>
@ -55,11 +68,104 @@
</div>
<div class="card-footer border-top-0" data-wizard-footer="data-wizard-footer">
<div class="d-flex pager wizard list-inline mb-0">
<button class="d-none btn btn-link ps-0" type="button" data-wizard-prev-btn="data-wizard-prev-btn"><svg class="svg-inline--fa fa-chevron-left me-1" data-fa-transform="shrink-3" aria-hidden="true" focusable="false" data-prefix="fas" data-icon="chevron-left" role="img" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 320 512" data-fa-i2svg="" style="transform-origin: 0.3125em 0.5em;"><g transform="translate(160 256)"><g transform="translate(0, 0) scale(0.8125, 0.8125) rotate(0 0 0)"><path fill="currentColor" d="M9.4 233.4c-12.5 12.5-12.5 32.8 0 45.3l192 192c12.5 12.5 32.8 12.5 45.3 0s12.5-32.8 0-45.3L77.3 256 246.6 86.6c12.5-12.5 12.5-32.8 0-45.3s-32.8-12.5-45.3 0l-192 192z" transform="translate(-160 -256)"></path></g></g></svg><!-- <span class="fas fa-chevron-left me-1" data-fa-transform="shrink-3"></span> Font Awesome fontawesome.com -->Previous</button>
<button class="d-none btn btn-link ps-0" type="button" data-wizard-prev-btn="data-wizard-prev-btn">{% trans 'Previous' %}</button>
<div class="flex-1 text-end">
<button class="btn btn-primary px-6 px-sm-6" type="submit" data-wizard-next-btn="data-wizard-next-btn">Next<svg class="svg-inline--fa fa-chevron-right ms-1" data-fa-transform="shrink-3" aria-hidden="true" focusable="false" data-prefix="fas" data-icon="chevron-right" role="img" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 320 512" data-fa-i2svg="" style="transform-origin: 0.3125em 0.5em;"><g transform="translate(160 256)"><g transform="translate(0, 0) scale(0.8125, 0.8125) rotate(0 0 0)"><path fill="currentColor" d="M310.6 233.4c12.5 12.5 12.5 32.8 0 45.3l-192 192c-12.5 12.5-32.8 12.5-45.3 0s-12.5-32.8 0-45.3L242.7 256 73.4 86.6c-12.5-12.5-12.5-32.8 0-45.3s32.8-12.5 45.3 0l192 192z" transform="translate(-160 -256)"></path></g></g></svg><!-- <span class="fas fa-chevron-right ms-1" data-fa-transform="shrink-3"> </span> Font Awesome fontawesome.com --></button>
<button class="btn btn-primary px-6 px-sm-6" type="submit" data-wizard-next-btn="data-wizard-next-btn">{% trans 'Next' %}</button>
</div>
</div>
</div>
</div>
</div>
</div>
</div>
</div>
<script>
function getCookie(name) {
let cookieValue = null;
if (document.cookie && document.cookie !== '') {
const cookies = document.cookie.split(';');
for (let cookie of cookies) {
cookie = cookie.trim();
if (cookie.substring(0, name.length + 1) === (name + '=')) {
cookieValue = decodeURIComponent(cookie.substring(name.length + 1));
break;
}
}
}
return cookieValue;
}
const url = "{% url 'account_signup' %}";
let submit_btn = document.getElementById('submit_btn');
const csrfToken = getCookie('csrftoken');
submit_btn.addEventListener('click', async () => {
const allFormData = getAllFormData();
console.log(allFormData);
try {
showLoading();
const response = await fetch(url, {
method: 'POST',
headers: {
'X-CSRFToken': csrfToken,
'Content-Type': 'application/json',
},
body: JSON.stringify(allFormData),
});
hideLoading();
const data = await response.json();
if (response.ok) {
notify("success","Account created successfully");
} else {
notify("error",data.error);
}
} catch (error) {
notify("error",error);
}
});
function getAllFormData() {
const forms = document.querySelectorAll('form');
const formData = {};
forms.forEach((form, index) => {
const formId = form.id || `form${index + 1}`;
formData[formId] = {};
const formElements = form.elements;
for (let element of formElements) {
if (element.name) {
formData[formId][element.name] = element.value;
}
}
});
return formData;
}
function showLoading() {
Swal.fire({
title: "{% trans 'Please Wait' %}",
text: "{% trans 'Loading' %}...",
allowOutsideClick: false,
didOpen: () => {
Swal.showLoading();
}
});
}
function hideLoading() {
Swal.close();
}
function notify(tag,msg){
Swal.fire({
icon: tag,
titleText: msg
});
}
</script>
{% endblock content %}

View File

@ -6,7 +6,7 @@
{% block content %}
<div class="container">
<div class="row flex-center min-vh-100 py-5">
<div class="row min-vh-100 text-center">
<div class="col-sm-10 col-md-8 col-lg-5 col-xl-5 col-xxl-3"><a class="d-flex flex-center text-decoration-none mb-4" href="{% url 'landing_page' %}">
<div class="d-flex align-items-center fw-bolder fs-3 d-inline-block"><img src="{% static 'images/logos/logo.png' %}" alt="{% trans 'Haikal' %}" width="58" />
</div>
@ -69,7 +69,7 @@
<div class="form-check mb-3">
<input class="form-check-input" id="termsService" type="checkbox" />
<label class="form-label fs-9 text-transform-none" for="termsService">I accept the <a href="#!">terms </a>and <a href="#!">privacy policy</a></label>
<label class="form-label fs-9 text-transform-none" for="termsService">I accept the <a href="">terms </a>and <a href="">privacy policy</a></label>
</div>
<button type="submit" class="btn btn-primary w-100 mb-3">{{ _("Sign Up") }}</button>
<div class="text-center">{% trans 'Already have an account?' %}<a class="fw-bold" href="{% url 'account_login' %}"> {{ _("Sign In") }}</a></div>

View File

@ -13,15 +13,9 @@
<meta name="viewport" content="width=device-width, initial-scale=1">
<meta name="description" content="Haikal - The Backbone of Car Qar: An innovative car inventory management system designed to streamline dealership operations. Manage inventory, sales, transfers, and accounting seamlessly with advanced analytics and intuitive tools. Inspired by Arabic origins, Haikal empowers businesses with precision and efficiency.">
<!-- ===============================================-->
<!-- Document Title-->
<!-- ===============================================-->
<title>{% block title %}{% trans 'HAIKAL' %}{% endblock %}</title>
<!-- ===============================================-->
<!-- Favicons-->
<!-- ===============================================-->
<link rel="apple-touch-icon" sizes="180x180" href="{% static 'images/favicons/apple-touch-icon.png' %}">
<link rel="icon" type="image/png" sizes="32x32" href="{% static 'images/favicons/favicon-32x32.png' %}">
@ -30,8 +24,11 @@
<link rel="manifest" href="{% static 'images/favicons/manifest.json' %}">
<meta name="msapplication-TileImage" content="{% static 'images/logos/logo-d.png' %}">
<meta name="theme-color" content="#ffffff">
<script src="{% static 'vendors/simplebar/simplebar.min.js' %}"></script>
<script src="{% static 'js/config.js' %}"></script>
<script src="{% static 'js/config.js' %}"></script>
<script src="{% static 'js/sweetalert2.all.min.js' %}"></script>
<!-- ===============================================-->
@ -45,7 +42,7 @@
<link rel="preconnect" href="https://fonts.gstatic.com" crossorigin="">
<link href="https://fonts.googleapis.com/css2?family=Nunito+Sans:wght@300;400;600;700;800;900&amp;display=swap" rel="stylesheet">
<link href="{% static 'vendors/simplebar/simplebar.min.css' %}" rel="stylesheet">
<link href="https://cdn.jsdelivr.net/npm/sweetalert2@11.15.3/dist/sweetalert2.min.css" rel="stylesheet">
<link href="{% static 'css/sweetalert2.min.css' %}" rel="stylesheet">
<link rel="stylesheet" href="https://unicons.iconscout.com/release/v4.0.8/css/line.css">
{% if LANGUAGE_CODE == 'en' %}
<link href="{% static 'css/theme.min.css' %}" type="text/css" rel="stylesheet" id="style-default">
@ -57,8 +54,8 @@
</head>
<body>
{% include 'messages.html' %}
<main class="main" id="top">
<nav class="navbar navbar-vertical navbar-expand-lg">
<div class="collapse navbar-collapse" id="navbarVerticalCollapse">
@ -103,9 +100,9 @@
</li>
<hr class="my-0" />
<li class="nav-item">
<!-- label-->
<p class="navbar-vertical-label">Apps
</p>
<p class="navbar-vertical-label">Apps</p>
<hr class="navbar-vertical-line" />
<!-- parent pages-->
<div class="nav-item-wrapper"><a class="nav-link dropdown-indicator label-1" href="#nv-inventory" role="button" data-bs-toggle="collapse" aria-expanded="false" aria-controls="nv-inventory">
@ -254,14 +251,9 @@
<button class="btn navbar-toggler navbar-toggler-humburger-icon hover-bg-transparent" type="button" data-bs-toggle="collapse" data-bs-target="#navbarVerticalCollapse" aria-controls="navbarVerticalCollapse" aria-expanded="false" aria-label="Toggle Navigation"><span class="navbar-toggle-icon"><span class="toggle-line"></span></span></button>
<a class="navbar-brand me-1 me-sm-3" href="{% url 'landing_page' %}">
<div class="d-flex align-items-center">
{% if data_bs_theme == 'dark' %}
<img src="{% static 'images/logos/logo.png' %}" alt="haikal" width="27" />
{% else %}
<img src="{% static 'images/logos/logo-d.png' %}" alt="haikal" width="27" />
{% endif %}
<img class="d-dark-none" src="{% static 'images/logos/logo.png' %}" alt="haikal" width="27" /><img class="d-light-none" src="{% static 'images/logos/logo-d.png' %}" alt="haikal" width="27" />
<h5 class="logo-text ms-2 d-none d-sm-block">{% trans 'Haikal' %}</h5>
</div>
</div>
</a>
</div>
@ -472,11 +464,10 @@
</li>
</ul>
</div>
</nav>
<div class="content">
{% include 'messages.html' %}
{% block content %}
<!-- Main content goes here -->
{% endblock %}
@ -493,146 +484,7 @@
</div>
</footer>
</div>
<div class="modal fade" id="searchBoxModal" tabindex="-1" aria-hidden="true" data-bs-backdrop="true" data-phoenix-modal="data-phoenix-modal" style="--phoenix-backdrop-opacity: 1;">
<div class="modal-dialog">
<div class="modal-content mt-15 rounded-pill">
<div class="modal-body p-0">
<div class="search-box navbar-top-search-box" data-list='{"valueNames":["title"]}' style="width: auto;">
<form class="position-relative" data-bs-toggle="search" data-bs-display="static">
<input class="form-control search-input fuzzy-search rounded-pill form-control-lg" type="search" placeholder="Search..." aria-label="Search" />
<span class="fas fa-search search-box-icon"></span>
</form>
<div class="btn-close position-absolute end-0 top-50 translate-middle cursor-pointer shadow-none" data-bs-dismiss="search">
<button class="btn btn-link p-0" aria-label="Close"></button>
</div>
<div class="dropdown-menu border start-0 py-0 overflow-hidden w-100">
<div class="scrollbar-overlay" style="max-height: 30rem;">
<div class="list pb-3">
<h6 class="dropdown-header text-body-highlight fs-10 py-2">24 <span class="text-body-quaternary">results</span></h6>
<hr class="my-0" />
<h6 class="dropdown-header text-body-highlight fs-9 border-bottom border-translucent py-2 lh-sm">Recently Searched </h6>
<div class="py-2"><a class="dropdown-item" href="../apps/e-commerce/landing/product-details.html">
<div class="d-flex align-items-center">
<div class="fw-normal text-body-highlight title"><span class="fa-solid fa-clock-rotate-left" data-fa-transform="shrink-2"></span> Store Macbook</div>
</div>
</a>
<a class="dropdown-item" href="../apps/e-commerce/landing/product-details.html">
<div class="d-flex align-items-center">
<div class="fw-normal text-body-highlight title"> <span class="fa-solid fa-clock-rotate-left" data-fa-transform="shrink-2"></span> MacBook Air - 13″</div>
</div>
</a>
</div>
<hr class="my-0" />
<h6 class="dropdown-header text-body-highlight fs-9 border-bottom border-translucent py-2 lh-sm">Products</h6>
<div class="py-2"><a class="dropdown-item py-2 d-flex align-items-center" href="../apps/e-commerce/landing/product-details.html">
<div class="file-thumbnail me-2"><img class="h-100 w-100 object-fit-cover rounded-3" src="{% static 'images/products/60x60/3.png' %}" alt="" /></div>
<div class="flex-1">
<h6 class="mb-0 text-body-highlight title">MacBook Air - 13″</h6>
<p class="fs-10 mb-0 d-flex text-body-tertiary"><span class="fw-medium text-body-tertiary text-opactity-85">8GB Memory - 1.6GHz - 128GB Storage</span></p>
</div>
</a>
<a class="dropdown-item py-2 d-flex align-items-center" href="../apps/e-commerce/landing/product-details.html">
<div class="file-thumbnail me-2"><img class="img-fluid" src="{% static 'images/products/60x60/3.png' %}" alt="" /></div>
<div class="flex-1">
<h6 class="mb-0 text-body-highlight title">MacBook Pro - 13″</h6>
<p class="fs-10 mb-0 d-flex text-body-tertiary"><span class="fw-medium text-body-tertiary text-opactity-85">30 Sep at 12:30 PM</span></p>
</div>
</a>
</div>
<hr class="my-0" />
<h6 class="dropdown-header text-body-highlight fs-9 border-bottom border-translucent py-2 lh-sm">Quick Links</h6>
<div class="py-2"><a class="dropdown-item" href="../apps/e-commerce/landing/product-details.html">
<div class="d-flex align-items-center">
<div class="fw-normal text-body-highlight title"><span class="fa-solid fa-link text-body" data-fa-transform="shrink-2"></span> Support MacBook House</div>
</div>
</a>
<a class="dropdown-item" href="../apps/e-commerce/landing/product-details.html">
<div class="d-flex align-items-center">
<div class="fw-normal text-body-highlight title"> <span class="fa-solid fa-link text-body" data-fa-transform="shrink-2"></span> Store MacBook″</div>
</div>
</a>
</div>
<hr class="my-0" />
<h6 class="dropdown-header text-body-highlight fs-9 border-bottom border-translucent py-2 lh-sm">Files</h6>
<div class="py-2"><a class="dropdown-item" href="../apps/e-commerce/landing/product-details.html">
<div class="d-flex align-items-center">
<div class="fw-normal text-body-highlight title"><span class="fa-solid fa-file-zipper text-body" data-fa-transform="shrink-2"></span> Library MacBook folder.rar</div>
</div>
</a>
<a class="dropdown-item" href="../apps/e-commerce/landing/product-details.html">
<div class="d-flex align-items-center">
<div class="fw-normal text-body-highlight title"> <span class="fa-solid fa-file-lines text-body" data-fa-transform="shrink-2"></span> Feature MacBook extensions.txt</div>
</div>
</a>
<a class="dropdown-item" href="../apps/e-commerce/landing/product-details.html">
<div class="d-flex align-items-center">
<div class="fw-normal text-body-highlight title"> <span class="fa-solid fa-image text-body" data-fa-transform="shrink-2"></span> MacBook Pro_13.jpg</div>
</div>
</a>
</div>
<hr class="my-0" />
<h6 class="dropdown-header text-body-highlight fs-9 border-bottom border-translucent py-2 lh-sm">Members</h6>
<div class="py-2"><a class="dropdown-item py-2 d-flex align-items-center" href="../pages/members.html">
<div class="avatar avatar-l status-online me-2 text-body">
<img class="rounded-circle " src="{% static 'images/team/40x40/10.webp' %}" alt="" />
</div>
<div class="flex-1">
<h6 class="mb-0 text-body-highlight title">Carry Anna</h6>
<p class="fs-10 mb-0 d-flex text-body-tertiary">anna@technext.it</p>
</div>
</a>
<a class="dropdown-item py-2 d-flex align-items-center" href="../pages/members.html">
<div class="avatar avatar-l me-2 text-body">
<img class="rounded-circle " src="{% static 'images/team/40x40/12.webp' %}" alt="" />
</div>
<div class="flex-1">
<h6 class="mb-0 text-body-highlight title">John Smith</h6>
<p class="fs-10 mb-0 d-flex text-body-tertiary">smith@technext.it</p>
</div>
</a>
</div>
<hr class="my-0" />
<h6 class="dropdown-header text-body-highlight fs-9 border-bottom border-translucent py-2 lh-sm">Related Searches</h6>
<div class="py-2"><a class="dropdown-item" href="../apps/e-commerce/landing/product-details.html">
<div class="d-flex align-items-center">
<div class="fw-normal text-body-highlight title"><span class="fa-brands fa-firefox-browser text-body" data-fa-transform="shrink-2"></span> Search in the Web MacBook</div>
</div>
</a>
<a class="dropdown-item" href="../apps/e-commerce/landing/product-details.html">
<div class="d-flex align-items-center">
<div class="fw-normal text-body-highlight title"> <span class="fa-brands fa-chrome text-body" data-fa-transform="shrink-2"></span> Store MacBook″</div>
</div>
</a>
</div>
</div>
<div class="text-center">
<p class="fallback fw-bold fs-7 d-none">No Result Found.</p>
</div>
</div>
</div>
</div>
</div>
</div>
</div>
</div>
<div class="support-chat-container">
<div class="container-fluid support-chat">
@ -681,127 +533,11 @@
<!-- ===============================================-->
<!-- End of Main Content-->
<!-- ===============================================-->
<script>
<script src="
https://cdn.jsdelivr.net/npm/sweetalert2@11.15.3/dist/sweetalert2.all.min.js
"></script>
<script>
const Toast = Swal.mixin({
toast: true,
position: "top-end",
showConfirmButton: false,
timer: 3000,
timerProgressBar: true,
didOpen: (toast) => {
toast.onmouseenter = Swal.stopTimer;
toast.onmouseleave = Swal.resumeTimer;
}
});
function notify(tag,msg){
Toast.fire({
icon: tag,
titleText: msg
});
}
var navbarTopStyle = window.config.config.phoenixNavbarTopStyle;
var navbarTop = document.querySelector('.navbar-top');
if (navbarTopStyle === 'darker') {
navbarTop.setAttribute('data-navbar-appearance', 'darker');
}
var navbarVerticalStyle = window.config.config.phoenixNavbarVerticalStyle;
var navbarVertical = document.querySelector('.navbar-vertical');
if (navbarVertical && navbarVerticalStyle === 'darker') {
navbarVertical.setAttribute('data-navbar-appearance', 'darker');
}
function save_as_pdf(){
const quotationHtml = document.getElementById('quotation-html').outerHTML;
const printWindow = window.open('', '', 'height=500,width=800');
printWindow.document.write(quotationHtml);
printWindow.document.close();
printWindow.print();
printWindow.close();
}
function getCookie(name) {
let cookieValue = null;
if (document.cookie && document.cookie !== '') {
const cookies = document.cookie.split(';');
for (let cookie of cookies) {
cookie = cookie.trim();
if (cookie.substring(0, name.length + 1) === (name + '=')) {
cookieValue = decodeURIComponent(cookie.substring(name.length + 1));
break;
}
}
}
return cookieValue;
}
let submit_btn = document.getElementById('submit_btn');
const csrfToken = getCookie('csrftoken');
submit_btn.addEventListener('click', async () => {
const allFormData = getAllFormData();
console.log(allFormData);
try {
showLoading();
const response = await fetch('http://10.10.1.120:8888/en/signup/', {
method: 'POST',
headers: {
'X-CSRFToken': csrfToken,
'Content-Type': 'application/json',
},
body: JSON.stringify(allFormData),
});
hideLoading();
const data = await response.json();
if (response.ok) {
notify("success","Account created successfully");
} else {
notify("error",data.error);
}
} catch (error) {
notify("error",error);
}
});
function getAllFormData() {
const forms = document.querySelectorAll('form');
const formData = {};
forms.forEach((form, index) => {
const formId = form.id || `form${index + 1}`;
formData[formId] = {};
const formElements = form.elements;
for (let element of formElements) {
if (element.name) {
formData[formId][element.name] = element.value;
}
}
});
return formData;
}
function showLoading() {
Swal.fire({
title: "{% trans 'Please Wait' %}",
text: "{% trans 'Loading' %}...",
allowOutsideClick: false,
didOpen: () => {
Swal.showLoading();
}
});
}
function hideLoading() {
Swal.close();
}
</script>
<!-- ===============================================-->

File diff suppressed because it is too large Load Diff

View File

@ -4,10 +4,7 @@
{% block content %}
<div class="container">
<!-- ============================================-->
<!-- <section> begin ============================-->
<section class="py-0">
<div class="container-small">
<div class="ecommerce-topbar">
@ -16,15 +13,7 @@
<!-- end of .container-->
</section>
<!-- <section> close ============================-->
<!-- ============================================-->
<!-- ============================================-->
<!-- <section> begin ============================-->
<section class="pt-5 pb-9">
<div class="container-small">
@ -37,7 +26,7 @@
<div class="row g-2 g-sm-3">
<div class="col-auto">
<button class="btn btn-phoenix-secondary"><span class="fas fa-key me-2"></span>Reset password</button>
<a href="{% url 'account_change_password' %}" class="btn btn-phoenix-secondary"><span class="fas fa-key me-2"></span>{{ _("Change Password") }}</a>
</div>
<div class="col-auto">
<a class="btn btn-phoenix-secondary " href="#!"><span class="fa-solid fa-user-gear me-2 mb-2 mb-xxl-0"></span>Settings </a>
@ -144,7 +133,7 @@
<div class="d-flex justify-content-between">
<div class="mb-5 mb-md-0 mb-lg-5 me-3">
<div class="d-sm-flex d-md-block d-lg-flex align-items-center mb-3">
<h3 class="mb-0">{{ dealer.get_active_plan.plan }}</h3><span class="badge ms-sm-3 ms-md-0 ms-lg-3 fs-10 text-bg-warning">{% trans 'most valuable'|upper %}</span>
<h3 class="mb-0">{{ dealer.get_active_plan.plan|capfirst }}</h3><span class="badge ms-sm-3 ms-md-0 ms-lg-3 fs-10 text-bg-warning">{% trans 'most valuable'|upper %}</span>
</div>
<p class="fs-9 text-body-tertiary">{% trans 'Active until' %}: {{ dealer.get_active_plan.end_date|date}}</p>
<div class="d-flex align-items-end mb-md-5 mb-lg-0">
@ -156,9 +145,9 @@
<div class="row flex-1 justify-content-end">
<div class="col-sm-8 col-md-12">
<div class="d-sm-flex d-md-block d-lg-flex justify-content-end align-items-end h-100">
<ul class="list-unstyled mb-0 border-start-sm border-start-md-0 border-start-lg ps-sm-5 ps-md-0 ps-lg-5 border-warning-subtle">
<li class="d-flex align-items-center"><span class="uil uil-check-circle text-success me-2"></span><span class="text-body-tertiary fw-semibold">{{ dealer.get_plan.description}}</span></li>
</ul>
<div class="list-unstyled mb-0 border-start-sm border-start-md-0 border-start-lg ps-sm-5 ps-md-0 ps-lg-5 border-warning-subtle">
<div class="d-flex align-items-center"><span class="uil uil-check-circle text-success me-2"></span><span class="text-body-tertiary fw-semibold">{{ dealer.get_plan.description}}</span></div>
</div>
</div>
</div>
</div>

View File

@ -51,7 +51,7 @@ function getCookie(name) {
return cookieValue;
}
const csrfToken = getCookie('csrftoken');
const csrfToken = getCookie('token');
async function sendMessage() {

View File

@ -11,44 +11,40 @@
<meta charset="utf-8">
<meta http-equiv="X-UA-Compatible" content="IE=edge">
<meta name="viewport" content="width=device-width, initial-scale=1">
<meta name="description" content="Haikal - The Backbone of Car Qar: An innovative car inventory management system designed to streamline dealership operations. Manage inventory, sales, transfers, and accounting seamlessly with advanced analytics and intuitive tools. Inspired by Arabic origins, Haikal empowers businesses with precision and efficiency.">
<!-- ===============================================-->
<!-- Document Title-->
<!-- ===============================================-->
<title>{% block title %}{% trans 'HAIKAL' %}{% endblock %}</title>
<!-- ===============================================-->
<!-- Favicons-->
<!-- ===============================================-->
<link rel="apple-touch-icon" sizes="180x180" href="{% static 'images/favicons/apple-touch-icon.png' %}">
<link rel="icon" type="image/png" sizes="32x32" href="{% static 'images/favicons/favicon-32x32.png' %}">
<link rel="icon" type="image/png" sizes="16x16" href="{% static 'images/favicons/favicon-16x16.png' %}">
<link rel="shortcut icon" type="image/x-icon" href="{% static 'images/favicons/favicon.ico' %}">
<link rel="manifest" href="{% static 'images/favicons/manifest.json' %}">
<meta name="msapplication-TileImage" content="{% static 'images/favicons/mstile-150x150.png' %}">
<meta name="msapplication-TileImage" content="{% static 'images/logos/logo-d.png' %}">
<meta name="theme-color" content="#ffffff">
<script src="{% static 'vendors/simplebar/simplebar.min.js' %}"></script>
<script src="{% static 'js/config.js' %}"></script>
<script src="{% static 'js/config.js' %}"></script>
<script src="{% static 'js/sweetalert2.all.min.js' %}"></script>
<!-- ===============================================-->
<!-- Stylesheets-->
<!-- ===============================================-->
<link href="{% static 'vendors/mapbox-gl/mapbox-gl.css' %}" rel="stylesheet">
<link href="{% static 'vendors/swiper/swiper-bundle.min.css' %}" rel="stylesheet">
<link rel="preconnect" href="https://fonts.googleapis.com">
<link rel="preconnect" href="https://fonts.gstatic.com" crossorigin="">
<link href="https://fonts.googleapis.com/css2?family=Nunito+Sans:wght@300;400;600;700;800;900&amp;display=swap" rel="stylesheet">
<link href="{% static 'vendors/simplebar/simplebar.min.css' %}" rel="stylesheet">
<link href="{% static 'css/sweetalert2.min.css' %}" rel="stylesheet">
<link rel="stylesheet" href="https://unicons.iconscout.com/release/v4.0.8/css/line.css">
{% if LANGUAGE_CODE == 'ar' %}
<link href="{% static 'css/theme-rtl.min.css' %}" type="text/css" rel="stylesheet" id="style-rtl">
<link href="{% static 'css/user-rtl.min.css' %}" type="text/css" rel="stylesheet" id="user-style-rtl">
{% else %}
<link href="{% static 'css/theme.min.css' %}" type="text/css" rel="stylesheet" id="style-default">
<link href="{% static 'css/user.min.css' %}" type="text/css" rel="stylesheet" id="user-style-default">
{% endif %}
{% if LANGUAGE_CODE == 'en' %}
<link href="{% static 'css/theme.min.css' %}" type="text/css" rel="stylesheet" id="style-default">
<link href="{% static 'css/user.min.css' %}" type="text/css" rel="stylesheet" id="user-style-default">
{% else %}
<link href="{% static 'css/theme-rtl.min.css' %}" type="text/css" rel="stylesheet" id="style-rtl">
<link href="{% static 'css/user-rtl.min.css' %}" type="text/css" rel="stylesheet" id="user-style-rtl">
{% endif %}
</head>
@ -61,18 +57,14 @@
<div class="bg-body-emphasis sticky-top" data-navbar-shadow-on-scroll="data-navbar-shadow-on-scroll">
<nav class="navbar navbar-expand-lg container-small px-3 px-lg-7 px-xxl-3"><a class="navbar-brand me-1 me-sm-3" href="{% url 'landing_page' %}">
<div class="d-flex align-items-center">
<div class="d-flex align-items-center"><img src="{% static 'images/logos/logo.png' %}" alt="haikal" width="27" />
<div class="d-flex align-items-center">
<img class="d-light-none" src="{% static 'images/logos/logo-d.png' %}" alt="haikal" width="27" />
<img class="d-dark-none" src="{% static 'images/logos/logo.png' %}" alt="haikal" width="27" />
<h5 class="logo-text ms-2 d-none d-sm-block">{% trans 'Haikal' %}</h5>
</div>
</div>
</a>
<div class="d-lg-none">
<div class="theme-control-toggle fa-icon-wait px-2">
<input class="form-check-input ms-0 theme-control-toggle-input" type="checkbox" data-theme-control="phoenixTheme" value="dark" id="themeControlToggle" />
<label class="mb-0 theme-control-toggle-label theme-control-toggle-light" for="themeControlToggle" data-bs-theme-value="light" data-bs-toggle="tooltip" data-bs-placement="left" data-bs-title="Switch theme" style="height:32px;width:32px;"><span class="icon" data-feather="moon"></span></label>
<label class="mb-0 theme-control-toggle-label theme-control-toggle-dark" for="themeControlToggle" data-bs-theme-value="dark" data-bs-toggle="tooltip" data-bs-placement="left" data-bs-title="Switch theme" style="height:32px;width:32px;"><span class="icon" data-feather="sun"></span></label>
</div>
</div>
<button class="navbar-toggler" type="button" data-bs-toggle="collapse" data-bs-target="#navbarSupportedContent" aria-controls="navbarSupportedContent" aria-expanded="false" aria-label="Toggle navigation"><span class="navbar-toggler-icon"></span></button>
<div class="collapse navbar-collapse" id="navbarSupportedContent">
<div class="border-bottom border-translucent border-bottom-lg-0 mb-2">
@ -1277,19 +1269,6 @@
</a>
<script>
function save_as_pdf(){
const quotationHtml = document.getElementById('quotation-html').outerHTML;
const printWindow = window.open('', '', 'height=500,width=800');
printWindow.document.write(quotationHtml);
printWindow.document.close();
printWindow.print();
printWindow.close();
}
</script>
<!-- ===============================================-->
<!-- JavaScripts-->
<!-- ===============================================-->
@ -1303,6 +1282,11 @@
<script src="{% static 'vendors/feather-icons/feather.min.js' %}"></script>
<script src="{% static 'vendors/dayjs/dayjs.min.js' %}"></script>
<script src="{% static 'js/phoenix.js' %}"></script>
<script src="{% static 'vendors/echarts/echarts.min.js' %}"></script>
<script src="{% static 'js/travel-agency-dashboard.js' %}"></script>
<script src="{% static 'vendors/mapbox-gl/mapbox-gl.js' %}"></script>
<script src="https://unpkg.com/@turf/turf@6/turf.min.js"></script>
<script src="{% static 'vendors/swiper/swiper-bundle.min.js' %}"></script>
</body>

View File

@ -58,28 +58,24 @@
</div>
<!-- Specification Modal -->
<div class="modal fade" id="specificationsModal" tabindex="-1" aria-labelledby="specificationsModalLabel" aria-hidden="true">
<div class="modal-dialog modal-lg modal-dialog-scrollable">
<div class="modal-content glossy-modal">
<div class="modal-header bg-primary text-light">
<h5 class="modal-title" id="specificationsModalLabel">
{% trans 'specifications'|upper %}
</h5>
<button type="button" class="btn-close" data-bs-dismiss="modal" aria-label="Close"></button>
</div>
<div class="modal-body">
<div id="specificationsContent"></div>
<div class="d-grid gap-2">
<button type="button"
class="btn btn-sm btn-danger"
data-bs-dismiss="modal">
{% trans 'Close' %}
</button>
</div>
</div>
<div class="modal fade" id="specificationsModal" tabindex="-1" aria-labelledby="specificationsModalLabel" aria-hidden="true">
<div class="modal-dialog modal-dialog-centered">
<div class="modal-content">
<div class="modal-header">
<h5 class="modal-title" id="specificationsModalLabel">{% trans 'specifications'|upper %}</h5>
<button class="btn btn-close p-1" type="button" data-bs-dismiss="modal" aria-label="Close"></button>
</div>
<div class="modal-body">
<div id="specificationsContent"></div>
</div>
<div class="modal-footer">
<button class="btn btn-outline-primary" type="button" data-bs-dismiss="modal">{% trans 'Close' %}</button>
</div>
</div>
</div>
</div>
<!-- Specification Modal -->
<!-- Main Container -->
<div class="d-flex flex-column min-vh-100">

View File

@ -23,7 +23,7 @@
<script src='https://cdn.jsdelivr.net/npm/tesseract.js@5/dist/tesseract.min.js'></script>
<div class="container p-2">
<div class="container">
<!-- Specification Modal -->
<div class="modal fade" id="specificationsModal"
@ -344,10 +344,7 @@
</div>
<script>
document.addEventListener("DOMContentLoaded", function() {
function getCookie(name) {
function getCookie(name) {
let cookieValue = null;
if (document.cookie && document.cookie !== '') {
const cookies = document.cookie.split(';');
@ -362,7 +359,10 @@ document.addEventListener("DOMContentLoaded", function() {
return cookieValue;
}
const csrfToken = getCookie('csrftoken');
document.addEventListener("DOMContentLoaded", function() {
const csrfToken = getCookie('token');
const vinInput = document.getElementById('{{ form.vin.id_for_label }}');
const stockTypeSelect = document.getElementById('{{ form.stock_type.id_for_label }}');
@ -613,23 +613,29 @@ async function loadSpecifications(trimId){
modelSelect.addEventListener("change", (e) => {loadSeries(e.target.value, yearSelect.value)})
decodeVinBtn.addEventListener('click', decodeVin);
});
const Toast = Swal.mixin({
toast: true,
position: "top-end",
showConfirmButton: false,
timer: 3000,
timerProgressBar: true,
didOpen: (toast) => {
toast.onmouseenter = Swal.stopTimer;
toast.onmouseleave = Swal.resumeTimer;
}
});
function notify(tag,msg){
Toast.fire({
function showLoading() {
Swal.fire({
title: "{% trans 'Please Wait' %}",
text: "{% trans 'Loading' %}...",
allowOutsideClick: false,
didOpen: () => {
Swal.showLoading();
}
});
}
function hideLoading() {
Swal.close();
}
function notify(tag,msg){
Swal.fire({
icon: tag,
titleText: msg
});
}
</script>
{% endblock %}

View File

@ -1,7 +1,8 @@
{% if messages %}
{% for message in messages %}
<script>
{% if messages %}
{% for message in messages %}
const Toast = Swal.mixin({
toast: true,
position: "top-end",
@ -17,7 +18,8 @@
icon: "{{ message.tags }}",
titleText: "{{ message| safe }}"
});
</script>
{% endfor %}
{% endif %}
{% endif %}
</script>

View File

@ -1,14 +1,14 @@
{% extends "base.html" %}
{% load crispy_forms_filters %}
{% load static %}
{% load crispy_forms_tags %}
{% load i18n %}
{% block title %}{{ _("Payyment Create") }}{% endblock title %}
{% block title %}{{ _("Make Payment") }}{% endblock title %}
{% block content %}
<div class="container">
<div class="row justify-content-center">
<div class="col-md-8">
<div class="card">
<div class="card-header">{{ _("Payment Create") }}</div>
<div class="card-header">{{ _("Make Payment") }}</div>
<div class="card-body">
<form method="post" action="{% url 'payment_create' pk=quotation.pk %}">
{% csrf_token %}

View File

@ -138,7 +138,7 @@
class="btn btn-success"
data-bs-toggle="modal"
data-bs-target="#confirmModal">
{% trans 'Acccept' %}
{% trans 'Accept' %}
</button>
{% endif %}
{% if quotation.status == 'Draft' and not quotation.is_approved %}