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 *.pyc
__pycache__ __pycache__
**/*__pycache__ **/*__pycache__
db.sqlite3 db.sqlite
media media
car_inventory/settings.py car_inventory/settings.py
# Backup files # # 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 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 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 import django.db.models.deletion
from django.db import migrations, models
class Migration(migrations.Migration): class Migration(migrations.Migration):

View File

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

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 django.db.models.deletion
import inventory.mixins import inventory.mixins
import phonenumber_field.modelfields import phonenumber_field.modelfields
from decimal import Decimal
from django.conf import settings
from django.db import migrations, models
class Migration(migrations.Migration): class Migration(migrations.Migration):
@ -13,8 +13,8 @@ class Migration(migrations.Migration):
initial = True initial = True
dependencies = [ dependencies = [
migrations.swappable_dependency(settings.AUTH_USER_MODEL),
('django_ledger', '0017_alter_accountmodel_unique_together_and_more'), ('django_ledger', '0017_alter_accountmodel_unique_together_and_more'),
migrations.swappable_dependency(settings.AUTH_USER_MODEL),
] ]
operations = [ operations = [
@ -66,90 +66,6 @@ class Migration(migrations.Migration):
}, },
bases=(models.Model, inventory.mixins.LocalizedNameMixin), 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( migrations.CreateModel(
name='ExteriorColors', name='ExteriorColors',
fields=[ fields=[
@ -178,25 +94,6 @@ class Migration(migrations.Migration):
}, },
bases=(models.Model, inventory.mixins.LocalizedNameMixin), 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( migrations.CreateModel(
name='Payment', name='Payment',
fields=[ fields=[
@ -211,30 +108,6 @@ class Migration(migrations.Migration):
'verbose_name_plural': 'payments', '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( migrations.CreateModel(
name='Subscription', name='Subscription',
fields=[ fields=[
@ -257,110 +130,91 @@ class Migration(migrations.Migration):
], ],
), ),
migrations.CreateModel( migrations.CreateModel(
name='Vendor', name='CarFinance',
fields=[ fields=[
('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')), ('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
('crn', models.CharField(max_length=10, unique=True, verbose_name='Commercial Registration Number')), ('cost_price', models.DecimalField(decimal_places=2, max_digits=14, verbose_name='Cost Price')),
('vrn', models.CharField(max_length=15, unique=True, verbose_name='VAT Registration Number')), ('selling_price', models.DecimalField(decimal_places=2, max_digits=14, verbose_name='Selling Price')),
('arabic_name', models.CharField(max_length=255, verbose_name='Arabic Name')), ('discount_amount', models.DecimalField(decimal_places=2, default=Decimal('0.00'), max_digits=14, verbose_name='Discount Amount')),
('name', models.CharField(max_length=255, verbose_name='English Name')), ('additional_services', models.ManyToManyField(blank=True, related_name='additional_finances', to='inventory.additionalservices')),
('contact_person', models.CharField(max_length=100, verbose_name='Contact Person')), ('car', models.OneToOneField(on_delete=django.db.models.deletion.CASCADE, related_name='finances', to='inventory.car')),
('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={ options={
'verbose_name': 'Vendor', 'verbose_name': 'Car Financial Details',
'verbose_name_plural': 'Vendors', '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), 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( migrations.AddField(
model_name='subscription', model_name='car',
name='users', name='id_car_model',
field=models.ManyToManyField(through='inventory.SubscriptionUser', to=settings.AUTH_USER_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( migrations.CreateModel(
name='SalesOrder', name='CarRegistration',
fields=[ fields=[
('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')), ('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
('created_at', models.DateTimeField(auto_now_add=True, verbose_name='Created At')), ('plate_number', models.IntegerField(verbose_name='Plate Number')),
('total_amount', models.DecimalField(decimal_places=2, max_digits=14, verbose_name='Total Amount')), ('text1', models.CharField(max_length=1, verbose_name='Text 1')),
('quotation', models.OneToOneField(on_delete=django.db.models.deletion.CASCADE, related_name='sales_order', to='inventory.salequotation', verbose_name='Quotation')), ('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')),
migrations.CreateModel( ('car', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, related_name='registrations', to='inventory.car', verbose_name='Car')),
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')),
], ],
options={ options={
'verbose_name': 'Representative', 'verbose_name': 'Registration',
'verbose_name_plural': 'Representatives', '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), 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( migrations.AddField(
model_name='payment', model_name='car',
name='quotation', name='id_car_serie',
field=models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, related_name='payments', to='inventory.salequotation'), 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.AddField(
model_name='customer',
name='dealer',
field=models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, related_name='customers', to='inventory.dealer'),
), ),
migrations.CreateModel( migrations.CreateModel(
name='CustomCard', name='CarSpecification',
fields=[ fields=[
('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')), ('id_car_specification', models.AutoField(primary_key=True, serialize=False)),
('custom_number', models.CharField(max_length=255, verbose_name='Custom Number')), ('name', models.CharField(max_length=255)),
('custom_date', models.DateField(verbose_name='Custom Date')), ('arabic_name', models.CharField(max_length=255)),
('car', models.OneToOneField(on_delete=django.db.models.deletion.CASCADE, related_name='custom_cards', to='inventory.car', verbose_name='Car')), ('id_parent', models.ForeignKey(blank=True, db_column='id_parent', null=True, on_delete=django.db.models.deletion.DO_NOTHING, to='inventory.carspecification')),
], ],
options={ options={
'verbose_name': 'Custom Card', 'verbose_name': 'Specification',
'verbose_name_plural': 'Custom Cards',
}, },
bases=(models.Model, inventory.mixins.LocalizedNameMixin),
), ),
migrations.CreateModel( migrations.CreateModel(
name='CarTrim', name='CarTrim',
@ -391,20 +245,66 @@ class Migration(migrations.Migration):
'verbose_name': 'Specification Value', '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( migrations.CreateModel(
name='CarRegistration', name='CustomCard',
fields=[ fields=[
('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')), ('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
('plate_number', models.IntegerField(verbose_name='Plate Number')), ('custom_number', models.CharField(max_length=255, verbose_name='Custom Number')),
('text1', models.CharField(max_length=1, verbose_name='Text 1')), ('custom_date', models.DateField(verbose_name='Custom Date')),
('text2', models.CharField(max_length=1, verbose_name='Text 2')), ('car', models.OneToOneField(on_delete=django.db.models.deletion.CASCADE, related_name='custom_cards', to='inventory.car', verbose_name='Car')),
('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={ options={
'verbose_name': 'Registration', 'verbose_name': 'Custom Card',
'verbose_name_plural': 'Registrations', '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( migrations.CreateModel(
@ -423,56 +323,152 @@ class Migration(migrations.Migration):
'verbose_name_plural': 'Car Locations', '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( migrations.AddField(
model_name='car', model_name='car',
name='dealer', name='dealer',
field=models.ForeignKey(on_delete=django.db.models.deletion.DO_NOTHING, related_name='cars', to='inventory.dealer', verbose_name='Dealer'), field=models.ForeignKey(on_delete=django.db.models.deletion.DO_NOTHING, related_name='cars', to='inventory.dealer', verbose_name='Dealer'),
), ),
migrations.AddField( migrations.AddField(
model_name='car', model_name='additionalservices',
name='id_car_make', name='dealer',
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'), 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( migrations.AddField(
model_name='car', model_name='payment',
name='id_car_model', name='quotation',
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'), 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( migrations.AddField(
model_name='car', model_name='subscription',
name='id_car_serie', name='users',
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'), field=models.ManyToManyField(through='inventory.SubscriptionUser', to=settings.AUTH_USER_MODEL),
), ),
migrations.AddField( migrations.CreateModel(
model_name='car', name='Vendor',
name='id_car_trim', fields=[
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'), ('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( migrations.AddField(
model_name='car', model_name='car',
name='vendor', 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'), 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( migrations.CreateModel(
name='CarReservation', name='CarReservation',
fields=[ 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) return getattr(self, 'name', None)
class AddDealerInstanceMixin: # class AddDealerInstanceMixin:
def form_valid(self, form): # def form_valid(self, form):
if form.is_valid(): # if form.is_valid():
form.instance.dealer = self.request.user.dealer.get_root_dealer # form.instance.dealer = self.request.user.dealer.get_root_dealer
form.save() # form.save()
return super().form_valid(form) # return super().form_valid(form)
else: # else:
return form.errors # return form.errors

View File

@ -3,7 +3,7 @@ from uuid import uuid4
from django.conf import settings from django.conf import settings
from django.db import models, transaction from django.db import models, transaction
from django.db.models import Sum, F, Count 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.db.models.signals import pre_save, post_save
from django.dispatch import receiver from django.dispatch import receiver
from django.utils.translation import gettext_lazy as _ from django.utils.translation import gettext_lazy as _
@ -29,6 +29,15 @@ from django.db.models import FloatField
from .mixins import LocalizedNameMixin from .mixins import LocalizedNameMixin
from django_ledger.models import EntityModel 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 = ( UNIT_CHOICES = (
("Kg", _("Kg")), ("Kg", _("Kg")),
("L", _("L")), ("L", _("L")),
@ -147,10 +156,10 @@ class CarStockTypeChoices(models.TextChoices):
class DEALER_TYPES(models.TextChoices): class DEALER_TYPES(models.TextChoices):
Owner = "Owner", _("Owner") OWNER = "Owner", _("Owner")
Inventory = "Inventory", _("Inventory") INVENTORY = "Inventory", _("Inventory")
Accountent = "Accountent", _("Accountent") ACCOUNTANT = "Accountent", _("Accountent")
Sales = "sales", _("Sales") SALES = "sales", _("Sales")
class AdditionalServices(models.Model, LocalizedNameMixin): class AdditionalServices(models.Model, LocalizedNameMixin):
@ -482,12 +491,15 @@ class Subscription(models.Model):
is_active = models.BooleanField(default=True) is_active = models.BooleanField(default=True)
def __str__(self): def __str__(self):
return self.plan return self.plan
class SubscriptionUser(models.Model): class SubscriptionUser(models.Model):
subscription = models.ForeignKey(Subscription, on_delete=models.CASCADE) subscription = models.ForeignKey(Subscription, on_delete=models.CASCADE)
user = models.ForeignKey(User, on_delete=models.CASCADE) user = models.ForeignKey(User, on_delete=models.CASCADE)
def __str__(self): def __str__(self):
return f"{self.subscription} - {self.user}" return f"{self.subscription} - {self.user}"
class SubscriptionPlan(models.Model): class SubscriptionPlan(models.Model):
name = models.CharField(max_length=255) name = models.CharField(max_length=255)
description = models.TextField() description = models.TextField()
@ -495,41 +507,44 @@ class SubscriptionPlan(models.Model):
max_users = models.IntegerField() # maximum number of users per account max_users = models.IntegerField() # maximum number of users per account
def __str__(self): def __str__(self):
return f"{self.name} - {self.price}" return f"{self.name} - {self.price}"
# Dealer Model # Dealer Model
class Dealer(models.Model, LocalizedNameMixin): class Dealer(models.Model, LocalizedNameMixin):
user = models.OneToOneField(User, on_delete=models.CASCADE, related_name="dealer") user = models.OneToOneField(User, on_delete=models.CASCADE, related_name="dealer")
crn = models.CharField( crn = models.CharField(max_length=10,
max_length=10, verbose_name=_("Commercial Registration Number"),null=True,blank=True verbose_name=_("Commercial Registration Number")
) ,null=True
vrn = models.CharField(max_length=15, verbose_name=_("VAT Registration Number"),null=True,blank=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")) arabic_name = models.CharField(max_length=255, verbose_name=_("Arabic Name"))
name = models.CharField(max_length=255, verbose_name=_("English Name")) name = models.CharField(max_length=255, verbose_name=_("English Name"))
phone_number = PhoneNumberField(region="SA", verbose_name=_("Phone Number"),unique=True) phone_number = PhoneNumberField(region="SA", verbose_name=_("Phone Number"))
address = models.CharField( address = models.CharField(max_length=200,
max_length=200, blank=True, null=True, verbose_name=_("Address") blank=True,
) null=True,
logo = models.ImageField( verbose_name=_("Address"))
upload_to="logos/users", blank=True, null=True, verbose_name=_("Logo") 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) 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")) 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",
parent_dealer = models.ForeignKey( on_delete=models.SET_NULL,
"self", blank=True,
on_delete=models.SET_NULL, null=True,
blank=True, verbose_name=_("Parent Dealer"),
null=True, related_name="sub_dealers",)
verbose_name=_("Parent Dealer"), dealer_type = models.CharField(max_length=255,
related_name="sub_dealers", choices=DEALER_TYPES.choices,
) verbose_name=_("Dealer Type"),
dealer_type = models.CharField( default=DEALER_TYPES.OWNER,)
max_length=255, objects = DealerUserManager()
choices=DEALER_TYPES.choices,
verbose_name=_("Dealer Type"),
default=DEALER_TYPES.Owner,
)
@property @property
def get_active_plan(self): def get_active_plan(self):
try: try:
@ -549,9 +564,6 @@ class Dealer(models.Model, LocalizedNameMixin):
return subscription_plan return subscription_plan
return None return None
class Meta: class Meta:
verbose_name = _("Dealer") verbose_name = _("Dealer")
verbose_name_plural = _("Dealers") verbose_name_plural = _("Dealers")
@ -564,21 +576,47 @@ class Dealer(models.Model, LocalizedNameMixin):
@property @property
def get_sub_dealers(self): def get_sub_dealers(self):
if self.dealer_type == "Owner": if self.dealer_type == "OWNER":
return self.sub_dealers.all() return self.sub_dealers.all()
return None return None
@property @property
def is_parent(self): def is_parent(self):
return self.dealer_type == "Owner" return self.dealer_type == "OWNER"
@property @property
def get_root_dealer(self): def get_root_dealer(self):
return self.parent_dealer if self.parent_dealer else self return self.parent_dealer if self.parent_dealer else self
@receiver(post_save, sender=User) # @receiver(post_save, sender=User)
def create_dealer(instance, created, *args, **kwargs): # def create_dealer(instance, created, *args, **kwargs):
if created: # if created:
Dealer.objects.create(user=instance) # 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 # Vendor Model
@ -611,9 +649,9 @@ class Vendor(models.Model, LocalizedNameMixin):
# Customer Model # Customer Model
class Customer(models.Model): class Customer(models.Model):
dealer = models.ForeignKey( dealer = models.ForeignKey(Dealer,
Dealer, on_delete=models.CASCADE, related_name="customers" on_delete=models.CASCADE,
) related_name="customers")
first_name = models.CharField(max_length=50, verbose_name=_("First Name")) first_name = models.CharField(max_length=50, verbose_name=_("First Name"))
middle_name = models.CharField( middle_name = models.CharField(
max_length=50, blank=True, null=True, verbose_name=_("Middle Name") 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}" return f"{self.first_name} {self.middle_name} {self.last_name}"
class Organization(models.Model, LocalizedNameMixin): class Organization(models.Model, LocalizedNameMixin):
dealer = models.ForeignKey(Dealer, on_delete=models.CASCADE, related_name='organizations') dealer = models.ForeignKey(Dealer, on_delete=models.CASCADE, related_name='organizations')
name = models.CharField(max_length=255, verbose_name=_("Name")) name = models.CharField(max_length=255, verbose_name=_("Name"))

View File

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

View File

@ -3,13 +3,10 @@ from django import template
register = template.Library() register = template.Library()
@register.filter @register.filter(name='percentage')
def percentage(value): def percentage(value):
try: if value is not None:
value = float(value) * 100 return '{0:,.2f}%'.format(value * 100)
return f'{value:.0f}%'
except (ValueError, TypeError):
return value
@register.filter @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('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('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/', 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/', path('password/change/',
allauth_views.PasswordChangeView.as_view(template_name='account/password_change.html'), allauth_views.PasswordChangeView.as_view(template_name='account/password_change.html'),
name='account_change_password'), name='account_change_password'),
@ -27,11 +27,9 @@ urlpatterns = [
path('login/code/', allauth_views.RequestLoginCodeView.as_view(template_name='account/request_login_code.html')), path('login/code/', allauth_views.RequestLoginCodeView.as_view(template_name='account/request_login_code.html')),
# Dealer URLs # Dealer URLs
path('dealers/', views.DealerListView.as_view(), name='dealer_list'),
path('dealers/<int:pk>/', views.DealerDetailView.as_view(), name='dealer_detail'), 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>/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 # Customer URLs
path('customers/', views.CustomerListView.as_view(), name='customer_list'), 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 logging
import json import json
import datetime import datetime
from decimal import Decimal
from django_ledger.models import TransactionModel, AccountModel,JournalEntryModel from django_ledger.models import TransactionModel, AccountModel,JournalEntryModel
from django.shortcuts import HttpResponse 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.mixins import LoginRequiredMixin
from django.contrib.auth.decorators import login_required from django.contrib.auth.decorators import login_required
from django.http import JsonResponse from django.http import JsonResponse
@ -29,14 +22,11 @@ from django.views.generic import (
from django.utils import timezone, translation from django.utils import timezone, translation
from django.conf import settings from django.conf import settings
from urllib.parse import urlparse, urlunparse from urllib.parse import urlparse, urlunparse
from django.forms import ChoiceField, ModelForm, RadioSelect
from django.urls import reverse, reverse_lazy from django.urls import reverse, reverse_lazy
from django.contrib import messages from django.contrib import messages
from django.db.models import Sum, F, Count from django.db.models import Sum, F, Count
from django.db import transaction from django.db import transaction
from inventory.mixins import AddDealerInstanceMixin
from .services import elm, decodevin, get_make, get_model, normalize_name
from .services import ( from .services import (
elm, elm,
decodevin, decodevin,
@ -46,12 +36,9 @@ from .services import (
get_ledger_data, get_ledger_data,
) )
from . import models, forms from . import models, forms
from django_tables2.export.views import ExportMixin
from django.contrib.auth.mixins import PermissionRequiredMixin 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.messages.views import SuccessMessageMixin
from django.contrib.auth.models import Group from django.contrib.auth.models import Group
from django.contrib.auth import get_user_model
from .utils import get_calculations from .utils import get_calculations
from django.contrib.auth.models import User from django.contrib.auth.models import User
@ -94,64 +81,62 @@ def switch_language(request):
logger.warning(f"Invalid language code: {language}") logger.warning(f"Invalid language code: {language}")
return redirect("/") return redirect("/")
def SignupView(request):
def dealer_signup(request, *args, **kwargs):
if request.method == "POST": if request.method == "POST":
data = json.loads(request.body) data = json.loads(request.body)
wf1 = data.get("wizardValidationForm1") wf1 = data.get("wizardValidationForm1")
wf2 = data.get("wizardValidationForm2") wf2 = data.get("wizardValidationForm2")
wf3 = data.get("wizardValidationForm3") wf3 = data.get("wizardValidationForm3")
name = wf1.get("name") name = wf1.get("name")
arabic_name = wf1.get("arabic_name") arabic_name = wf1.get("arabic_name")
email = wf1.get("email") email = wf1.get("email")
phone = wf2.get("phone_number") phone = wf2.get("phone_number")
crn = wf2.get("crn") crn = wf2.get("crn")
vrn = wf2.get("vrn") vrn = wf2.get("vrn")
address = wf2.get("address") address = wf2.get("address")
password = wf3.get("password") password = wf3.get("password")
password_confirm = wf3.get("confirm_password") password_confirm = wf3.get("confirm_password")
if password != password_confirm: if password != password_confirm:
return JsonResponse({"error": "Passwords do not match."}, status=400) return JsonResponse({"error": "Passwords do not match."}, status=400)
try: try:
with transaction.atomic(): with transaction.atomic():
user = User.objects.create(username=name, email=email) user = User.objects.create(email=email, password=password)
user.set_password(password) user.set_password(password)
user.save() user.save()
models.Dealer.objects.create_or_update( models.Dealer.objects.create(user=user,
user=user, name=name,
name=name, arabic_name=arabic_name,
arabic_name=arabic_name, crn=crn,
crn=crn, vrn=vrn,
vrn=vrn, phone_number=phone,
email=email, address=address,
phone_number=phone, dealer_type="OWNER",)
address=address,
dealer_type="Owner",
)
print("User created successfully.")
return JsonResponse({"message": "User created successfully."}, status=200) return JsonResponse({"message": "User created successfully."}, status=200)
except Exception as e: except Exception as e:
print("Error creating user:", e)
return JsonResponse({"error": str(e)}, status=400) return JsonResponse({"error": str(e)}, status=400)
form1 = forms.WizardForm1() form1 = forms.WizardForm1()
form2 = forms.WizardForm2() form2 = forms.WizardForm2()
form3 = forms.WizardForm3() form3 = forms.WizardForm3()
return render(request, "account/signup-wizard.html",{"form1": form1, "form2": form2, "form3": form3}) return render(request, "account/signup-wizard.html",{"form1": form1, "form2": form2, "form3": form3})
class HomeView(LoginRequiredMixin, TemplateView): class HomeView(LoginRequiredMixin, TemplateView):
template_name = "dashboards/accounting.html" template_name = "dashboards/accounting.html"
def dispatch(self, request, *args, **kwargs): def dispatch(self, request, *args, **kwargs):
if ( if (
not any(hasattr(request.user, attr) for attr in ["dealer", "subdealer"]) not any(hasattr(request.user, attr) for attr in ["dealer", "subdealer"])
or not request.user.is_authenticated 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 redirect("welcome")
return super().dispatch(request, *args, **kwargs) return super().dispatch(request, *args, **kwargs)
def get_context_data(self, **kwargs): def get_context_data(self, **kwargs):
context = super().get_context_data(**kwargs) context = super().get_context_data(**kwargs)
@ -534,7 +519,7 @@ class CarLocationCreateView(CreateView):
def form_valid(self, form): def form_valid(self, form):
form.instance.car = get_object_or_404(models.Car, pk=self.kwargs["car_pk"]) 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() form.save()
messages.success(self.request, "Car saved successfully.") messages.success(self.request, "Car saved successfully.")
return super().form_valid(form) 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): class DealerDetailView(LoginRequiredMixin, DetailView):
model = models.Dealer model = models.Dealer
template_name = "dealers/dealer_detail.html" template_name = "dealers/dealer_detail.html"
context_object_name = "dealer" 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): class DealerUpdateView(LoginRequiredMixin, SuccessMessageMixin, UpdateView):
model = models.Dealer model = models.Dealer
form_class = forms.DealerForm form_class = forms.DealerForm
@ -658,19 +629,12 @@ class DealerUpdateView(LoginRequiredMixin, SuccessMessageMixin, UpdateView):
return form return form
def get_form_class(self): def get_form_class(self):
if self.request.user.dealer.dealer_type == "Owner": if self.request.user.dealer.dealer_type == "OWNER":
return forms.DealerForm return forms.DealerForm
else: else:
return forms.UserForm 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): class CustomerListView(LoginRequiredMixin, PermissionRequiredMixin, ListView):
model = models.Customer model = models.Customer
home_label = _("customers") home_label = _("customers")
@ -710,7 +674,6 @@ class CustomerCreateView(
LoginRequiredMixin, LoginRequiredMixin,
PermissionRequiredMixin, PermissionRequiredMixin,
SuccessMessageMixin, SuccessMessageMixin,
AddDealerInstanceMixin,
CreateView, CreateView,
): ):
model = models.Customer model = models.Customer
@ -725,7 +688,6 @@ class CustomerUpdateView(
LoginRequiredMixin, LoginRequiredMixin,
PermissionRequiredMixin, PermissionRequiredMixin,
SuccessMessageMixin, SuccessMessageMixin,
AddDealerInstanceMixin,
UpdateView, UpdateView,
): ):
model = models.Customer model = models.Customer
@ -762,7 +724,6 @@ class VendorCreateView(
LoginRequiredMixin, LoginRequiredMixin,
PermissionRequiredMixin, PermissionRequiredMixin,
SuccessMessageMixin, SuccessMessageMixin,
AddDealerInstanceMixin,
CreateView, CreateView,
): ):
model = models.Vendor model = models.Vendor
@ -777,7 +738,6 @@ class VendorUpdateView(
LoginRequiredMixin, LoginRequiredMixin,
PermissionRequiredMixin, PermissionRequiredMixin,
SuccessMessageMixin, SuccessMessageMixin,
AddDealerInstanceMixin,
UpdateView, UpdateView,
): ):
model = models.Vendor model = models.Vendor
@ -1168,7 +1128,6 @@ class UserCreateView(
LoginRequiredMixin, LoginRequiredMixin,
PermissionRequiredMixin, PermissionRequiredMixin,
SuccessMessageMixin, SuccessMessageMixin,
AddDealerInstanceMixin,
CreateView, CreateView,
): ):
model = models.Dealer model = models.Dealer
@ -1181,7 +1140,7 @@ class UserCreateView(
def get_form(self, form_class=None): def get_form(self, form_class=None):
form = super().get_form(form_class) form = super().get_form(form_class)
form.fields["dealer_type"].choices = [ 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 return form
@ -1211,7 +1170,6 @@ class UserUpdateView(
LoginRequiredMixin, LoginRequiredMixin,
PermissionRequiredMixin, PermissionRequiredMixin,
SuccessMessageMixin, SuccessMessageMixin,
AddDealerInstanceMixin,
UpdateView, UpdateView,
): ):
model = models.Dealer model = models.Dealer
@ -1398,7 +1356,8 @@ def download_quotation_pdf(request, quotation_id):
return response return response
except models.SaleQuotation.DoesNotExist: except models.SaleQuotation.DoesNotExist:
return HttpResponse("Quotation not found", status=404) return HttpResponse("Quotation not found", status=404)
@login_required @login_required
def invoice_detail(request,pk): def invoice_detail(request,pk):
quotation = get_object_or_404(models.SaleQuotation, pk=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() invoice = invoice_model.filter(customer=customer,date_draft=quotation.date_draft).first()
return redirect('quotation_detail', pk=pk) return redirect('quotation_detail', pk=pk)
@login_required @login_required
def payment_invoice(request,pk): def payment_invoice(request,pk):
quotation = get_object_or_404(models.SaleQuotation, pk=pk) quotation = get_object_or_404(models.SaleQuotation, pk=pk)
@ -1451,7 +1412,7 @@ def payment_create(request, pk):
insatnce = form.save() insatnce = form.save()
dealer = request.user.dealer.get_root_dealer 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() customer = entity.get_customers().filter(customer_name=quotation.customer.get_full_name).first()
coa_qs, coa_map = entity.get_all_coa_accounts() coa_qs, coa_map = entity.get_all_coa_accounts()
cash_account = coa_qs.first().get_coa_accounts().filter(name="Cash") 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; 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); 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; return config;
}));
//# sourceMappingURL=config.js.map //# sourceMappingURL=config.js.map
var phoenixIsRTL = window.config.config.phoenixIsRTL; var phoenixIsRTL = window.config.config.phoenixIsRTL;
if (phoenixIsRTL) { if (phoenixIsRTL) {
@ -127,3 +127,4 @@
linkRTL.setAttribute('disabled', true); linkRTL.setAttribute('disabled', true);
userLinkRTL.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: color:
getItemFromStore('phoenixTheme') === 'dark' getItemFromStore('phoenixTheme') === 'dark'
? getColor('primary') ? getColor('primary')
: getColor('primary-light') : getColor('primary')
}, },
data: profitData[0] data: profitData[0]
}, },
@ -772,7 +772,7 @@
color: color:
getItemFromStore('phoenixTheme') === 'dark' getItemFromStore('phoenixTheme') === 'dark'
? getColor('success') ? getColor('success')
: getColor('success-light') : getColor('success')
}, },
data: revenueData[0] data: revenueData[0]
}, },
@ -788,7 +788,7 @@
color: color:
getItemFromStore('phoenixTheme') === 'dark' getItemFromStore('phoenixTheme') === 'dark'
? getColor('info') ? getColor('info')
: getColor('info-light') : getColor('info')
}, },
data: expansesData[0] 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 title %}{{ _("Sign In") }}{% endblock title %}
{% block content %} {% block content %}
<div class="container"> <div class="container">
<div class="row flex-center min-vh-100 py-5"> <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="col-sm-10 col-md-8 col-lg-5 col-xl-5 col-xxl-3">
<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" /> <a class="d-flex flex-center text-decoration-none mb-4" href="{% url 'landing_page' %}">
</div> <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> </a>
<div class="text-center"> <div class="text-center">
<h3 class="text-body-highlight">{{ _("Sign In") }}</h3> <h3 class="text-body-highlight">{{ _("Sign In") }}</h3>
{% if SOCIALACCOUNT_ENABLED %} {% 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> </div>
{% include "socialaccount/snippets/login.html" with page_layout="entrance" %} {% include "socialaccount/snippets/login.html" with page_layout="entrance" %}
@ -34,7 +34,6 @@
</div> </div>
</div> </div>
</div> </div>
{% endblock content %} {% endblock content %}
{% block extra_body %} {% block extra_body %}

View File

@ -1,38 +1,34 @@
{% extends "base.html" %} {% extends "base.html" %}
{% load crispy_forms_filters %} {% load i18n static crispy_forms_filters %}
{% load i18n %}
{% block title %}{{ _("Change Password") }}{% endblock title %} {% block title %}{{ _("Change Password") }}{% endblock title %}
{% block content %} {% block content %}
<div class="container my-5"> <div class="container">
<div class="row justify-content-center"> <div class="row min-vh-100">
<div class="col-md-6"> <div class="col-sm-10 col-md-8 col-lg-5 col-xl-5 col-xxl-3">
<!-- Page Header --> <a class="d-flex flex-center text-decoration-none mb-4" href="{% url 'landing_page' %}">
<div class="text-center mb-4"> <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>
<h1 class="fw-bold">{{ _("Change Password") }}</h1> </a>
<p class="text-muted">{{ _("Ensure your account is using a strong, unique password.") }}</p> <div class="text-center">
</div> <h1 class="fw-bold">{{ _("Change Password") }}</h1>
<p class="text-muted">{{ _("Ensure your account is using a strong, unique password.") }}</p>
<!-- 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> </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> </div>
{% endblock %} {% endblock %}

View File

@ -5,12 +5,12 @@
{% block content %} {% block content %}
<div class="container"> <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="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 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> </div>
</a> </a>
<div class="text-center mb-6"> <div class="text-center">
<h4 class="text-body-highlight">{{ _("Password Reset") }}</h4> <h4 class="text-body-highlight">{{ _("Password Reset") }}</h4>
<p class="text-body-tertiary">{{ _("Type your new password") }}</p> <p class="text-body-tertiary">{{ _("Type your new password") }}</p>
<form method="post" action="{% url 'account_reset_password' %}" class="needs-validation" novalidate> <form method="post" action="{% url 'account_reset_password' %}" class="needs-validation" novalidate>
@ -28,9 +28,9 @@
</div> </div>
<button type="submit" class="btn btn-primary w-100 mb-3">{{ _("Reset My Password") }}</button> <button type="submit" class="btn btn-primary w-100 mb-3">{{ _("Reset My Password") }}</button>
</form> </form>
<p class="text-muted"> <small class="text-muted mt-4">
{{ _("Please contact us if you have any trouble resetting your password.") }} {{ _("Please contact us if you have any trouble resetting your password.") }}
</p> </small>
</div> </div>
</div> </div>
</div> </div>

View File

@ -1,23 +1,36 @@
{% extends "base.html" %} {% extends "base.html" %}
{% load crispy_forms_filters %} {% load crispy_forms_filters %}
{% load i18n %} {% load i18n static %}
{% block content %} {% block content %}
<div class="card theme-wizard mb-5" data-theme-wizard="data-theme-wizard"> <div class="container">
<div class="card-header bg-body-highlight pt-3 pb-2 border-bottom-0"> <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"> <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"> <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> </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"> <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> </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"> <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> </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"> <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> </a></li>
</ul> </ul>
</div> </div>
@ -41,12 +54,12 @@
<div class="tab-pane" role="tabpanel" aria-labelledby="bootstrap-wizard-validation-tab4" id="bootstrap-wizard-validation-tab4"> <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="row flex-center pb-8 pt-4 gx-3 gy-4">
<div class="col-12 col-sm-auto"> <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>
<div class="col-12 col-sm-auto"> <div class="col-12 col-sm-auto">
<div class="text-center text-sm-start"> <div class="text-center text-sm-start">
<h5 class="mb-3">You are all set!</h5> <h5 class="mb-3">{% trans '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> <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> </div>
</div> </div>
@ -55,11 +68,104 @@
</div> </div>
<div class="card-footer border-top-0" data-wizard-footer="data-wizard-footer"> <div class="card-footer border-top-0" data-wizard-footer="data-wizard-footer">
<div class="d-flex pager wizard list-inline mb-0"> <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"> <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>
</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 %} {% endblock content %}

View File

@ -6,7 +6,7 @@
{% block content %} {% block content %}
<div class="container"> <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="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 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> </div>
@ -69,7 +69,7 @@
<div class="form-check mb-3"> <div class="form-check mb-3">
<input class="form-check-input" id="termsService" type="checkbox" /> <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> </div>
<button type="submit" class="btn btn-primary w-100 mb-3">{{ _("Sign Up") }}</button> <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> <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="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."> <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> <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="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="32x32" href="{% static 'images/favicons/favicon-32x32.png' %}">
@ -30,8 +24,11 @@
<link rel="manifest" href="{% static 'images/favicons/manifest.json' %}"> <link rel="manifest" href="{% static 'images/favicons/manifest.json' %}">
<meta name="msapplication-TileImage" content="{% static 'images/logos/logo-d.png' %}"> <meta name="msapplication-TileImage" content="{% static 'images/logos/logo-d.png' %}">
<meta name="theme-color" content="#ffffff"> <meta name="theme-color" content="#ffffff">
<script src="{% static 'vendors/simplebar/simplebar.min.js' %}"></script> <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 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="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 '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"> <link rel="stylesheet" href="https://unicons.iconscout.com/release/v4.0.8/css/line.css">
{% if LANGUAGE_CODE == 'en' %} {% if LANGUAGE_CODE == 'en' %}
<link href="{% static 'css/theme.min.css' %}" type="text/css" rel="stylesheet" id="style-default"> <link href="{% static 'css/theme.min.css' %}" type="text/css" rel="stylesheet" id="style-default">
@ -57,8 +54,8 @@
</head> </head>
<body> <body>
{% include 'messages.html' %}
<main class="main" id="top"> <main class="main" id="top">
<nav class="navbar navbar-vertical navbar-expand-lg"> <nav class="navbar navbar-vertical navbar-expand-lg">
<div class="collapse navbar-collapse" id="navbarVerticalCollapse"> <div class="collapse navbar-collapse" id="navbarVerticalCollapse">
@ -103,9 +100,9 @@
</li> </li>
<hr class="my-0" /> <hr class="my-0" />
<li class="nav-item"> <li class="nav-item">
<!-- label--> <!-- label-->
<p class="navbar-vertical-label">Apps <p class="navbar-vertical-label">Apps</p>
</p>
<hr class="navbar-vertical-line" /> <hr class="navbar-vertical-line" />
<!-- parent pages--> <!-- 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"> <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> <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' %}"> <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">
{% if data_bs_theme == 'dark' %} <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" />
<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 %}
<h5 class="logo-text ms-2 d-none d-sm-block">{% trans 'Haikal' %}</h5> <h5 class="logo-text ms-2 d-none d-sm-block">{% trans 'Haikal' %}</h5>
</div> </div>
</div>
</a> </a>
</div> </div>
@ -472,11 +464,10 @@
</li> </li>
</ul> </ul>
</div>
</nav> </nav>
<div class="content"> <div class="content">
{% include 'messages.html' %}
{% block content %} {% block content %}
<!-- Main content goes here --> <!-- Main content goes here -->
{% endblock %} {% endblock %}
@ -493,146 +484,7 @@
</div> </div>
</footer> </footer>
</div> </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="support-chat-container">
<div class="container-fluid support-chat"> <div class="container-fluid support-chat">
@ -681,127 +533,11 @@
<!-- ===============================================--> <!-- ===============================================-->
<!-- End of Main Content--> <!-- 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> </script>
<!-- ===============================================--> <!-- ===============================================-->

File diff suppressed because it is too large Load Diff

View File

@ -4,10 +4,7 @@
{% block content %} {% block content %}
<div class="container"> <div class="container">
<!-- ============================================-->
<!-- <section> begin ============================-->
<section class="py-0"> <section class="py-0">
<div class="container-small"> <div class="container-small">
<div class="ecommerce-topbar"> <div class="ecommerce-topbar">
@ -16,15 +13,7 @@
<!-- end of .container--> <!-- end of .container-->
</section> </section>
<!-- <section> close ============================-->
<!-- ============================================-->
<!-- ============================================-->
<!-- <section> begin ============================-->
<section class="pt-5 pb-9"> <section class="pt-5 pb-9">
<div class="container-small"> <div class="container-small">
@ -37,7 +26,7 @@
<div class="row g-2 g-sm-3"> <div class="row g-2 g-sm-3">
<div class="col-auto"> <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>
<div class="col-auto"> <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> <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="d-flex justify-content-between">
<div class="mb-5 mb-md-0 mb-lg-5 me-3"> <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"> <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> </div>
<p class="fs-9 text-body-tertiary">{% trans 'Active until' %}: {{ dealer.get_active_plan.end_date|date}}</p> <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"> <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="row flex-1 justify-content-end">
<div class="col-sm-8 col-md-12"> <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"> <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"> <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">
<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> <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>
</ul> </div>
</div> </div>
</div> </div>
</div> </div>

View File

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

View File

@ -11,44 +11,40 @@
<meta charset="utf-8"> <meta charset="utf-8">
<meta http-equiv="X-UA-Compatible" content="IE=edge"> <meta http-equiv="X-UA-Compatible" content="IE=edge">
<meta name="viewport" content="width=device-width, initial-scale=1"> <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> <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="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="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="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="shortcut icon" type="image/x-icon" href="{% static 'images/favicons/favicon.ico' %}">
<link rel="manifest" href="{% static 'images/favicons/manifest.json' %}"> <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"> <meta name="theme-color" content="#ffffff">
<script src="{% static 'vendors/simplebar/simplebar.min.js' %}"></script> <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>
<!-- ===============================================--> <link href="{% static 'vendors/mapbox-gl/mapbox-gl.css' %}" rel="stylesheet">
<!-- Stylesheets--> <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.googleapis.com">
<link rel="preconnect" href="https://fonts.gstatic.com" crossorigin=""> <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="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 '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"> <link rel="stylesheet" href="https://unicons.iconscout.com/release/v4.0.8/css/line.css">
{% if LANGUAGE_CODE == 'ar' %} {% if LANGUAGE_CODE == 'en' %}
<link href="{% static 'css/theme-rtl.min.css' %}" type="text/css" rel="stylesheet" id="style-rtl"> <link href="{% static 'css/theme.min.css' %}" type="text/css" rel="stylesheet" id="style-default">
<link href="{% static 'css/user-rtl.min.css' %}" type="text/css" rel="stylesheet" id="user-style-rtl"> <link href="{% static 'css/user.min.css' %}" type="text/css" rel="stylesheet" id="user-style-default">
{% else %} {% else %}
<link href="{% static 'css/theme.min.css' %}" type="text/css" rel="stylesheet" id="style-default"> <link href="{% static 'css/theme-rtl.min.css' %}" type="text/css" rel="stylesheet" id="style-rtl">
<link href="{% static 'css/user.min.css' %}" type="text/css" rel="stylesheet" id="user-style-default"> <link href="{% static 'css/user-rtl.min.css' %}" type="text/css" rel="stylesheet" id="user-style-rtl">
{% endif %} {% endif %}
</head> </head>
@ -61,18 +57,14 @@
<div class="bg-body-emphasis sticky-top" data-navbar-shadow-on-scroll="data-navbar-shadow-on-scroll"> <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' %}"> <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">
<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> <h5 class="logo-text ms-2 d-none d-sm-block">{% trans 'Haikal' %}</h5>
</div> </div>
</div> </div>
</a> </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> <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="collapse navbar-collapse" id="navbarSupportedContent">
<div class="border-bottom border-translucent border-bottom-lg-0 mb-2"> <div class="border-bottom border-translucent border-bottom-lg-0 mb-2">
@ -1277,19 +1269,6 @@
</a> </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--> <!-- JavaScripts-->
<!-- ===============================================--> <!-- ===============================================-->
@ -1303,6 +1282,11 @@
<script src="{% static 'vendors/feather-icons/feather.min.js' %}"></script> <script src="{% static 'vendors/feather-icons/feather.min.js' %}"></script>
<script src="{% static 'vendors/dayjs/dayjs.min.js' %}"></script> <script src="{% static 'vendors/dayjs/dayjs.min.js' %}"></script>
<script src="{% static 'js/phoenix.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> </body>

View File

@ -58,28 +58,24 @@
</div> </div>
<!-- Specification Modal --> <!-- 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 fade" id="specificationsModal" tabindex="-1" aria-labelledby="specificationsModalLabel" aria-hidden="true">
<div class="modal-content glossy-modal"> <div class="modal-dialog modal-dialog-centered">
<div class="modal-header bg-primary text-light"> <div class="modal-content">
<h5 class="modal-title" id="specificationsModalLabel"> <div class="modal-header">
{% trans 'specifications'|upper %} <h5 class="modal-title" id="specificationsModalLabel">{% trans 'specifications'|upper %}</h5>
</h5> <button class="btn btn-close p-1" type="button" data-bs-dismiss="modal" aria-label="Close"></button>
<button type="button" class="btn-close" data-bs-dismiss="modal" aria-label="Close"></button> </div>
</div> <div class="modal-body">
<div class="modal-body"> <div id="specificationsContent"></div>
<div id="specificationsContent"></div> </div>
<div class="d-grid gap-2"> <div class="modal-footer">
<button type="button" <button class="btn btn-outline-primary" type="button" data-bs-dismiss="modal">{% trans 'Close' %}</button>
class="btn btn-sm btn-danger"
data-bs-dismiss="modal">
{% trans 'Close' %}
</button>
</div>
</div>
</div> </div>
</div> </div>
</div> </div>
</div>
<!-- Specification Modal -->
<!-- Main Container --> <!-- Main Container -->
<div class="d-flex flex-column min-vh-100"> <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> <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 --> <!-- Specification Modal -->
<div class="modal fade" id="specificationsModal" <div class="modal fade" id="specificationsModal"
@ -344,10 +344,7 @@
</div> </div>
<script> <script>
function getCookie(name) {
document.addEventListener("DOMContentLoaded", function() {
function getCookie(name) {
let cookieValue = null; let cookieValue = null;
if (document.cookie && document.cookie !== '') { if (document.cookie && document.cookie !== '') {
const cookies = document.cookie.split(';'); const cookies = document.cookie.split(';');
@ -362,7 +359,10 @@ document.addEventListener("DOMContentLoaded", function() {
return cookieValue; return cookieValue;
} }
const csrfToken = getCookie('csrftoken'); document.addEventListener("DOMContentLoaded", function() {
const csrfToken = getCookie('token');
const vinInput = document.getElementById('{{ form.vin.id_for_label }}'); const vinInput = document.getElementById('{{ form.vin.id_for_label }}');
const stockTypeSelect = document.getElementById('{{ form.stock_type.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)}) modelSelect.addEventListener("change", (e) => {loadSeries(e.target.value, yearSelect.value)})
decodeVinBtn.addEventListener('click', decodeVin); decodeVinBtn.addEventListener('click', decodeVin);
}); });
const Toast = Swal.mixin({ function showLoading() {
toast: true, Swal.fire({
position: "top-end", title: "{% trans 'Please Wait' %}",
showConfirmButton: false, text: "{% trans 'Loading' %}...",
timer: 3000, allowOutsideClick: false,
timerProgressBar: true, didOpen: () => {
didOpen: (toast) => { Swal.showLoading();
toast.onmouseenter = Swal.stopTimer; }
toast.onmouseleave = Swal.resumeTimer; });
} }
});
function notify(tag,msg){ function hideLoading() {
Toast.fire({ Swal.close();
}
function notify(tag,msg){
Swal.fire({
icon: tag, icon: tag,
titleText: msg titleText: msg
}); });
} }
</script> </script>
{% endblock %} {% endblock %}

View File

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

View File

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

View File

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