This commit is contained in:
Marwan Alwali 2025-01-22 14:47:01 +03:00
commit ca8e5a7190
30 changed files with 1870 additions and 991 deletions

Binary file not shown.

View File

BIN
db.sqlite3.backup Normal file

Binary file not shown.

View File

@ -1,4 +1,4 @@
# Generated by Django 5.1.4 on 2025-01-12 17:20 # Generated by Django 4.2.17 on 2025-01-21 13:59
from django.db import migrations, models from django.db import migrations, models

View File

@ -1,4 +1,4 @@
# Generated by Django 5.1.4 on 2025-01-12 17:20 # Generated by Django 4.2.17 on 2025-01-21 13:59
from django.db import migrations, models from django.db import migrations, models
import django.db.models.deletion import django.db.models.deletion
@ -9,8 +9,8 @@ class Migration(migrations.Migration):
initial = True initial = True
dependencies = [ dependencies = [
('inventory', '0001_initial'),
('haikalbot', '0001_initial'), ('haikalbot', '0001_initial'),
('inventory', '0001_initial'),
] ]
operations = [ operations = [

View File

@ -9,6 +9,7 @@ from phonenumber_field.phonenumber import PhoneNumber
from .mixins import AddClassMixin from .mixins import AddClassMixin
from django.forms.models import inlineformset_factory from django.forms.models import inlineformset_factory
from django_ledger.forms.invoice import InvoiceModelCreateForm as InvoiceModelCreateFormBase from django_ledger.forms.invoice import InvoiceModelCreateForm as InvoiceModelCreateFormBase
from django_ledger.forms.bill import BillModelCreateForm as BillModelCreateFormBase
from .models import ( from .models import (
Dealer, Dealer,
# Branch, # Branch,
@ -31,7 +32,7 @@ from .models import (
Staff, Staff,
Opportunity, Priority, Sources, Lead, Activity, Notes, CarModel Opportunity, Priority, Sources, Lead, Activity, Notes, CarModel
) )
from django_ledger.models import ItemModel, InvoiceModel from django_ledger.models import ItemModel, InvoiceModel,BillModel
from django.forms import ModelMultipleChoiceField, ValidationError, DateInput from django.forms import ModelMultipleChoiceField, ValidationError, DateInput
from django.utils.translation import gettext_lazy as _ from django.utils.translation import gettext_lazy as _
import django_tables2 as tables import django_tables2 as tables
@ -543,7 +544,10 @@ class ItemForm(forms.Form):
class PaymentForm(forms.Form): class PaymentForm(forms.Form):
invoice = forms.ModelChoiceField( invoice = forms.ModelChoiceField(
queryset=InvoiceModel.objects.all(), label="Invoice", required=True queryset=InvoiceModel.objects.all(), label="Invoice", required=False
)
bill = forms.ModelChoiceField(
queryset=BillModel.objects.all(), label="Bill", required=False
) )
amount = forms.DecimalField(label="Amount", required=True) amount = forms.DecimalField(label="Amount", required=True)
payment_method = forms.ChoiceField( payment_method = forms.ChoiceField(
@ -561,17 +565,19 @@ class PaymentForm(forms.Form):
def clean_amount(self): def clean_amount(self):
invoice = self.cleaned_data['invoice'] invoice = self.cleaned_data['invoice']
bill = self.cleaned_data['bill']
model = invoice if invoice else bill
amount = self.cleaned_data['amount'] amount = self.cleaned_data['amount']
if amount < invoice.amount_due: if amount + model.amount_paid > model.amount_due:
raise forms.ValidationError("Payment amount is greater than invoice amount due") raise forms.ValidationError("Payment amount is greater than amount due")
if amount <= 0: if amount <= 0:
raise forms.ValidationError("Payment amount must be greater than 0") raise forms.ValidationError("Payment amount must be greater than 0")
if invoice.amount_due == invoice.amount_paid or invoice.invoice_status == "paid": if model.is_paid():
raise forms.ValidationError("Invoice is already paid") raise forms.ValidationError("Invoice is already paid")
if amount > invoice.amount_due: if amount > model.amount_due:
raise forms.ValidationError("Payment amount is greater than invoice amount due") raise forms.ValidationError("Payment amount is greater than amount due")
return amount return amount
class EmailForm(forms.Form): class EmailForm(forms.Form):
@ -628,4 +634,13 @@ class InvoiceModelCreateForm(InvoiceModelCreateFormBase):
self.fields['cash_account'].widget = forms.HiddenInput() self.fields['cash_account'].widget = forms.HiddenInput()
self.fields['prepaid_account'].widget = forms.HiddenInput() self.fields['prepaid_account'].widget = forms.HiddenInput()
self.fields['unearned_account'].widget = forms.HiddenInput() self.fields['unearned_account'].widget = forms.HiddenInput()
self.fields['date_draft'] = forms.DateField(widget=DateInput(attrs={'type': 'date'})) self.fields['date_draft'] = forms.DateField(widget=DateInput(attrs={'type': 'date'}))
class BillModelCreateForm(BillModelCreateFormBase):
def __init__(self, *args, **kwargs):
super().__init__(*args, **kwargs)
self.fields['cash_account'].widget = forms.HiddenInput()
self.fields['prepaid_account'].widget = forms.HiddenInput()
self.fields['unearned_account'].widget = forms.HiddenInput()
self.fields['date_draft'] = forms.DateField(widget=DateInput(attrs={'type': 'date'}))

View File

@ -1,4 +1,4 @@
# Generated by Django 5.1.4 on 2025-01-12 17:20 # Generated by Django 4.2.17 on 2025-01-21 13:59
from decimal import Decimal from decimal import Decimal
from django.conf import settings from django.conf import settings
@ -15,6 +15,7 @@ class Migration(migrations.Migration):
dependencies = [ dependencies = [
('contenttypes', '0002_remove_content_type_name'), ('contenttypes', '0002_remove_content_type_name'),
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'),
] ]
@ -42,7 +43,7 @@ class Migration(migrations.Migration):
('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')), ('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
('vin', models.CharField(max_length=17, unique=True, verbose_name='VIN')), ('vin', models.CharField(max_length=17, unique=True, verbose_name='VIN')),
('year', models.IntegerField(verbose_name='Year')), ('year', models.IntegerField(verbose_name='Year')),
('status', models.CharField(choices=[('available', 'Available'), ('sold', 'Sold'), ('hold', 'Hold'), ('damaged', 'Damaged'), ('reserved', 'Reserved')], default='available', max_length=10, verbose_name='Status')), ('status', models.CharField(choices=[('available', 'Available'), ('sold', 'Sold'), ('hold', 'Hold'), ('damaged', 'Damaged'), ('reserved', 'Reserved'), ('transfer', 'Transfer')], default='available', max_length=10, verbose_name='Status')),
('stock_type', models.CharField(choices=[('new', 'New'), ('used', 'Used')], default='new', max_length=10, verbose_name='Stock Type')), ('stock_type', models.CharField(choices=[('new', 'New'), ('used', 'Used')], default='new', max_length=10, verbose_name='Stock Type')),
('remarks', models.TextField(blank=True, null=True, verbose_name='Remarks')), ('remarks', models.TextField(blank=True, null=True, verbose_name='Remarks')),
('mileage', models.IntegerField(blank=True, null=True, verbose_name='Mileage')), ('mileage', models.IntegerField(blank=True, null=True, verbose_name='Mileage')),
@ -74,138 +75,13 @@ class Migration(migrations.Migration):
('arabic_name', models.CharField(blank=True, max_length=255, null=True)), ('arabic_name', models.CharField(blank=True, max_length=255, null=True)),
('logo', models.ImageField(blank=True, null=True, upload_to='car_make', verbose_name='logo')), ('logo', models.ImageField(blank=True, null=True, upload_to='car_make', verbose_name='logo')),
('is_sa_import', models.BooleanField(default=False)), ('is_sa_import', models.BooleanField(default=False)),
('car_type', models.SmallIntegerField(choices=[(1, 'Car'), (2, 'Light Commercial'), (3, 'Heavy-Duty Tractors'), (4, 'Trailers'), (5, 'Medium Trucks'), (6, 'Buses'), (20, 'Motorcycles'), (21, 'Buggy'), (22, 'Moto ATV'), (23, 'Scooters'), (24, 'Karting'), (25, 'ATV'), (26, 'Snowmobiles')])), ('car_type', models.SmallIntegerField(blank=True, choices=[(1, 'Car'), (2, 'Light Commercial'), (3, 'Heavy-Duty Tractors'), (4, 'Trailers'), (5, 'Medium Trucks'), (6, 'Buses'), (20, 'Motorcycles'), (21, 'Buggy'), (22, 'Moto ATV'), (23, 'Scooters'), (24, 'Karting'), (25, 'ATV'), (26, 'Snowmobiles')], null=True)),
], ],
options={ options={
'verbose_name': 'Make', 'verbose_name': 'Make',
}, },
bases=(models.Model, inventory.mixins.LocalizedNameMixin), bases=(models.Model, inventory.mixins.LocalizedNameMixin),
), ),
migrations.CreateModel(
name='ExteriorColors',
fields=[
('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
('name', models.CharField(max_length=255, verbose_name='Name')),
('arabic_name', models.CharField(max_length=255, verbose_name='Arabic Name')),
('rgb', models.CharField(blank=True, max_length=24, null=True, verbose_name='RGB')),
],
options={
'verbose_name': 'Exterior Colors',
'verbose_name_plural': 'Exterior Colors',
},
bases=(models.Model, inventory.mixins.LocalizedNameMixin),
),
migrations.CreateModel(
name='InteriorColors',
fields=[
('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
('name', models.CharField(max_length=255, verbose_name='Name')),
('arabic_name', models.CharField(max_length=255, verbose_name='Arabic Name')),
('rgb', models.CharField(blank=True, max_length=24, null=True, verbose_name='RGB')),
],
options={
'verbose_name': 'Interior Colors',
'verbose_name_plural': 'Interior Colors',
},
bases=(models.Model, inventory.mixins.LocalizedNameMixin),
),
migrations.CreateModel(
name='Payment',
fields=[
('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
('amount', models.DecimalField(decimal_places=2, max_digits=10, verbose_name='amount')),
('payment_method', models.CharField(choices=[('cash', 'cash'), ('credit', 'credit'), ('transfer', 'transfer'), ('debit', 'debit'), ('SADAD', 'SADAD')], max_length=50, verbose_name='method')),
('reference_number', models.CharField(blank=True, max_length=100, null=True, verbose_name='reference number')),
('payment_date', models.DateField(auto_now_add=True, verbose_name='date')),
],
options={
'verbose_name': 'payment',
'verbose_name_plural': 'payments',
},
),
migrations.CreateModel(
name='SubscriptionPlan',
fields=[
('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
('name', models.CharField(help_text='Name of the subscription plan', max_length=100, unique=True)),
('description', models.TextField()),
('price', models.DecimalField(decimal_places=2, max_digits=10)),
('max_users', models.PositiveIntegerField(default=1, help_text='Maximum number of users allowed')),
('max_inventory_size', models.PositiveIntegerField(default=50, help_text='Maximum number of cars in inventory')),
('support_level', models.CharField(choices=[('basic', 'Basic Support'), ('priority', 'Priority Support'), ('dedicated', 'Dedicated Support')], default='basic', help_text='Level of support provided', max_length=50)),
('custom_features', models.JSONField(blank=True, help_text='Additional features specific to this plan', null=True)),
('created_at', models.DateTimeField(auto_now_add=True)),
('updated_at', models.DateTimeField(auto_now=True)),
],
options={
'verbose_name': 'Subscription Plan',
'verbose_name_plural': 'Subscription Plans',
},
),
migrations.CreateModel(
name='VatRate',
fields=[
('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
('rate', models.DecimalField(decimal_places=2, default=Decimal('0.15'), max_digits=5)),
('is_active', models.BooleanField(default=True)),
('created_at', models.DateTimeField(auto_now_add=True)),
],
),
migrations.CreateModel(
name='Activity',
fields=[
('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
('object_id', models.PositiveIntegerField()),
('activity_type', models.CharField(choices=[('call', 'Call'), ('sms', 'SMS'), ('email', 'Email'), ('whatsapp', 'WhatsApp'), ('visit', 'Visit'), ('add_car', 'Add Car'), ('reserve_car', 'Reserve Car'), ('remove_car', 'Remove Car'), ('create_quotation', 'Create Quotation'), ('cancel_quotation', 'Cancel Quotation'), ('create_order', 'Create Order'), ('cancel_order', 'Cancel Order'), ('create_invoice', 'Create Invoice'), ('cancel_invoice', 'Cancel Invoice')], max_length=50, verbose_name='Activity Type')),
('notes', models.TextField(blank=True, null=True, verbose_name='Notes')),
('created', models.DateTimeField(auto_now_add=True, verbose_name='Created')),
('updated', models.DateTimeField(auto_now=True, verbose_name='Updated')),
('content_type', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to='contenttypes.contenttype')),
('created_by', models.ForeignKey(on_delete=django.db.models.deletion.DO_NOTHING, related_name='activities_created', to=settings.AUTH_USER_MODEL)),
],
options={
'verbose_name': 'Activity',
'verbose_name_plural': 'Activities',
},
),
migrations.CreateModel(
name='AdditionalServices',
fields=[
('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
('name', models.CharField(max_length=255, verbose_name='Name')),
('arabic_name', models.CharField(max_length=255, verbose_name='Arabic Name')),
('description', models.TextField(verbose_name='Description')),
('price', models.DecimalField(decimal_places=2, max_digits=14, verbose_name='Price')),
('taxable', models.BooleanField(default=False, verbose_name='taxable')),
('uom', models.CharField(choices=[('EA', 'Each'), ('PR', 'Pair'), ('SET', 'Set'), ('GAL', 'Gallon'), ('L', 'Liter'), ('M', 'Meter'), ('KG', 'Kilogram'), ('HR', 'Hour'), ('BX', 'Box'), ('RL', 'Roll'), ('PKG', 'Package'), ('DZ', 'Dozen'), ('SQ_M', 'Square Meter'), ('PC', 'Piece'), ('BDL', 'Bundle')], max_length=10, verbose_name='Unit of Measurement')),
('item', models.OneToOneField(blank=True, null=True, on_delete=django.db.models.deletion.CASCADE, to='django_ledger.itemmodel', verbose_name='Item')),
],
options={
'verbose_name': 'Additional Services',
'verbose_name_plural': 'Additional Services',
},
bases=(models.Model, inventory.mixins.LocalizedNameMixin),
),
migrations.CreateModel(
name='CarFinance',
fields=[
('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
('cost_price', models.DecimalField(decimal_places=2, max_digits=14, verbose_name='Cost Price')),
('selling_price', models.DecimalField(decimal_places=2, max_digits=14, verbose_name='Selling Price')),
('discount_amount', models.DecimalField(decimal_places=2, default=Decimal('0.00'), max_digits=14, verbose_name='Discount Amount')),
('additional_services', models.ManyToManyField(blank=True, related_name='additional_finances', to='inventory.additionalservices')),
('car', models.OneToOneField(on_delete=django.db.models.deletion.CASCADE, related_name='finances', to='inventory.car')),
],
options={
'verbose_name': 'Car Financial Details',
'verbose_name_plural': 'Car Financial Details',
},
),
migrations.AddField(
model_name='car',
name='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( migrations.CreateModel(
name='CarModel', name='CarModel',
fields=[ fields=[
@ -274,7 +150,6 @@ class Migration(migrations.Migration):
('email', models.EmailField(max_length=254, unique=True, verbose_name='Email')), ('email', models.EmailField(max_length=254, unique=True, verbose_name='Email')),
('national_id', models.CharField(max_length=10, unique=True, verbose_name='National ID')), ('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')), ('phone_number', phonenumber_field.modelfields.PhoneNumberField(max_length=128, region='SA', unique=True, verbose_name='Phone Number')),
('city', models.CharField(blank=True, max_length=255, verbose_name='City')),
('address', models.CharField(blank=True, max_length=200, null=True, verbose_name='Address')), ('address', models.CharField(blank=True, max_length=200, null=True, verbose_name='Address')),
('created', models.DateTimeField(auto_now_add=True, verbose_name='Created')), ('created', models.DateTimeField(auto_now_add=True, verbose_name='Created')),
('updated', models.DateTimeField(auto_now=True, verbose_name='Updated')), ('updated', models.DateTimeField(auto_now=True, verbose_name='Updated')),
@ -310,51 +185,46 @@ class Migration(migrations.Migration):
], ],
), ),
migrations.CreateModel( migrations.CreateModel(
name='CarLocation', name='ExteriorColors',
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')),
('description', models.TextField(blank=True, help_text='Optional description about the showroom placement.', null=True, verbose_name='Description')), ('name', models.CharField(max_length=255, verbose_name='Name')),
('created_at', models.DateTimeField(auto_now_add=True, verbose_name='Created At')), ('arabic_name', models.CharField(max_length=255, verbose_name='Arabic Name')),
('updated_at', models.DateTimeField(auto_now=True, verbose_name='Last Updated')), ('rgb', models.CharField(blank=True, max_length=24, null=True, verbose_name='RGB')),
('car', models.OneToOneField(on_delete=django.db.models.deletion.CASCADE, related_name='location', to='inventory.car', verbose_name='Car')),
('owner', models.ForeignKey(help_text='Dealer who owns the car.', on_delete=django.db.models.deletion.CASCADE, related_name='owned_cars', to='inventory.dealer', verbose_name='Owner')),
('showroom', models.ForeignKey(help_text='Dealer where the car is displayed (can be the owner).', on_delete=django.db.models.deletion.CASCADE, related_name='showroom_cars', to='inventory.dealer', verbose_name='Showroom')),
], ],
options={ options={
'verbose_name': 'Car Location', 'verbose_name': 'Exterior Colors',
'verbose_name_plural': 'Car Locations', 'verbose_name_plural': 'Exterior Colors',
}, },
bases=(models.Model, inventory.mixins.LocalizedNameMixin),
), ),
migrations.AddField( migrations.CreateModel(
model_name='car', name='InteriorColors',
name='dealer', fields=[
field=models.ForeignKey(on_delete=django.db.models.deletion.DO_NOTHING, related_name='cars', to='inventory.dealer', verbose_name='Dealer'), ('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
), ('name', models.CharField(max_length=255, verbose_name='Name')),
migrations.AddField( ('arabic_name', models.CharField(max_length=255, verbose_name='Arabic Name')),
model_name='additionalservices', ('rgb', models.CharField(blank=True, max_length=24, null=True, verbose_name='RGB')),
name='dealer', ],
field=models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to='inventory.dealer', verbose_name='Dealer'), options={
'verbose_name': 'Interior Colors',
'verbose_name_plural': 'Interior Colors',
},
bases=(models.Model, inventory.mixins.LocalizedNameMixin),
), ),
migrations.CreateModel( migrations.CreateModel(
name='Lead', name='Lead',
fields=[ fields=[
('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')), ('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
('title', models.CharField(choices=[('mr', 'Mr'), ('mrs', 'Mrs'), ('ms', 'Ms'), ('miss', 'Miss'), ('dr', 'Dr'), ('prof', 'Prof'), ('prince', 'Prince'), ('princess', 'Princess'), ('company', 'Company'), ('na', 'N/A')], max_length=20, verbose_name='Title')),
('first_name', models.CharField(max_length=50, verbose_name='First Name')),
('last_name', models.CharField(max_length=50, verbose_name='Last Name')),
('email', models.EmailField(db_index=True, max_length=254, unique=True, verbose_name='Email')),
('phone_number', phonenumber_field.modelfields.PhoneNumberField(max_length=128, region='SA', verbose_name='Phone Number')),
('salary', models.PositiveIntegerField(blank=True, null=True, verbose_name='Salary')),
('obligations', models.PositiveIntegerField(blank=True, null=True, verbose_name='Obligations')),
('year', models.PositiveSmallIntegerField(blank=True, null=True, verbose_name='Year')), ('year', models.PositiveSmallIntegerField(blank=True, null=True, verbose_name='Year')),
('source', models.CharField(choices=[('referrals', 'Referrals'), ('whatsapp', 'WhatsApp'), ('showroom', 'Showroom'), ('tiktok', 'TikTok'), ('instagram', 'Instagram'), ('x', 'X'), ('facebook', 'Facebook'), ('motory', 'Motory'), ('influencers', 'Influencers'), ('youtube', 'Youtube'), ('campaign', 'Campaign')], max_length=50, verbose_name='Source')), ('source', models.CharField(choices=[('referrals', 'Referrals'), ('whatsapp', 'WhatsApp'), ('showroom', 'Showroom'), ('tiktok', 'TikTok'), ('instagram', 'Instagram'), ('x', 'X'), ('facebook', 'Facebook'), ('motory', 'Motory'), ('influencers', 'Influencers'), ('youtube', 'Youtube'), ('campaign', 'Campaign')], max_length=50, verbose_name='Source')),
('channel', models.CharField(choices=[('walk_in', 'Walk In'), ('toll_free', 'Toll Free'), ('website', 'Website'), ('email', 'Email'), ('form', 'Form')], max_length=50, verbose_name='Channel')), ('channel', models.CharField(choices=[('walk_in', 'Walk In'), ('toll_free', 'Toll Free'), ('website', 'Website'), ('email', 'Email'), ('form', 'Form')], max_length=50, verbose_name='Channel')),
('address', models.CharField(blank=True, max_length=200, null=True, verbose_name='Address')),
('city', models.CharField(max_length=50, verbose_name='City')), ('city', models.CharField(max_length=50, verbose_name='City')),
('priority', models.CharField(choices=[('low', 'Low'), ('medium', 'Medium'), ('high', 'High')], default='medium', max_length=10, verbose_name='Priority')), ('priority', models.CharField(choices=[('low', 'Low'), ('medium', 'Medium'), ('high', 'High')], default='medium', max_length=10, verbose_name='Priority')),
('status', models.CharField(choices=[('new', 'New'), ('pending', 'Pending'), ('in_progress', 'In Progress'), ('qualified', 'Qualified'), ('canceled', 'Canceled')], db_index=True, default='new', max_length=50, verbose_name='Status')), ('status', models.CharField(choices=[('new', 'New'), ('pending', 'Pending'), ('in_progress', 'In Progress'), ('qualified', 'Qualified'), ('canceled', 'Canceled')], db_index=True, default='new', max_length=50, verbose_name='Status')),
('created', models.DateTimeField(auto_now_add=True, db_index=True, verbose_name='Created')), ('created', models.DateTimeField(auto_now_add=True, db_index=True, verbose_name='Created')),
('updated', models.DateTimeField(auto_now=True, verbose_name='Updated')), ('updated', models.DateTimeField(auto_now=True, verbose_name='Updated')),
('customer', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, related_name='leads', to='inventory.customer')),
('dealer', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, related_name='leads', to='inventory.dealer')), ('dealer', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, related_name='leads', to='inventory.dealer')),
('id_car_make', models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.DO_NOTHING, to='inventory.carmake', verbose_name='Make')), ('id_car_make', models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.DO_NOTHING, to='inventory.carmake', verbose_name='Make')),
('id_car_model', models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.DO_NOTHING, to='inventory.carmodel', verbose_name='Model')), ('id_car_model', models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.DO_NOTHING, to='inventory.carmodel', verbose_name='Model')),
@ -364,62 +234,6 @@ class Migration(migrations.Migration):
'verbose_name_plural': 'Leads', 'verbose_name_plural': 'Leads',
}, },
), ),
migrations.CreateModel(
name='Customer',
fields=[
('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
('title', models.CharField(choices=[('mr', 'Mr'), ('mrs', 'Mrs'), ('ms', 'Ms'), ('miss', 'Miss'), ('dr', 'Dr'), ('prof', 'Prof'), ('prince', 'Prince'), ('princess', 'Princess'), ('company', 'Company'), ('na', 'N/A')], default='na', max_length=10, verbose_name='Title')),
('first_name', models.CharField(max_length=50, verbose_name='First Name')),
('middle_name', models.CharField(blank=True, max_length=50, null=True, verbose_name='Middle Name')),
('last_name', models.CharField(max_length=50, verbose_name='Last Name')),
('gender', models.CharField(choices=[('m', 'Male'), ('f', 'Female')], max_length=1, verbose_name='Gender')),
('dob', models.DateField(verbose_name='Date of Birth')),
('email', models.EmailField(max_length=254, unique=True, verbose_name='Email')),
('national_id', models.CharField(max_length=10, unique=True, verbose_name='National ID')),
('phone_number', phonenumber_field.modelfields.PhoneNumberField(max_length=128, region='SA', unique=True, verbose_name='Phone Number')),
('city', models.CharField(blank=True, max_length=255, verbose_name='City')),
('address', models.CharField(blank=True, max_length=200, null=True, verbose_name='Address')),
('created', models.DateTimeField(auto_now_add=True, verbose_name='Created')),
('updated', models.DateTimeField(auto_now=True, verbose_name='Updated')),
('dealer', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, related_name='customers', to='inventory.dealer')),
('lead', models.OneToOneField(blank=True, null=True, on_delete=django.db.models.deletion.SET_NULL, related_name='converted', to='inventory.lead', verbose_name='Lead')),
],
options={
'verbose_name': 'Customer',
'verbose_name_plural': 'Customers',
},
),
migrations.CreateModel(
name='Notes',
fields=[
('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
('object_id', models.PositiveIntegerField()),
('note', models.TextField(verbose_name='Note')),
('created', models.DateTimeField(auto_now_add=True, verbose_name='Created')),
('updated', models.DateTimeField(auto_now=True, verbose_name='Updated')),
('content_type', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to='contenttypes.contenttype')),
('created_by', models.ForeignKey(on_delete=django.db.models.deletion.DO_NOTHING, related_name='notes_created', to=settings.AUTH_USER_MODEL)),
],
options={
'verbose_name': 'Note',
'verbose_name_plural': 'Notes',
},
),
migrations.CreateModel(
name='Notification',
fields=[
('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
('message', models.CharField(max_length=255, verbose_name='Message')),
('is_read', models.BooleanField(default=False, verbose_name='Is Read')),
('created', models.DateTimeField(auto_now_add=True, verbose_name='Created')),
('user', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, related_name='notifications', to=settings.AUTH_USER_MODEL)),
],
options={
'verbose_name': 'Notification',
'verbose_name_plural': 'Notifications',
'ordering': ['-created'],
},
),
migrations.CreateModel( migrations.CreateModel(
name='Organization', name='Organization',
fields=[ fields=[
@ -431,7 +245,8 @@ class Migration(migrations.Migration):
('phone_number', phonenumber_field.modelfields.PhoneNumberField(max_length=128, region='SA', verbose_name='Phone Number')), ('phone_number', phonenumber_field.modelfields.PhoneNumberField(max_length=128, region='SA', verbose_name='Phone Number')),
('address', models.CharField(blank=True, max_length=200, null=True, verbose_name='Address')), ('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')), ('logo', models.ImageField(blank=True, null=True, upload_to='logos', verbose_name='Logo')),
('created_at', models.DateTimeField(auto_now_add=True, verbose_name='Created At')), ('created', models.DateTimeField(auto_now_add=True, verbose_name='Created')),
('updated', models.DateTimeField(auto_now=True, verbose_name='Updated')),
('dealer', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, related_name='organizations', to='inventory.dealer')), ('dealer', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, related_name='organizations', to='inventory.dealer')),
], ],
options={ options={
@ -478,98 +293,6 @@ class Migration(migrations.Migration):
('dealer', models.ForeignKey(null=True, on_delete=django.db.models.deletion.CASCADE, related_name='sales', to='inventory.dealer')), ('dealer', models.ForeignKey(null=True, on_delete=django.db.models.deletion.CASCADE, related_name='sales', to='inventory.dealer')),
], ],
), ),
migrations.AddField(
model_name='payment',
name='quotation',
field=models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, related_name='payments', to='inventory.salequotation'),
),
migrations.CreateModel(
name='SaleQuotationCar',
fields=[
('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
('quantity', models.PositiveIntegerField(default=1, verbose_name='Quantity')),
('car', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to='inventory.car', verbose_name='Car')),
('quotation', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, related_name='quotation_cars', to='inventory.salequotation', verbose_name='Quotation')),
],
),
migrations.CreateModel(
name='SalesOrder',
fields=[
('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
('created_at', models.DateTimeField(auto_now_add=True, verbose_name='Created At')),
('total_amount', models.DecimalField(decimal_places=2, max_digits=14, verbose_name='Total Amount')),
('quotation', models.OneToOneField(on_delete=django.db.models.deletion.CASCADE, related_name='sales_order', to='inventory.salequotation', verbose_name='Quotation')),
],
),
migrations.CreateModel(
name='Staff',
fields=[
('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
('name', models.CharField(max_length=255, verbose_name='Name')),
('arabic_name', models.CharField(max_length=255, verbose_name='Arabic Name')),
('phone_number', phonenumber_field.modelfields.PhoneNumberField(max_length=128, region='SA', verbose_name='Phone Number')),
('staff_type', models.CharField(choices=[('manager', 'Manager'), ('inventory', 'Inventory'), ('accountant', 'Accountant'), ('sales', 'Sales'), ('coordinator', 'Coordinator'), ('receptionist', 'Receptionist'), ('agent', 'Agent')], max_length=255, verbose_name='Staff Type')),
('created', models.DateTimeField(auto_now_add=True, verbose_name='Created')),
('updated', models.DateTimeField(auto_now=True, verbose_name='Updated')),
('dealer', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, related_name='staff', to='inventory.dealer')),
('user', models.OneToOneField(on_delete=django.db.models.deletion.CASCADE, related_name='staff', to=settings.AUTH_USER_MODEL)),
],
options={
'verbose_name': 'Staff',
'verbose_name_plural': 'Staff',
'permissions': [],
},
bases=(models.Model, inventory.mixins.LocalizedNameMixin),
managers=[
('objects', inventory.models.StaffUserManager()),
],
),
migrations.CreateModel(
name='Opportunity',
fields=[
('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
('stage', models.CharField(choices=[('prospect', 'Prospect'), ('proposal', 'Proposal'), ('negotiation', 'Negotiation'), ('closed_won', 'Closed Won'), ('closed_lost', 'Closed Lost')], max_length=20, verbose_name='Stage')),
('status', models.CharField(choices=[('new', 'New'), ('pending', 'Pending'), ('in_progress', 'In Progress'), ('qualified', 'Qualified'), ('canceled', 'Canceled')], default='new', max_length=20, verbose_name='Status')),
('probability', models.PositiveIntegerField(validators=[inventory.models.validate_probability])),
('closing_date', models.DateField(verbose_name='Closing Date')),
('created', models.DateTimeField(auto_now_add=True, verbose_name='Created')),
('updated', models.DateTimeField(auto_now=True, verbose_name='Updated')),
('closed', models.BooleanField(default=False, verbose_name='Closed')),
('car', models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.SET_NULL, to='inventory.car', verbose_name='Car')),
('customer', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, related_name='opportunities', to='inventory.customer')),
('dealer', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, related_name='opportunities', to='inventory.dealer')),
('staff', models.ForeignKey(null=True, on_delete=django.db.models.deletion.SET_NULL, related_name='owner', to='inventory.staff', verbose_name='Owner')),
],
options={
'verbose_name': 'Opportunity',
'verbose_name_plural': 'Opportunities',
},
),
migrations.CreateModel(
name='LeadStatusHistory',
fields=[
('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
('old_status', models.CharField(choices=[('new', 'New'), ('pending', 'Pending'), ('in_progress', 'In Progress'), ('qualified', 'Qualified'), ('canceled', 'Canceled')], max_length=50, verbose_name='Old Status')),
('new_status', models.CharField(choices=[('new', 'New'), ('pending', 'Pending'), ('in_progress', 'In Progress'), ('qualified', 'Qualified'), ('canceled', 'Canceled')], max_length=50, verbose_name='New Status')),
('changed_at', models.DateTimeField(auto_now_add=True, verbose_name='Changed At')),
('lead', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, related_name='status_history', to='inventory.lead')),
('changed_by', models.ForeignKey(on_delete=django.db.models.deletion.DO_NOTHING, related_name='status_changes', to='inventory.staff')),
],
options={
'verbose_name': 'Lead Status History',
'verbose_name_plural': 'Lead Status Histories',
},
),
migrations.AddField(
model_name='lead',
name='assigned',
field=models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.SET_NULL, related_name='assigned', to='inventory.staff', verbose_name='Assigned'),
),
migrations.AddField(
model_name='customer',
name='staff',
field=models.ForeignKey(null=True, on_delete=django.db.models.deletion.SET_NULL, related_name='customer_staff', to='inventory.staff', verbose_name='Staff'),
),
migrations.CreateModel( migrations.CreateModel(
name='Subscription', name='Subscription',
fields=[ fields=[
@ -719,7 +442,7 @@ class Migration(migrations.Migration):
('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')), ('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
('name', models.CharField(max_length=255, verbose_name='Name')), ('name', models.CharField(max_length=255, verbose_name='Name')),
('arabic_name', models.CharField(max_length=255, verbose_name='Arabic Name')), ('arabic_name', models.CharField(max_length=255, verbose_name='Arabic Name')),
('id_number', models.CharField(max_length=10, verbose_name='ID Number')), ('id_number', models.CharField(max_length=10, unique=True, verbose_name='ID Number')),
('phone_number', phonenumber_field.modelfields.PhoneNumberField(max_length=128, region='SA', verbose_name='Phone Number')), ('phone_number', phonenumber_field.modelfields.PhoneNumberField(max_length=128, region='SA', verbose_name='Phone Number')),
('email', models.EmailField(max_length=255, verbose_name='Email Address')), ('email', models.EmailField(max_length=255, verbose_name='Email Address')),
('address', models.CharField(blank=True, max_length=200, null=True, verbose_name='Address')), ('address', models.CharField(blank=True, max_length=200, null=True, verbose_name='Address')),
@ -820,39 +543,14 @@ class Migration(migrations.Migration):
), ),
migrations.AddField( migrations.AddField(
model_name='lead', model_name='lead',
name='assigned', name='staff',
field=models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.SET_NULL, related_name='assigned', to='inventory.staff', verbose_name='Assigned'), field=models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.SET_NULL, related_name='assigned', to='inventory.staff', verbose_name='Assigned'),
), ),
migrations.AddField(
model_name='lead',
name='dealer',
field=models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, related_name='leads', to='inventory.dealer'),
),
migrations.AddField(
model_name='lead',
name='id_car_make',
field=models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.DO_NOTHING, to='inventory.carmake', verbose_name='Make'),
),
migrations.AddField(
model_name='lead',
name='id_car_model',
field=models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.DO_NOTHING, to='inventory.carmodel', verbose_name='Model'),
),
migrations.AddField( migrations.AddField(
model_name='customer', model_name='customer',
name='dealer', name='dealer',
field=models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, related_name='customers', to='inventory.dealer'), field=models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, related_name='customers', to='inventory.dealer'),
), ),
migrations.AddField(
model_name='customer',
name='lead',
field=models.OneToOneField(blank=True, null=True, on_delete=django.db.models.deletion.SET_NULL, related_name='converted', to='inventory.lead', verbose_name='Lead'),
),
migrations.AddField(
model_name='customer',
name='staff',
field=models.ForeignKey(null=True, on_delete=django.db.models.deletion.SET_NULL, related_name='customer_staff', to='inventory.staff', verbose_name='Staff'),
),
migrations.CreateModel( migrations.CreateModel(
name='CustomCard', name='CustomCard',
fields=[ fields=[
@ -881,6 +579,27 @@ class Migration(migrations.Migration):
}, },
bases=(models.Model, inventory.mixins.LocalizedNameMixin), bases=(models.Model, inventory.mixins.LocalizedNameMixin),
), ),
migrations.CreateModel(
name='CarTransfer',
fields=[
('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
('transfer_date', models.DateTimeField(auto_now_add=True, verbose_name='Transfer Date')),
('quantity', models.IntegerField(default=1, verbose_name='Quantity')),
('remarks', models.TextField(blank=True, null=True, verbose_name='Remarks')),
('status', models.CharField(default='draft', max_length=10, verbose_name=[('draft', 'Draft'), ('approved', 'Approved'), ('pending', 'Pending'), ('accepted', 'Accepted'), ('success', 'Success'), ('reject', 'Reject')])),
('is_approved', models.BooleanField(default=False)),
('active', models.BooleanField(default=True)),
('created_at', models.DateTimeField(auto_now_add=True, verbose_name='Created At')),
('updated_at', models.DateTimeField(auto_now=True, verbose_name='Updated At')),
('car', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, related_name='transfer_logs', to='inventory.car', verbose_name='Car')),
('from_dealer', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, related_name='transfers_out', to='inventory.dealer', verbose_name='From Dealer')),
('to_dealer', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, related_name='transfers_in', to='inventory.dealer', verbose_name='To Dealer')),
],
options={
'verbose_name': 'Car Transfer Log',
'verbose_name_plural': 'Car Transfer Logs',
},
),
migrations.CreateModel( migrations.CreateModel(
name='CarSpecificationValue', name='CarSpecificationValue',
fields=[ fields=[

View File

@ -1,18 +0,0 @@
# Generated by Django 4.2.17 on 2025-01-13 10:20
from django.db import migrations, models
class Migration(migrations.Migration):
dependencies = [
('inventory', '0001_initial'),
]
operations = [
migrations.AlterField(
model_name='carmake',
name='car_type',
field=models.SmallIntegerField(blank=True, choices=[], null=True),
),
]

View File

@ -1,18 +0,0 @@
# Generated by Django 4.2.17 on 2025-01-14 12:10
from django.db import migrations, models
class Migration(migrations.Migration):
dependencies = [
('inventory', '0002_alter_carmake_car_type'),
]
operations = [
migrations.AlterField(
model_name='carmake',
name='car_type',
field=models.SmallIntegerField(blank=True, choices=[(1, 'Car'), (2, 'Light Commercial'), (3, 'Heavy-Duty Tractors'), (4, 'Trailers'), (5, 'Medium Trucks'), (6, 'Buses'), (20, 'Motorcycles'), (21, 'Buggy'), (22, 'Moto ATV'), (23, 'Scooters'), (24, 'Karting'), (25, 'ATV'), (26, 'Snowmobiles')], null=True),
),
]

View File

@ -1,90 +0,0 @@
# Generated by Django 5.1.4 on 2025-01-17 00:20
import django.db.models.deletion
import django.utils.timezone
from django.db import migrations, models
class Migration(migrations.Migration):
dependencies = [
('inventory', '0003_alter_carmake_car_type'),
]
operations = [
migrations.RenameField(
model_name='lead',
old_name='assigned',
new_name='staff',
),
migrations.RemoveField(
model_name='customer',
name='city',
),
migrations.RemoveField(
model_name='customer',
name='lead',
),
migrations.RemoveField(
model_name='customer',
name='staff',
),
migrations.RemoveField(
model_name='lead',
name='address',
),
migrations.RemoveField(
model_name='lead',
name='email',
),
migrations.RemoveField(
model_name='lead',
name='first_name',
),
migrations.RemoveField(
model_name='lead',
name='last_name',
),
migrations.RemoveField(
model_name='lead',
name='obligations',
),
migrations.RemoveField(
model_name='lead',
name='phone_number',
),
migrations.RemoveField(
model_name='lead',
name='salary',
),
migrations.RemoveField(
model_name='lead',
name='title',
),
migrations.RemoveField(
model_name='organization',
name='created_at',
),
migrations.AddField(
model_name='lead',
name='customer',
field=models.ForeignKey(default=1, on_delete=django.db.models.deletion.CASCADE, related_name='leads', to='inventory.customer'),
preserve_default=False,
),
migrations.AddField(
model_name='organization',
name='created',
field=models.DateTimeField(auto_now_add=True, default=django.utils.timezone.now, verbose_name='Created'),
preserve_default=False,
),
migrations.AddField(
model_name='organization',
name='updated',
field=models.DateTimeField(auto_now=True, verbose_name='Updated'),
),
migrations.AlterField(
model_name='representative',
name='id_number',
field=models.CharField(max_length=10, unique=True, verbose_name='ID Number'),
),
]

View File

@ -1,13 +0,0 @@
# Generated by Django 4.2.17 on 2025-01-19 12:55
from django.db import migrations
class Migration(migrations.Migration):
dependencies = [
('inventory', '0004_rename_assigned_lead_staff_remove_customer_city_and_more'),
]
operations = [
]

View File

@ -1,32 +0,0 @@
# Generated by Django 4.2.17 on 2025-01-19 14:01
from django.db import migrations, models
import django.db.models.deletion
class Migration(migrations.Migration):
dependencies = [
('inventory', '0005_merge_20250119_1555'),
]
operations = [
migrations.CreateModel(
name='CarTransferLog',
fields=[
('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
('transfer_date', models.DateTimeField(auto_now_add=True, verbose_name='Transfer Date')),
('remarks', models.TextField(blank=True, null=True, verbose_name='Remarks')),
('cars', models.ManyToManyField(related_name='transfer_logs', to='inventory.car', verbose_name='Cars')),
('from_dealer', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, related_name='transfers_out', to='inventory.dealer', verbose_name='From Dealer')),
('to_dealer', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, related_name='transfers_in', to='inventory.dealer', verbose_name='To Dealer')),
],
options={
'verbose_name': 'Car Transfer Log',
'verbose_name_plural': 'Car Transfer Logs',
},
),
# migrations.DeleteModel(
# name='InvoiceModelBase',
# ),
]

View File

@ -1,196 +1,531 @@
from django.urls import path from django.urls import path
from . import views from . import views
from allauth.account import views as allauth_views from allauth.account import views as allauth_views
from django.conf.urls import ( from django.conf.urls import handler400, handler403, handler404, handler500
handler400, handler403, handler404, handler500
)
urlpatterns = [ urlpatterns = [
# main URLs # main URLs
path('', views.HomeView.as_view(), name='landing_page'), path("", views.HomeView.as_view(), name="landing_page"),
path('welcome/', views.WelcomeView.as_view(), name='welcome'), path("welcome/", views.WelcomeView.as_view(), name="welcome"),
# Accounts URLs # Accounts URLs
path('login/', views.Login.as_view(), name='account_login'), path("login/", views.Login.as_view(), name="account_login"),
path('logout/', allauth_views.LogoutView.as_view(template_name='account/logout.html'), name='account_logout'), path(
# path('signup/', allauth_views.SignupView.as_view(template_name='account/signup.html'), name='account_signup'), "logout/",
path('signup/', views.dealer_signup, name='account_signup'), allauth_views.LogoutView.as_view(template_name="account/logout.html"),
path('password/change/', name="account_logout",
allauth_views.PasswordChangeView.as_view(template_name='account/password_change.html'), ),
name='account_change_password'), # path('signup/', allauth_views.SignupView.as_view(template_name='account/signup.html'), name='account_signup'),
path('password/reset/', path("signup/", views.dealer_signup, name="account_signup"),
allauth_views.PasswordResetView.as_view(template_name='account/password_reset.html'), path(
name='account_reset_password'), "password/change/",
path('password/reset/done/', allauth_views.PasswordChangeView.as_view(
allauth_views.PasswordResetDoneView.as_view(template_name='account/password_reset_done.html'), template_name="account/password_change.html"
name='account_password_reset_done'), ),
path('login/code/', allauth_views.RequestLoginCodeView.as_view(template_name='account/request_login_code.html')), name="account_change_password",
#Dashboards ),
path('dashboards/accounting/', views.AccountingDashboard.as_view(), name='accounting'), path(
path('test/', views.TestView.as_view(), name='test'), "password/reset/",
allauth_views.PasswordResetView.as_view(
template_name="account/password_reset.html"
),
name="account_reset_password",
),
path(
"password/reset/done/",
allauth_views.PasswordResetDoneView.as_view(
template_name="account/password_reset_done.html"
),
name="account_password_reset_done",
),
path(
"login/code/",
allauth_views.RequestLoginCodeView.as_view(
template_name="account/request_login_code.html"
),
),
# Dashboards
path(
"dashboards/accounting/", views.AccountingDashboard.as_view(), name="accounting"
),
path("test/", views.TestView.as_view(), name="test"),
# Dealer URLs # Dealer URLs
path('dealers/<int:pk>/', views.DealerDetailView.as_view(), name='dealer_detail'), path("dealers/<int:pk>/", views.DealerDetailView.as_view(), name="dealer_detail"),
path('dealers/<int:pk>/update/', views.DealerUpdateView.as_view(), name='dealer_update'), path(
path('dealers/activity/', views.UserActivityLogListView.as_view(), name='dealer_activity'), "dealers/<int:pk>/update/",
views.DealerUpdateView.as_view(),
name="dealer_update",
),
path(
"dealers/activity/",
views.UserActivityLogListView.as_view(),
name="dealer_activity",
),
# path('dealers/<int:pk>/delete/', views.DealerDeleteView.as_view(), name='dealer_delete'), # path('dealers/<int:pk>/delete/', views.DealerDeleteView.as_view(), name='dealer_delete'),
# CRM URLs # CRM URLs
path('customers/', views.CustomerListView.as_view(), name='customer_list'), path("customers/", views.CustomerListView.as_view(), name="customer_list"),
path('customers/<int:pk>/', views.CustomerDetailView.as_view(), name='customer_detail'), path(
path('customers/create/', views.CustomerCreateView.as_view(), name='customer_create'), "customers/<int:pk>/",
path('customers/<int:pk>/update/', views.CustomerUpdateView.as_view(), name='customer_update'), views.CustomerDetailView.as_view(),
path('customers/<int:pk>/delete/', views.delete_customer, name='customer_delete'), name="customer_detail",
path('customers/<int:customer_id>/opportunities/create/', views.OpportunityCreateView.as_view(), name='create_opportunity'), ),
path('customers/<int:pk>/add-note/', views.add_note_to_customer, name='add_note_to_customer'), path(
"customers/create/", views.CustomerCreateView.as_view(), name="customer_create"
path('crm/leads/', views.LeadListView.as_view(), name='lead_list'), ),
path('crm/leads/<int:pk>/view/', views.LeadDetailView.as_view(), name='lead_detail'), path(
path('crm/leads/create/', views.LeadCreateView.as_view(), name='lead_create'), "customers/<int:pk>/update/",
path('crm/leads/<int:pk>/update/', views.LeadUpdateView.as_view(), name='lead_update'), views.CustomerUpdateView.as_view(),
path('crm/leads/<int:pk>/delete/', views.LeadDeleteView.as_view(), name='lead_delete'), name="customer_update",
path('crm/leads/<int:pk>/add-note/', views.add_note_to_lead, name='add_note'), ),
path('crm/leads/<int:pk>/add-activity/', views.add_activity_to_lead, name='add_activity'), path("customers/<int:pk>/delete/", views.delete_customer, name="customer_delete"),
path('crm/opportunities/create/', views.OpportunityCreateView.as_view(), name='opportunity_create'), path(
path('crm/opportunities/<int:pk>/', views.OpportunityDetailView.as_view(), name='opportunity_detail'), "customers/<int:customer_id>/opportunities/create/",
path('crm/opportunities/<int:pk>/edit/', views.OpportunityUpdateView.as_view(), name='update_opportunity'), views.OpportunityCreateView.as_view(),
path('crm/opportunities/', views.OpportunityListView.as_view(), name='opportunity_list'), name="create_opportunity",
path('crm/opportunities/<int:pk>/delete/', views.delete_opportunity, name='delete_opportunity'), ),
path(
"customers/<int:pk>/add-note/",
views.add_note_to_customer,
name="add_note_to_customer",
),
path("crm/leads/", views.LeadListView.as_view(), name="lead_list"),
path(
"crm/leads/<int:pk>/view/", views.LeadDetailView.as_view(), name="lead_detail"
),
path("crm/leads/create/", views.LeadCreateView.as_view(), name="lead_create"),
path(
"crm/leads/<int:pk>/update/", views.LeadUpdateView.as_view(), name="lead_update"
),
path(
"crm/leads/<int:pk>/delete/", views.LeadDeleteView.as_view(), name="lead_delete"
),
path("crm/leads/<int:pk>/add-note/", views.add_note_to_lead, name="add_note"),
path(
"crm/leads/<int:pk>/add-activity/",
views.add_activity_to_lead,
name="add_activity",
),
path(
"crm/opportunities/create/",
views.OpportunityCreateView.as_view(),
name="opportunity_create",
),
path(
"crm/opportunities/<int:pk>/",
views.OpportunityDetailView.as_view(),
name="opportunity_detail",
),
path(
"crm/opportunities/<int:pk>/edit/",
views.OpportunityUpdateView.as_view(),
name="update_opportunity",
),
path(
"crm/opportunities/",
views.OpportunityListView.as_view(),
name="opportunity_list",
),
path(
"crm/opportunities/<int:pk>/delete/",
views.delete_opportunity,
name="delete_opportunity",
),
# path('crm/opportunities/<int:pk>/logs/', views.OpportunityLogsView.as_view(), name='opportunity_logs'), # path('crm/opportunities/<int:pk>/logs/', views.OpportunityLogsView.as_view(), name='opportunity_logs'),
path('crm/notifications/', views.NotificationListView.as_view(), name='notifications_history'), path(
path('crm/fetch_notifications/', views.fetch_notifications, name='fetch_notifications'), "crm/notifications/",
path('crm/notifications/<int:pk>/mark_as_read/', views.mark_notification_as_read, name='mark_notification_as_read'), views.NotificationListView.as_view(),
name="notifications_history",
#Vendor URLs ),
path('vendors', views.VendorListView.as_view(), name='vendor_list'), path(
path('vendors/<int:pk>/', views.VendorDetailView.as_view(), name='vendor_detail'), "crm/fetch_notifications/",
path('vendors/create/', views.VendorCreateView.as_view(), name='vendor_create'), views.fetch_notifications,
path('vendors/<int:pk>/update/', views.VendorUpdateView.as_view(), name='vendor_update'), name="fetch_notifications",
path('vendors/<int:pk>/delete/', views.VendorDetailView.as_view(), name='vendor_delete'), ),
path(
"crm/notifications/<int:pk>/mark_as_read/",
views.mark_notification_as_read,
name="mark_notification_as_read",
),
# Vendor URLs
path("vendors", views.VendorListView.as_view(), name="vendor_list"),
path("vendors/<int:pk>/", views.VendorDetailView.as_view(), name="vendor_detail"),
path("vendors/create/", views.VendorCreateView.as_view(), name="vendor_create"),
path(
"vendors/<int:pk>/update/",
views.VendorUpdateView.as_view(),
name="vendor_update",
),
path(
"vendors/<int:pk>/delete/",
views.VendorDetailView.as_view(),
name="vendor_delete",
),
# Car URLs # Car URLs
path('cars/inventory/', views.CarInventory.as_view(), name='car_inventory_all'), path("cars/inventory/", views.CarInventory.as_view(), name="car_inventory_all"),
path('cars/inventory/<int:make_id>/<int:model_id>/<int:trim_id>/', views.CarInventory.as_view(), name='car_inventory'), path(
path('cars/inventory/stats', views.inventory_stats_view, name='inventory_stats'), "cars/inventory/<int:make_id>/<int:model_id>/<int:trim_id>/",
path('cars/<int:pk>/', views.CarDetailView.as_view(), name='car_detail'), views.CarInventory.as_view(),
path('cars/<int:pk>/update/', views.CarUpdateView.as_view(), name='car_update'), name="car_inventory",
path('cars/<int:pk>/delete/', views.CarDeleteView.as_view(), name='car_delete'), ),
path('cars/<int:car_pk>/finance/create/', views.CarFinanceCreateView.as_view(), name='car_finance_create'), path("cars/inventory/stats", views.inventory_stats_view, name="inventory_stats"),
path('cars/finance/<int:pk>/update/', views.CarFinanceUpdateView.as_view(), name='car_finance_update'), path("cars/<int:pk>/", views.CarDetailView.as_view(), name="car_detail"),
path('cars/add/', views.CarCreateView.as_view(), name='car_add'), path("cars/<int:pk>/update/", views.CarUpdateView.as_view(), name="car_update"),
path('ajax/', views.AjaxHandlerView.as_view(), name='ajax_handler'), path("cars/<int:pk>/delete/", views.CarDeleteView.as_view(), name="car_delete"),
path('cars/get-car-models/', views.get_car_models, name='get_car_models'), path(
path('cars/<int:car_pk>/add-color/', views.CarColorCreate.as_view(), name='add_color'), "cars/<int:car_pk>/finance/create/",
path('cars/<int:car_pk>/location/add/', views.CarLocationCreateView.as_view(), name='add_car_location'), views.CarFinanceCreateView.as_view(),
path('cars/<int:pk>/location/update/', views.CarLocationUpdateView.as_view(), name='transfer'), name="car_finance_create",
path('cars/inventory/search/', views.SearchCodeView.as_view(), name='car_search'), ),
path(
"cars/finance/<int:pk>/update/",
views.CarFinanceUpdateView.as_view(),
name="car_finance_update",
),
path("cars/add/", views.CarCreateView.as_view(), name="car_add"),
path("ajax/", views.AjaxHandlerView.as_view(), name="ajax_handler"),
path("cars/get-car-models/", views.get_car_models, name="get_car_models"),
path(
"cars/<int:car_pk>/add-color/", views.CarColorCreate.as_view(), name="add_color"
),
path(
"cars/<int:car_pk>/location/add/",
views.CarLocationCreateView.as_view(),
name="add_car_location",
),
path(
"cars/<int:pk>/location/update/",
views.CarTransferCreateView.as_view(),
name="transfer",
),
path(
"cars/<int:pk>/location/detail/",
views.CarTransferDetailView,
name="transfer_detail",
),
path(
"cars/<int:car_pk>/location/<int:transfer_pk>/transfer_approve/",
views.car_transfer_approve,
name="transfer_confirm",
),
path(
"cars/<int:car_pk>/location/<int:transfer_pk>/transfer_accept_reject/",
views.car_transfer_accept_reject,
name="transfer_accept_reject",
),
path(
"cars/<int:car_pk>/location/<int:transfer_pk>/preview/",
views.CarTransferPreviewView,
name="transfer_preview",
),
path("cars/inventory/search/", views.SearchCodeView.as_view(), name="car_search"),
# path('cars/<int:car_pk>/colors/<int:pk>/update/',views.CarColorUpdateView.as_view(),name='color_update'), # path('cars/<int:car_pk>/colors/<int:pk>/update/',views.CarColorUpdateView.as_view(),name='color_update'),
path("cars/reserve/<int:car_id>/", views.reserve_car_view, name="reserve_car"),
path('cars/reserve/<int:car_id>/', views.reserve_car_view, name='reserve_car'), path(
path('reservations/<int:reservation_id>/', views.manage_reservation, name='reservations'), "reservations/<int:reservation_id>/",
path('cars/<int:car_pk>/add-custom-card/', views.CustomCardCreateView.as_view(), name='add_custom_card'), views.manage_reservation,
name="reservations",
),
path(
"cars/<int:car_pk>/add-custom-card/",
views.CustomCardCreateView.as_view(),
name="add_custom_card",
),
# Sales URLs quotation_create # Sales URLs quotation_create
path('sales/quotations/create/', views.QuotationCreateView.as_view(), name='quotation_create'), path(
path('sales/quotations/<int:pk>/', views.QuotationDetailView.as_view(), name='quotation_detail'), "sales/quotations/create/",
path('sales/quotations/', views.QuotationListView.as_view(), name='quotation_list'), views.QuotationCreateView.as_view(),
path('sales/quotations/<int:pk>/confirm/', views.confirm_quotation, name='confirm_quotation'), name="quotation_create",
path('sales/orders/detail/<int:order_id>/', views.SalesOrderDetailView.as_view(), name='order_detail'), ),
path('quotation/<int:quotation_id>/pdf/', views.download_quotation_pdf, name='quotation_pdf'), path(
path('generate_invoice/<int:pk>/', views.generate_invoice, name='generate_invoice'), "sales/quotations/<int:pk>/",
path('sales/quotations/<int:pk>/mark_quotation/', views.mark_quotation, name='mark_quotation'), views.QuotationDetailView.as_view(),
path('sales/quotations/<int:pk>/post_quotation/', views.post_quotation, name='post_quotation'), name="quotation_detail",
path('sales/quotations/<int:pk>/invoice_detail/', views.invoice_detail, name='invoice_detail'), ),
path('subscriptions', views.SubscriptionPlans.as_view(), name='subscriptions'), path("sales/quotations/", views.QuotationListView.as_view(), name="quotation_list"),
#Payment URLs path(
# path('sales/quotations/<int:pk>/payment/', views.PaymentCreateView.as_view(), name='payment_create'), "sales/quotations/<int:pk>/confirm/",
path('sales/quotations/<int:pk>/payment/', views.payment_create, name='payment_create'), views.confirm_quotation,
name="confirm_quotation",
),
path(
"sales/orders/detail/<int:order_id>/",
views.SalesOrderDetailView.as_view(),
name="order_detail",
),
path(
"quotation/<int:quotation_id>/pdf/",
views.download_quotation_pdf,
name="quotation_pdf",
),
path("generate_invoice/<int:pk>/", views.generate_invoice, name="generate_invoice"),
path(
"sales/quotations/<int:pk>/mark_quotation/",
views.mark_quotation,
name="mark_quotation",
),
path(
"sales/quotations/<int:pk>/post_quotation/",
views.post_quotation,
name="post_quotation",
),
path(
"sales/quotations/<int:pk>/invoice_detail/",
views.invoice_detail,
name="invoice_detail",
),
path("subscriptions", views.SubscriptionPlans.as_view(), name="subscriptions"),
# Payment URLs
# path('sales/quotations/<int:pk>/payment/', views.PaymentCreateView.as_view(), name='payment_create'),
path(
"sales/quotations/<int:pk>/payment/",
views.payment_create,
name="payment_create",
),
# Users URLs # Users URLs
path('user/create/', views.UserCreateView.as_view(), name='user_create'), path("user/create/", views.UserCreateView.as_view(), name="user_create"),
path('user/<int:pk>/update/', views.UserUpdateView.as_view(), name='user_update'), path("user/<int:pk>/update/", views.UserUpdateView.as_view(), name="user_update"),
path('user/<int:pk>/', views.UserDetailView.as_view(), name='user_detail'), path("user/<int:pk>/", views.UserDetailView.as_view(), name="user_detail"),
path('user/', views.UserListView.as_view(), name='user_list'), path("user/", views.UserListView.as_view(), name="user_list"),
path('user/<int:pk>/confirm/', views.UserDeleteview, name='user_delete'), path("user/<int:pk>/confirm/", views.UserDeleteview, name="user_delete"),
# Organization URLs # Organization URLs
path('organizations/', views.OrganizationListView.as_view(), name='organization_list'), path(
path('organizations/<int:pk>/', views.OrganizationDetailView.as_view(), name='organization_detail'), "organizations/", views.OrganizationListView.as_view(), name="organization_list"
path('organizations/create/', views.OrganizationCreateView.as_view(), name='organization_create'), ),
path('organizations/<int:pk>/update/', views.OrganizationUpdateView.as_view(), name='organization_update'), path(
path('organizations/<int:pk>/delete/', views.OrganizationDeleteView.as_view(), name='organization_delete'), "organizations/<int:pk>/",
views.OrganizationDetailView.as_view(),
name="organization_detail",
),
path(
"organizations/create/",
views.OrganizationCreateView.as_view(),
name="organization_create",
),
path(
"organizations/<int:pk>/update/",
views.OrganizationUpdateView.as_view(),
name="organization_update",
),
path(
"organizations/<int:pk>/delete/",
views.OrganizationDeleteView.as_view(),
name="organization_delete",
),
# Representative URLs # Representative URLs
path('representatives/', views.RepresentativeListView.as_view(), name='representative_list'), path(
path('representatives/<int:pk>/', views.RepresentativeDetailView.as_view(), name='representative_detail'), "representatives/",
path('representatives/create/', views.RepresentativeCreateView.as_view(), name='representative_create'), views.RepresentativeListView.as_view(),
path('representatives/<int:pk>/update/', views.RepresentativeUpdateView.as_view(), name='representative_update'), name="representative_list",
path('representatives/<int:pk>/delete/', views.RepresentativeDeleteView.as_view(), name='representative_delete'), ),
path(
#Ledger URLS "representatives/<int:pk>/",
#Bank Account views.RepresentativeDetailView.as_view(),
path('bank_accounts/', views.BankAccountListView.as_view(), name='bank_account_list'), name="representative_detail",
path('bank_accounts/<uuid:pk>/', views.BankAccountDetailView.as_view(), name='bank_account_detail'), ),
path('bank_accounts/create/', views.BankAccountCreateView.as_view(), name='bank_account_create'), path(
path('bank_accounts/<uuid:pk>/update/', views.BankAccountUpdateView.as_view(), name='bank_account_update'), "representatives/create/",
path('bank_accounts/<uuid:pk>/delete/', views.bank_account_delete, name='bank_account_delete'), views.RepresentativeCreateView.as_view(),
name="representative_create",
),
path(
"representatives/<int:pk>/update/",
views.RepresentativeUpdateView.as_view(),
name="representative_update",
),
path(
"representatives/<int:pk>/delete/",
views.RepresentativeDeleteView.as_view(),
name="representative_delete",
),
# Ledger URLS
# Bank Account
path(
"bank_accounts/", views.BankAccountListView.as_view(), name="bank_account_list"
),
path(
"bank_accounts/<uuid:pk>/",
views.BankAccountDetailView.as_view(),
name="bank_account_detail",
),
path(
"bank_accounts/create/",
views.BankAccountCreateView.as_view(),
name="bank_account_create",
),
path(
"bank_accounts/<uuid:pk>/update/",
views.BankAccountUpdateView.as_view(),
name="bank_account_update",
),
path(
"bank_accounts/<uuid:pk>/delete/",
views.bank_account_delete,
name="bank_account_delete",
),
# Account # Account
path('coa_accounts/', views.AccountListView.as_view(), name='account_list'), path("coa_accounts/", views.AccountListView.as_view(), name="account_list"),
path('coa_accounts/<uuid:pk>/', views.AccountDetailView.as_view(), name='account_detail'), path(
path('coa_accounts/create/', views.AccountCreateView.as_view(), name='account_create'), "coa_accounts/<uuid:pk>/",
path('coa_accounts/<uuid:pk>/update/', views.AccountUpdateView.as_view(), name='account_update'), views.AccountDetailView.as_view(),
path('coa_accounts/<uuid:pk>/delete/', views.account_delete, name='account_delete'), name="account_detail",
),
path(
"coa_accounts/create/", views.AccountCreateView.as_view(), name="account_create"
),
path(
"coa_accounts/<uuid:pk>/update/",
views.AccountUpdateView.as_view(),
name="account_update",
),
path("coa_accounts/<uuid:pk>/delete/", views.account_delete, name="account_delete"),
# Estimate # Estimate
path('sales/estimates/', views.EstimateListView.as_view(), name='estimate_list'), path("sales/estimates/", views.EstimateListView.as_view(), name="estimate_list"),
path('sales/estimates/<uuid:pk>/', views.EstimateDetailView.as_view(), name='estimate_detail'), path(
path('sales/estimates/create/', views.create_estimate, name='estimate_create'), "sales/estimates/<uuid:pk>/",
path('sales/estimates/<uuid:pk>/estimate_mark_as/', views.estimate_mark_as, name='estimate_mark_as'), views.EstimateDetailView.as_view(),
path('sales/estimates/<uuid:pk>/preview/', views.EstimatePreviewView.as_view(), name='estimate_preview'), name="estimate_detail",
path('sales/estimates/<uuid:pk>/payment_request/', views.PaymentRequest.as_view(), name='payment_request'), ),
path('sales/estimates/<uuid:pk>/send_email', views.send_email_view, name='send_email'), path("sales/estimates/create/", views.create_estimate, name="estimate_create"),
path(
"sales/estimates/<uuid:pk>/estimate_mark_as/",
views.estimate_mark_as,
name="estimate_mark_as",
),
path(
"sales/estimates/<uuid:pk>/preview/",
views.EstimatePreviewView.as_view(),
name="estimate_preview",
),
path(
"sales/estimates/<uuid:pk>/payment_request/",
views.PaymentRequest.as_view(),
name="payment_request",
),
path(
"sales/estimates/<uuid:pk>/send_email", views.send_email_view, name="send_email"
),
# Invoice # Invoice
path('sales/invoices/', views.InvoiceListView.as_view(), name='invoice_list'), path("sales/invoices/", views.InvoiceListView.as_view(), name="invoice_list"),
path('sales/invoices/<uuid:pk>/create/', views.invoice_create, name='invoice_create'), path(
path('sales/invoices/<uuid:pk>/', views.InvoiceDetailView.as_view(), name='invoice_detail'), "sales/invoices/<uuid:pk>/create/", views.invoice_create, name="invoice_create"
path('sales/invoices/<uuid:pk>/preview/', views.InvoicePreviewView.as_view(), name='invoice_preview'), ),
path('sales/invoices/<uuid:pk>/invoice_mark_as/', views.invoice_mark_as, name='invoice_mark_as'), path(
path('sales/invoices/<uuid:pk>/draft_invoice_update/', views.DraftInvoiceModelUpdateFormView.as_view(), name='draft_invoice_update'), "sales/invoices/<uuid:pk>/",
path('sales/invoices/<uuid:pk>/approved_invoice_update/', views.ApprovedInvoiceModelUpdateFormView.as_view(), name='approved_invoice_update'), views.InvoiceDetailView.as_view(),
path('sales/invoices/<uuid:pk>/paid_invoice_update/', views.PaidInvoiceModelUpdateFormView.as_view(), name='paid_invoice_update'), name="invoice_detail",
),
# path('sales/estimates/<uuid:pk>/preview/', views.EstimatePreviewView.as_view(), name='estimate_preview'), path(
# path('send_email/<uuid:pk>', views.send_email, name='send_email'), "sales/invoices/<uuid:pk>/preview/",
views.InvoicePreviewView.as_view(),
#Payment name="invoice_preview",
path('sales/payments/', views.PaymentListView, name='payment_list'), ),
path('sales/payments/<uuid:pk>/create/', views.PaymentCreateView, name='payment_create'), path(
path('sales/payments/create/', views.PaymentCreateView, name='payment_create'), "sales/invoices/<uuid:pk>/invoice_mark_as/",
path('sales/payments/<uuid:pk>/payment_details/', views.PaymentDetailView, name='payment_details'), views.invoice_mark_as,
path('sales/payments/<uuid:pk>/payment_mark_as_paid/', views.payment_mark_as_paid, name='payment_mark_as_paid'), name="invoice_mark_as",
# path('sales/payments/<uuid:pk>/update/', views.JournalEntryUpdateView.as_view(), name='payment_update'), ),
# path('sales/payments/<uuid:pk>/delete/', views.JournalEntryDeleteView.as_view(), name='payment_delete'), path(
# path('sales/payments/<uuid:pk>/preview/', views.JournalEntryPreviewView.as_view(), name='payment_preview'), "sales/invoices/<uuid:pk>/draft_invoice_update/",
# # Journal views.DraftInvoiceModelUpdateFormView.as_view(),
# path('sales/journal/<uuid:pk>/create/', views.JournalEntryCreateView.as_view(), name='journal_create'), name="draft_invoice_update",
),
# Items path(
path('items/services/', views.ItemServiceListView.as_view(), name='item_service_list'), "sales/invoices/<uuid:pk>/approved_invoice_update/",
path('items/services/create/', views.ItemServiceCreateView.as_view(), name='item_service_create'), views.ApprovedInvoiceModelUpdateFormView.as_view(),
path('items/services/<int:pk>/update/', views.ItemServiceUpdateView.as_view(), name='item_service_update'), name="approved_invoice_update",
# Expanese ),
path('items/expeneses/', views.ItemExpenseListView.as_view(), name='item_expense_list'), path(
path('items/expeneses/create/', views.ItemExpenseCreateView.as_view(), name='item_expense_create'), "sales/invoices/<uuid:pk>/paid_invoice_update/",
path('items/expeneses/<uuid:pk>/update/', views.ItemExpenseUpdateView.as_view(), name='item_expense_update'), views.PaidInvoiceModelUpdateFormView.as_view(),
# Bills name="paid_invoice_update",
path('items/bills/', views.BillListView.as_view(), name='bill_list'), ),
path('items/bills/create/', views.BillCreateView.as_view(), name='bill_create'), # path('sales/estimates/<uuid:pk>/preview/', views.EstimatePreviewView.as_view(), name='estimate_preview'),
# path('items/bills/<uuid:pk>/update/', views.ItemExpenseUpdateView.as_view(), name='item_expense_update'), # path('send_email/<uuid:pk>', views.send_email, name='send_email'),
# Payment
path("sales/payments/", views.PaymentListView, name="payment_list"),
path(
"sales/payments/<uuid:pk>/create/",
views.PaymentCreateView,
name="payment_create",
),
# path("sales/payments/create/", views.PaymentCreateView, name="payment_create"),
path(
"sales/payments/<uuid:pk>/payment_details/",
views.PaymentDetailView,
name="payment_details",
),
path(
"sales/payments/<uuid:pk>/payment_mark_as_paid/",
views.payment_mark_as_paid,
name="payment_mark_as_paid",
),
# path('sales/payments/<uuid:pk>/update/', views.JournalEntryUpdateView.as_view(), name='payment_update'),
# path('sales/payments/<uuid:pk>/delete/', views.JournalEntryDeleteView.as_view(), name='payment_delete'),
# path('sales/payments/<uuid:pk>/preview/', views.JournalEntryPreviewView.as_view(), name='payment_preview'),
# # Journal
# path('sales/journal/<uuid:pk>/create/', views.JournalEntryCreateView.as_view(), name='journal_create'),
# Items
path(
"items/services/", views.ItemServiceListView.as_view(), name="item_service_list"
),
path(
"items/services/create/",
views.ItemServiceCreateView.as_view(),
name="item_service_create",
),
path(
"items/services/<int:pk>/update/",
views.ItemServiceUpdateView.as_view(),
name="item_service_update",
),
# Expanese
path(
"items/expeneses/",
views.ItemExpenseListView.as_view(),
name="item_expense_list",
),
path(
"items/expeneses/create/",
views.ItemExpenseCreateView.as_view(),
name="item_expense_create",
),
path(
"items/expeneses/<uuid:pk>/update/",
views.ItemExpenseUpdateView.as_view(),
name="item_expense_update",
),
# Bills
path("items/bills/", views.BillListView.as_view(), name="bill_list"),
path("items/bills/create/", views.bill_create, name="bill_create"),
path(
"items/bills/<uuid:pk>/bill_detail/",
views.BillDetailView.as_view(),
name="bill_detail",
),
path("items/bills/<uuid:pk>/delete/", views.BillDeleteView, name="bill_delete"),
path(
"items/bills/<uuid:pk>/in_review/",
views.InReviewBillView.as_view(),
name="in_review_bill",
),
path(
"items/bills/<uuid:pk>/in_approve/",
views.ApprovedBillModelView.as_view(),
name="in_approve_bill",
),
path(
"items/bills/<uuid:pk>/mark_as_approved/",
views.bill_mark_as_approved,
name="bill_mark_as_approved",
),
path(
"items/bills/<uuid:pk>/mark_as_paid/",
views.bill_mark_as_paid,
name="bill_mark_as_paid",
),
] ]
handler404 = 'inventory.views.custom_page_not_found_view' handler404 = "inventory.views.custom_page_not_found_view"
handler500 = 'inventory.views.custom_error_view' handler500 = "inventory.views.custom_error_view"
handler403 = 'inventory.views.custom_permission_denied_view' handler403 = "inventory.views.custom_permission_denied_view"
handler400 = 'inventory.views.custom_bad_request_view' handler400 = "inventory.views.custom_bad_request_view"

View File

@ -1,6 +1,13 @@
import json
import random
import datetime
from django.shortcuts import redirect from django.shortcuts import redirect
from django.contrib import messages from django.contrib import messages
from django.utils import timezone from django.utils import timezone
from django_ledger.models.entity import UnitOfMeasureModel
from django_ledger.models.journal_entry import JournalEntryModel
from django_ledger.models.ledger import LedgerModel
from django_ledger.models.transactions import TransactionModel
import requests import requests
from inventory import models from inventory import models
from django.conf import settings from django.conf import settings
@ -8,7 +15,7 @@ from django.core.mail import send_mail
from django.utils.translation import gettext_lazy as _ from django.utils.translation import gettext_lazy as _
from inventory.utilities.financials import get_financial_value from inventory.utilities.financials import get_financial_value
from django_ledger.models.items import ItemModel from django_ledger.models.items import ItemModel
from django_ledger.models import InvoiceModel, EstimateModel from django_ledger.models import InvoiceModel, EstimateModel,BillModel
from decimal import Decimal from decimal import Decimal
@ -179,3 +186,287 @@ def get_financial_values(model):
"vat_amount": vat_amount, "vat_amount": vat_amount,
"vat": vat.rate, "vat": vat.rate,
} }
def set_invoice_payment(dealer, entity, invoice, amount, payment_method):
vat_amount = 0
total_amount = 0
if invoice.terms == "on_receipt":
for x in invoice.get_itemtxs_data()[0].all():
vat_amount += models.Car.objects.get(
vin=x.item_model.name
).finances.vat_amount * Decimal(x.quantity)
total_amount += Decimal(x.unit_cost) * Decimal(x.quantity)
grand_total = total_amount - Decimal(vat_amount)
ledger = LedgerModel.objects.filter(
name__icontains=str(invoice.pk), entity=entity
).first()
journal = JournalEntryModel.objects.create(
posted=False,
description=f"Payment for Invoice {invoice.invoice_number}",
ledger=ledger,
locked=False,
origin="Payment",
)
credit_account = entity.get_default_coa_accounts().get(name="Sales Revenue")
debit_account = None
if payment_method == "cash":
debit_account = entity.get_default_coa_accounts().get(name="Cash", active=True)
elif payment_method == "credit":
debit_account = entity.get_default_coa_accounts().get(
name="Accounts Receivable", active=True
)
else:
debit_account = entity.get_default_coa_accounts().get(
name="Cash in Bank", active=True
)
vat_payable_account = entity.get_default_coa_accounts().get(
name="VAT Payable", active=True
)
TransactionModel.objects.create(
journal_entry=journal,
account=debit_account, # Debit Cash
amount=amount, # Payment amount
tx_type="debit",
description="Payment Received",
)
TransactionModel.objects.create(
journal_entry=journal,
account=credit_account, # Credit Accounts Receivable
amount=grand_total, # Payment amount
tx_type="credit",
description="Payment Received",
)
if vat_amount > 0:
TransactionModel.objects.create(
journal_entry=journal,
account=vat_payable_account, # Credit VAT Payable
amount=vat_amount,
tx_type="credit",
description="VAT Payable on Invoice",
)
invoice.make_payment(amount)
invoice.save()
def set_bill_payment(dealer, entity, bill, amount, payment_method):
total_amount = 0
for x in bill.get_itemtxs_data()[0].all():
total_amount += Decimal(x.unit_cost) * Decimal(x.quantity)
journal = JournalEntryModel.objects.create(
posted=False,
description=f"Payment for bill {bill.bill_number}",
ledger=bill.ledger,
locked=False,
origin="Payment",
)
cash_account = entity.get_default_coa_accounts().get(name="Cash", active=True)
account_payable = entity.get_default_coa_accounts().get(
name="Accounts Payable", active=True
)
TransactionModel.objects.create(
journal_entry=journal,
account=cash_account, # Debit Cash
amount=amount, # Payment amount
tx_type="debit",
description="Payment Received",
)
TransactionModel.objects.create(
journal_entry=journal,
account=account_payable, # Credit Accounts Receivable
amount=amount, # Payment amount
tx_type="credit",
description="Payment Received",
)
bill.make_payment(amount)
bill.save()
def transfer_to_dealer(request,cars, to_dealer, remarks=None):
dealer = get_user_type(request)
if not cars:
raise ValueError("No cars selected for transfer.")
from_dealer = cars[0].dealer # Assume all cars are from the same dealer
# Validate that all cars are from the same dealer
for car in cars:
if car.dealer != from_dealer:
raise ValueError("All cars must be from the same dealer.")
if from_dealer == to_dealer:
raise ValueError("Cannot transfer cars to the same dealer.")
# Log the transfer
transfer_log = models.CarTransferLog.objects.create(
from_dealer=from_dealer,
to_dealer=to_dealer,
remarks=remarks,
)
transfer_log.cars.set(cars) # Associate the cars with the transfer log
# Update the dealer for all cars
for car in cars:
car.dealer = to_dealer
car.save()
def transfer_car(car,transfer):
from_dealer = transfer.from_dealer
to_dealer = transfer.to_dealer
# add transfer.to_dealer as customer in transfer.from_dealer entity
instance = models.Customer.objects.filter(
dealer=from_dealer,
email=to_dealer.user.email,
).first()
if not instance:
instance = models.Customer.objects.create(
dealer=from_dealer,
title=models.Title.MR,
email=to_dealer.user.email,
first_name=to_dealer.user.first_name,
last_name=to_dealer.user.last_name,
phone_number=f"05685412{random.randint(10, 99)}",
address=to_dealer.address,
national_id=f"{random.randint(100, 9999)}",
dob="1990-01-01",
)
# create invoice from transfer.from_dealer to transfer.to_dealer
name = f"{instance.first_name} {instance.middle_name} {instance.last_name}"
customer = from_dealer.entity.get_customers().filter(customer_name=name).first()
invoice = from_dealer.entity.create_invoice(
customer_model=customer,
terms=InvoiceModel.TERMS_NET_30,
cash_account=from_dealer.entity.get_default_coa_accounts().get(name="Cash", active=True),
prepaid_account=from_dealer.entity.get_default_coa_accounts().get(name="Accounts Receivable", active=True),
coa_model=from_dealer.entity.get_default_coa(),
)
ledger = from_dealer.entity.create_ledger(name=str(invoice.pk))
invoice.ledgar = ledger
ledger.invoicemodel = invoice
ledger.save()
invoice.save()
item = from_dealer.entity.get_items_products().filter(name=car.vin).first()
if not item:
return
invoice_itemtxs = {
item.item_number: {
"unit_cost": car.finances.cost_price,
"quantity": transfer.quantity,
"total_amount": transfer.total_price,
}
}
invoice_itemtxs = invoice.migrate_itemtxs(
itemtxs=invoice_itemtxs,
commit=True,
operation=InvoiceModel.ITEMIZE_APPEND,
)
invoice.mark_as_review()
invoice.mark_as_approved(from_dealer.entity.slug, from_dealer.entity.admin)
invoice.mark_as_paid(from_dealer.entity.slug, from_dealer.entity.admin)
invoice.save()
#create car item product in to_dealer entity
uom = to_dealer.entity.get_uom_all().filter(name=item.uom.name).first()
product = to_dealer.entity.create_item_product(
name=item.name,
uom_model=uom,
item_type=item.item_type,
coa_model=to_dealer.entity.get_default_coa(),
)
car_dict = vars(car).copy()
del car_dict["_state"]
for key, value in car_dict.items():
if isinstance(value, datetime.datetime):
car_dict[key] = value.strftime('%Y-%m-%d %H:%M:%S')
product.additional_info = json.dumps({"car_info": car_dict})
product.save()
#add the sender as vendor and create a bill for it
vendor_instance, created = models.Vendor.objects.get_or_create(
dealer=to_dealer,
crn=from_dealer.crn,
vrn=from_dealer.vrn,
name=from_dealer.name,
email=from_dealer.user.email,
arabic_name=from_dealer.arabic_name,
address=from_dealer.address,
phone_number=from_dealer.phone_number,
contact_person='',
)
#transfer the car to to_dealer and create items record
vendor = to_dealer.entity.get_vendors().filter(vendor_name=vendor_instance.name).first()
bill = to_dealer.entity.create_bill(
vendor_model=vendor,
terms=BillModel.TERMS_NET_30,
cash_account=to_dealer.entity.get_default_coa_accounts().get(name="Cash", active=True),
prepaid_account=to_dealer.entity.get_default_coa_accounts().get(name="Prepaid Expenses", active=True),
coa_model=to_dealer.entity.get_default_coa(),
)
bill_itemtxs = {
item.item_number: {
"unit_cost": car.finances.cost_price,
"quantity": transfer.quantity,
"total_amount": transfer.total_price,
}
}
bill_itemtxs = bill.migrate_itemtxs(itemtxs=bill_itemtxs,
commit=True,
operation=BillModel.ITEMIZE_REPLACE)
car.dealer = to_dealer
car.vendor = vendor_instance
car.receiving_date = datetime.datetime.now()
car.finances.additional_services.clear()
if hasattr(car, "custom_cards"):
car.custom_cards.delete()
# car.finances.cost_price = 0
car.finances.selling_price = 0
car.finances.discount_amount = 0
car.finances.save()
car.location.owner = to_dealer
car.location.showroom = to_dealer
car.location.description = ""
car.location.save()
# car.reservations.all().delete()
car.status = models.CarStatusChoices.AVAILABLE
transfer.status = models.CarTransferStatusChoices.success
transfer.active = False
transfer.save()
car.save()
return True
#pay the pill
# set_bill_payment(to_dealer,to_dealer.entity,bill,transfer.total_price,"credit")

View File

@ -1,6 +1,7 @@
from decimal import Decimal from decimal import Decimal
from django.core.paginator import Paginator from django.core.paginator import Paginator
from django.forms import DateField, DateInput, HiddenInput, TextInput from django.forms import DateField, DateInput, HiddenInput, TextInput
from django_ledger.forms.bill import ApprovedBillModelUpdateForm, InReviewBillModelUpdateForm
from django.utils.decorators import method_decorator from django.utils.decorators import method_decorator
from django.views.decorators.csrf import csrf_exempt from django.views.decorators.csrf import csrf_exempt
from django_ledger.models import ( from django_ledger.models import (
@ -81,6 +82,9 @@ from .utils import (
reserve_car, reserve_car,
send_email, send_email,
get_user_type, get_user_type,
set_bill_payment,
set_invoice_payment,
transfer_car,
) )
from django.contrib.auth.models import User from django.contrib.auth.models import User
from allauth.account import views from allauth.account import views
@ -775,6 +779,120 @@ class CarLocationUpdateView(UpdateView):
def get_success_url(self): def get_success_url(self):
return reverse_lazy("car_detail", kwargs={"pk": self.object.car.pk}) return reverse_lazy("car_detail", kwargs={"pk": self.object.car.pk})
class CarTransferCreateView(CreateView):
model = models.CarTransfer
form_class = forms.CarTransferForm
template_name = "inventory/car_location_form.html"
def get_form(self, form_class = None):
form = super().get_form(form_class)
form.fields['to_dealer'].queryset = models.Dealer.objects.exclude(pk=get_user_type(self.request).pk).all()
form.fields['car'].queryset = models.Car.objects.filter(pk=self.kwargs["pk"])
return form
def get_initial(self):
initial = super().get_initial()
initial["car"] = get_object_or_404(models.Car, pk=self.kwargs["pk"])
return initial
def form_valid(self, form):
form.instance.from_dealer = get_user_type(self.request)
form.instance.car.status = "transfer"
form.instance.car.save()
return super().form_valid(form)
def get_success_url(self):
return reverse_lazy("car_detail", kwargs={"pk": self.object.car.pk})
def CarTransferDetailView(request, pk):
transfer = get_object_or_404(models.CarTransfer, pk=pk)
context = {"transfer":transfer}
return render(request,'inventory/transfer_details.html',context)
def car_transfer_approve(request, car_pk,transfer_pk):
car = get_object_or_404(models.Car, pk=car_pk)
transfer = get_object_or_404(models.CarTransfer, pk=transfer_pk)
transfer.status = "approved"
transfer.save()
url = request.build_absolute_uri(reverse('transfer_preview',kwargs={'car_pk':car.pk,"transfer_pk":transfer.pk}))
models.Notification.objects.create(
user=transfer.to_dealer.user,
message=f"Car transfer request from {transfer.from_dealer} is waiting for your acceptance. <a href='{url}'> Accept</a>",
)
messages.success(request, _("Car transfer approved successfully."))
return redirect("car_detail", pk=car.pk)
def car_transfer_accept_reject(request, car_pk,transfer_pk):
car = get_object_or_404(models.Car, pk=car_pk)
transfer = get_object_or_404(models.CarTransfer, pk=transfer_pk)
status = request.GET.get("status")
if status == "rejected":
transfer.status = "reject"
transfer.active = False
messages.success(request, _("Car transfer rejected successfully."))
models.Notification.objects.create(
user=transfer.from_dealer.user,
message=f"Car transfer request from {transfer.to_dealer} is rejected.",
)
transfer.save()
elif status == "accepted":
transfer.status = "accept"
transfer.save()
success = transfer_car(car,transfer)
if success:
messages.success(request, _("Car Transfer Completed successfully."))
models.Notification.objects.create(
user=transfer.from_dealer.user,
message=f"Car transfer request from {transfer.to_dealer} is completed.",
)
return redirect("inventory_stats")
def CarTransferPreviewView(request, car_pk,transfer_pk):
transfer = get_object_or_404(models.CarTransfer, pk=transfer_pk)
if transfer.to_dealer != get_user_type(request):
return redirect("car_detail", pk=car_pk)
return render(request,'inventory/transfer_preview.html',{"transfer":transfer})
# def get_context_data(self, **kwargs):
# estimate = kwargs.get("object")
# if estimate.get_itemtxs_data():
# data = get_financial_values(estimate)
# kwargs["vat_amount"] = data["vat_amount"]
# kwargs["total"] = data["grand_total"]
# kwargs["discount_amount"] = data["discount_amount"]
# kwargs["vat"] = data["vat"]
# kwargs["car_and_item_info"] = data["car_and_item_info"]
# kwargs["additional_services"] = data["additional_services"]
# return super().get_context_data(**kwargs)
# class CarTransferView(View):
# template_name = "inventory/car_location_form.html"
# def get(self, request, *args, **kwargs):
# form = forms.CarTransferForm()
# car = models.Car.objects.filter(pk=self.kwargs["pk"])
# form.fields['to_dealer'].queryset = form.fields['to_dealer'].queryset.exclude(pk=get_user_type(request).pk)
# form.fields['car'].queryset = car
# form.initial['car'] = car.first()
# context = {"form": form}
# return render(request, self.template_name,context)
# def post(self, request, *args, **kwargs):
# form = forms.CarTransferForm(request.POST)
# if form.is_valid():
# from_dealer = get_user_type(request)
# car = form.cleaned_data['car']
# to_dealer = form.cleaned_data['to_dealer']
# remarks = form.cleaned_data['remarks']
# models.CarTransferLog.objects.create(car=car, from_dealer=from_dealer, to_dealer=to_dealer, remarks=remarks)
# # car = models.Car.objects.filter(pk=self.kwargs["pk"])
# # form.instance.car = car.first()
# # form.instance.to_dealer = get_user_type(request)
# # form.save()
# # messages.success(request, "Car transfered successfully.")
# return redirect("car_detail", pk=self.kwargs["pk"])
class CustomCardCreateView(LoginRequiredMixin, CreateView): class CustomCardCreateView(LoginRequiredMixin, CreateView):
model = models.CustomCard model = models.CustomCard
@ -2439,8 +2557,11 @@ class InvoicePreviewView(LoginRequiredMixin, DetailView):
# payments # payments
def PaymentCreateView(request, pk=None): def PaymentCreateView(request, pk):
print(pk)
invoice = InvoiceModel.objects.filter(pk=pk).first() invoice = InvoiceModel.objects.filter(pk=pk).first()
bill = BillModel.objects.filter(pk=pk).first()
model = invoice if invoice else bill
dealer = get_user_type(request) dealer = get_user_type(request)
entity = dealer.entity entity = dealer.entity
form = forms.PaymentForm() form = forms.PaymentForm()
@ -2449,92 +2570,38 @@ def PaymentCreateView(request, pk=None):
if form.is_valid(): if form.is_valid():
amount = form.cleaned_data.get("amount") amount = form.cleaned_data.get("amount")
invoice = form.cleaned_data.get("invoice") invoice = form.cleaned_data.get("invoice")
bill = form.cleaned_data.get("bill")
payment_method = form.cleaned_data.get("payment_method") payment_method = form.cleaned_data.get("payment_method")
redirect_url = 'invoice_detail' if invoice else 'bill_detail'
model = invoice if invoice else bill
if not model.is_approved():
model.mark_as_approved(user_model=entity.admin)
try: try:
vat_amount = 0 if invoice:
total_amount = 0 set_invoice_payment(dealer,entity,invoice,amount,payment_method)
elif bill:
if invoice.terms == "on_receipt": set_bill_payment(dealer,entity,bill,amount,payment_method)
for x in invoice.get_itemtxs_data()[0].all(): messages.success(request, "Payment created successfully!")
vat_amount += models.Car.objects.get( return redirect(redirect_url, pk=model.pk)
vin=x.item_model.name
).finances.vat_amount * Decimal(x.quantity)
total_amount += Decimal(x.unit_cost) * Decimal(x.quantity)
grand_total = total_amount - Decimal(vat_amount)
ledger = LedgerModel.objects.filter(
name=str(invoice.pk), entity=entity
).first()
journal = JournalEntryModel.objects.create(
posted=False,
description=f"Payment for Invoice {invoice.invoice_number}",
ledger=ledger,
locked=False,
origin="Payment",
)
credit_account = entity.get_default_coa_accounts().get(
name="Sales Revenue"
)
debit_account = None
if payment_method == "cash":
debit_account = entity.get_default_coa_accounts().get(
name="Cash", active=True
)
elif payment_method == "credit":
debit_account = entity.get_default_coa_accounts().get(
name="Accounts Receivable", active=True
)
else:
debit_account = entity.get_default_coa_accounts().get(
name="Cash in Bank", active=True
)
vat_payable_account = entity.get_default_coa_accounts().get(
name="VAT Payable", active=True
)
TransactionModel.objects.create(
journal_entry=journal,
account=debit_account, # Debit Cash
amount=amount, # Payment amount
tx_type="debit",
description="Payment Received",
)
TransactionModel.objects.create(
journal_entry=journal,
account=credit_account, # Credit Accounts Receivable
amount=grand_total, # Payment amount
tx_type="credit",
description="Payment Received",
)
if vat_amount > 0:
TransactionModel.objects.create(
journal_entry=journal,
account=vat_payable_account, # Credit VAT Payable
amount=vat_amount,
tx_type="credit",
description="VAT Payable on Invoice",
)
invoice.make_payment(amount)
invoice.save()
except Exception as e: except Exception as e:
messages.error(request, f"Error creating payment: {str(e)}") messages.error(request, f"Error creating payment: {str(e)}")
else: else:
messages.error(request, f"Invalid form data: {str(form.errors)}") messages.error(request, f"Invalid form data: {str(form.errors)}")
return redirect("invoice_detail", pk=invoice.pk) # return redirect(redirect_url, pk=model.pk)
form = forms.PaymentForm() form = forms.PaymentForm()
form.initial["amount"] = invoice.amount_due if model:
form.initial["amount"] = model.amount_due - model.amount_paid
if invoice: if isinstance(model, InvoiceModel):
form.initial["invoice"] = invoice form.initial["invoice"] = model
form.fields['bill'].widget = HiddenInput()
elif isinstance(model, BillModel):
form.initial["bill"] = model
form.fields['invoice'].widget = HiddenInput()
return render( return render(
request, "sales/payments/payment_form.html", {"invoice": invoice, "form": form} request, "sales/payments/payment_form.html", {"model": model, "form": form}
) )
def PaymentListView(request): def PaymentListView(request):
dealer = get_user_type(request) dealer = get_user_type(request)
@ -2574,7 +2641,12 @@ def payment_mark_as_paid(request, pk):
invoice.ledger.post() invoice.ledger.post()
invoice.ledger.save() invoice.ledger.save()
messages.success(request, "Payment created successfully!") messages.success(request, "Payment created successfully!")
else:
messages.error(
request,
"Invoice is not fully paid. Payment cannot be marked as paid.",
)
except Exception as e: except Exception as e:
messages.error(request, f"Error: {str(e)}") messages.error(request, f"Error: {str(e)}")
return redirect("invoice_detail", pk=invoice.pk) return redirect("invoice_detail", pk=invoice.pk)
@ -2793,15 +2865,16 @@ def fetch_notifications(request):
notifications = models.Notification.objects.filter( notifications = models.Notification.objects.filter(
user=request.user, is_read=False user=request.user, is_read=False
).order_by("-created") ).order_by("-created")
notifications_data = [ # notifications_data = [
{ # {
"id": notification.id, # "id": notification.id,
"message": notification.message, # "message": notification.message,
"created": notification.created.strftime("%Y-%m-%d %H:%M:%S"), # "created": notification.created.strftime("%Y-%m-%d %H:%M:%S"),
} # }
for notification in notifications # for notification in notifications
] # ]
return JsonResponse({"notifications": notifications_data}) # return JsonResponse({"notifications": notifications_data})
return render(request,'notifications.html',{'notifications_':notifications})
class ItemServiceCreateView(LoginRequiredMixin, SuccessMessageMixin, CreateView): class ItemServiceCreateView(LoginRequiredMixin, SuccessMessageMixin, CreateView):
@ -2897,33 +2970,273 @@ class ItemExpenseListView(ListView):
class BillListView(ListView): class BillListView(ListView):
model = ItemModel model = BillModel
template_name = "ledger/bills/bill_list.html" template_name = "ledger/bills/bill_list.html"
context_object_name = "bills" context_object_name = "bills"
paginate_by = 20
def get_queryset(self): def get_queryset(self):
dealer = get_user_type(self.request) dealer = get_user_type(self.request)
return dealer.entity.get_bills() qs = dealer.entity.get_bills()
return qs
class BillDetailView(LoginRequiredMixin, DetailView):
class BillCreateView(LoginRequiredMixin,SuccessMessageMixin,CreateView):
model = BillModel model = BillModel
form_class = BillModelCreateForm template_name = "ledger/bills/bill_detail.html"
template_name = "ledger/bills/bill_form.html" context_object_name = "bill"
def get_context_data(self, **kwargs):
bill = kwargs.get("object")
if bill.get_itemtxs_data():
txs = bill.get_itemtxs_data()[0]
car_and_item_info = [
{
"car": models.Car.objects.get(vin=x.item_model.name),
"total": models.Car.objects.get(
vin=x.item_model.name
).finances.cost_price
* Decimal(x.quantity),
"itemmodel": x,
}
for x in txs
]
grand_total = sum(
Decimal(
models.Car.objects.get(vin=x.item_model.name).finances.cost_price
)
* Decimal(x.quantity)
for x in txs
)
kwargs["car_and_item_info"] = car_and_item_info
kwargs["grand_total"] = grand_total
return super().get_context_data(**kwargs)
class InReviewBillView(LoginRequiredMixin, UpdateView):
model = BillModel
form_class = InReviewBillModelUpdateForm
template_name = "ledger/bills/bill_update_form.html"
success_url = reverse_lazy("bill_list") success_url = reverse_lazy("bill_list")
success_message = _("Bill created successfully.") success_message = _("Bill updated successfully.")
context_object_name = "bill"
def get_form_kwargs(self): def get_form_kwargs(self):
dealer = get_user_type(self.request)
kwargs = super().get_form_kwargs() kwargs = super().get_form_kwargs()
dealer = get_user_type(self.request)
kwargs["entity_model"] = dealer.entity kwargs["entity_model"] = dealer.entity
kwargs["user_model"] = dealer.entity.admin
return kwargs return kwargs
def get_success_url(self):
return reverse_lazy("bill_detail", kwargs={"pk": self.kwargs["pk"]})
def form_valid(self, form):
dealer = get_user_type(self.request)
form.instance.entity = dealer.entity
self.object.mark_as_review()
return super().form_valid(form)
class ApprovedBillModelView(LoginRequiredMixin, UpdateView):
model = BillModel
form_class = ApprovedBillModelUpdateForm
template_name = "ledger/bills/bill_update_form.html"
success_url = reverse_lazy("bill_list")
success_message = _("Bill updated successfully.")
context_object_name = "bill"
def get_form_kwargs(self):
kwargs = super().get_form_kwargs()
dealer = get_user_type(self.request)
kwargs["entity_model"] = dealer.entity
kwargs["user_model"] = dealer.entity.admin
return kwargs
def get_success_url(self):
return reverse_lazy("bill_detail", kwargs={"pk": self.kwargs["pk"]})
def form_valid(self, form):
dealer = get_user_type(self.request)
form.instance.entity = dealer.entity
if not self.object.is_approved():
self.object.mark_as_approved(user_model=dealer.entity.admin)
return super().form_valid(form)
# def form_valid(self, form): def bill_mark_as_approved(request,pk):
bill = get_object_or_404(BillModel,pk=pk)
if request.method == "POST":
dealer = get_user_type(request)
if bill.is_approved():
messages.error(request, _("Bill is already approved."))
return redirect("bill_detail",pk=bill.pk)
bill.mark_as_approved(user_model=dealer.entity.admin)
bill.save()
messages.success(request, _("Bill marked as approved successfully."))
return redirect("bill_detail",pk=bill.pk)
def bill_mark_as_paid(request,pk):
bill = get_object_or_404(BillModel,pk=pk)
if request.method == "POST":
dealer = get_user_type(request)
if bill.is_paid():
messages.error(request, _("Bill is already paid."))
return redirect("bill_detail",pk=bill.pk)
if bill.amount_due == bill.amount_paid:
bill.mark_as_paid(user_model=dealer.entity.admin)
bill.save()
bill.ledger.lock_journal_entries()
bill.ledger.post_journal_entries()
bill.ledger.post()
bill.ledger.save()
messages.success(request, _("Bill marked as paid successfully."))
else:
messages.error(request, _("Amount paid is not equal to amount due."))
return redirect("bill_detail",pk=bill.pk)
# def get_context_data(self, **kwargs):
# dealer = get_user_type(self.request) # dealer = get_user_type(self.request)
# form.instance.entity = dealer.entity # context = super().get_context_data(**kwargs)
# return super().form_valid(form) # context['entity_model'] = dealer.entity
# context['user_model'] = dealer.entity.admin
# return context
# class BillCreateView(LoginRequiredMixin, SuccessMessageMixin, CreateView):
# model = BillModel
# form_class = BillModelCreateForm
# template_name = "ledger/bills/bill_form.html"
# success_url = reverse_lazy("bill_list")
# success_message = _("Bill created successfully.")
# def get_form_kwargs(self):
# dealer = get_user_type(self.request)
# kwargs = super().get_form_kwargs()
# kwargs["entity_model"] = dealer.entity
# return kwargs
# def form_valid(self, form):
# dealer = get_user_type(self.request)
# form.instance.entity = dealer.entity
# ledger = dealer.entity.create_ledger(
# name=f"Bill for Vendor {form.instance.vendor.vendor_name}", posted=True
# )
# form.instance.ledger = ledger
# return super().form_valid(form)
@login_required
def bill_create(request):
dealer = get_user_type(request)
entity = dealer.entity
if request.method == "POST":
data = json.loads(request.body)
vendor_id = data.get("vendor")
terms = data.get("terms")
vendor = entity.get_vendors().filter(pk=vendor_id).first()
items = data.get("item", [])
quantities = data.get("quantity", [])
if not all([items, quantities]):
return JsonResponse(
{"status": "error", "message": "Items and Quantities are required"},
status=400,
)
if isinstance(quantities, list):
if "0" in quantities:
return JsonResponse(
{"status": "error", "message": "Quantity must be greater than zero"}
)
else:
if int(quantities) <= 0:
return JsonResponse(
{"status": "error", "message": "Quantity must be greater than zero"}
)
bill = entity.create_bill(vendor_model=vendor, terms=terms)
if isinstance(items, list):
item_quantity_map = {}
for item, quantity in zip(items, quantities):
if item in item_quantity_map:
item_quantity_map[item] += int(quantity)
else:
item_quantity_map[item] = int(quantity)
item_list = list(item_quantity_map.keys())
quantity_list = list(item_quantity_map.values())
items_list = [
{"item_id": item_list[i], "quantity": quantity_list[i]}
for i in range(len(item_list))
]
items_txs = []
for item in items_list:
item_instance = ItemModel.objects.get(pk=item.get("item_id"))
car = models.Car.objects.get(vin=item_instance.name)
quantity = Decimal(item.get("quantity"))
items_txs.append(
{
"item_number": item_instance.item_number,
"quantity": quantity,
"unit_cost": car.finances.cost_price,
"total_amount": car.finances.cost_price * quantity,
}
)
bill_itemtxs = {
item.get("item_number"): {
"unit_cost": item.get("unit_cost"),
"quantity": item.get("quantity"),
"total_amount": item.get("total_amount"),
}
for item in items_txs
}
else:
item = entity.get_items_all().filter(pk=items).first()
instance = models.Car.objects.get(vin=item.name)
bill_itemtxs = {
item.item_number: {
"unit_cost": instance.finances.cost_price,
"quantity": Decimal(quantities),
"total_amount": instance.finances.cost_price * Decimal(quantities),
}
}
bill_itemtxs = bill.migrate_itemtxs(
itemtxs=bill_itemtxs,
commit=True,
operation=BillModel.ITEMIZE_APPEND,
)
url = reverse("bill_detail", kwargs={"pk": bill.pk})
return JsonResponse(
{
"status": "success",
"message": "Bill created successfully!",
"url": f"{url}",
}
)
form = forms.BillModelCreateForm(entity_model=entity)
form.initial.update(
{
"cash_account": entity.get_default_coa_accounts().get(name="Cash"),
"prepaid_account": entity.get_default_coa_accounts().get(
name="Prepaid Expenses"
),
"unearned_account": entity.get_default_coa_accounts().get(
name="Accounts Payable"
),
}
)
car_list = models.Car.objects.filter(dealer=dealer)
context = {
"form": form,
"items": [
{
"car": x,
"product": entity.get_items_all()
.filter(item_role=ItemModel.ITEM_ROLE_PRODUCT, name=x.vin)
.first(),
}
for x in car_list
],
}
return render(request, "ledger/bills/bill_form.html", context)
def BillDeleteView(request, pk):
bill = get_object_or_404(BillModel, pk=pk)
bill.delete()
return redirect("bill_list")
class SubscriptionPlans(ListView): class SubscriptionPlans(ListView):

View File

@ -79,7 +79,7 @@
</script> </script>
{% block extra_js %}{% endblock extra_js %} {% block customJS %}{% endblock customJS %}
<script> <script>

View File

@ -71,7 +71,6 @@
{% endblock %} {% endblock %}
<!-- ===============================================--> <!-- ===============================================-->
<!-- JavaScripts--> <!-- JavaScripts-->
<!-- ===============================================--> <!-- ===============================================-->
@ -90,8 +89,10 @@
<script src="{% static 'js/main.js' %}"></script> <script src="{% static 'js/main.js' %}"></script>
<script src="{% static 'vendors/mapbox-gl/mapbox-gl.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="https://unpkg.com/@turf/turf@6/turf.min.js"></script>
<script src="https://unpkg.com/htmx.org@2.0.4"></script>
<script src="{% static 'vendors/swiper/swiper-bundle.min.js' %}"></script> <script src="{% static 'vendors/swiper/swiper-bundle.min.js' %}"></script>
<script src="{% static 'vendors/flatpickr/flatpickr.min.js' %}"></script> <script src="{% static 'vendors/flatpickr/flatpickr.min.js' %}"></script>
<script> <script>

View File

@ -15,7 +15,7 @@
{% if not notification.is_read %} {% if not notification.is_read %}
<p class="fs-9 text-body-highlight"><span class="far fa-envelope text-success-dark fs-8 me-1"></span><span class="me-1">{{ notification.message }}</span> <span class="ms-2 text-body-tertiary text-opacity-85 fw-bold fs-10 text-end">{{ notification.created|timesince }}</span></p> <p class="fs-9 text-body-highlight"><span class="far fa-envelope text-success-dark fs-8 me-1"></span><span class="me-1">{{ notification.message }}</span> <span class="ms-2 text-body-tertiary text-opacity-85 fw-bold fs-10 text-end">{{ notification.created|timesince }}</span></p>
{% else %} {% else %}
<p class="fs-9 text-body-highlight"><span class="far fa-envelope-open text-danger-dark fs-8 me-1"></span><span>{{ notification.message }}</span> <span class="ms-2 text-body-tertiary text-opacity-85 fw-bold fs-10 text-end">{{ notification.created|timesince }}</span></p> <p class="fs-9 text-body-highlight"><span class="far fa-envelope-open text-danger-dark fs-8 me-1"></span><span>{{ notification.message|safe }}</span> <span class="ms-2 text-body-tertiary text-opacity-85 fw-bold fs-10 text-end">{{ notification.created|timesince }}</span></p>
{% endif %} {% endif %}
<p class="text-body-secondary fs-9 mb-0"><span class="me-1 far fa-clock"></span>{{ notification.created }}</p> <p class="text-body-secondary fs-9 mb-0"><span class="me-1 far fa-clock"></span>{{ notification.created }}</p>
</div> </div>

View File

@ -230,6 +230,13 @@
</div> </div>
</a> </a>
</li> </li>
<li class="nav-item">
<a class="nav-link" href="{% url 'bill_list' %}">
<div class="d-flex align-items-center">
<span class="nav-link-icon"><span data-feather="package"></span></span><span class="nav-link-text">{% trans 'bills'|capfirst %}</span>
</div>
</a>
</li>
</ul> </ul>
</div> </div>
</div> </div>
@ -293,11 +300,8 @@
</label> </label>
</div> </div>
</li> </li>
<li class="nav-item dropdown"> {% include "notifications.html" %}
<a class="nav-link" href="{% url 'notifications_history' %}" style="min-width: 2.25rem;" role="button" data-bs-toggle="dropdown" aria-haspopup="true" aria-expanded="false" data-bs-auto-close="outside">
<span class="d-block" style="height: 20px; width: 20px;"><span data-feather="bell" style="height: 20px; width: 20px;"></span></span>
</a>
</li>
<li class="nav-item dropdown"> <li class="nav-item dropdown">
<a class="nav-link dropdown-toggle" href="#" id="languageDropdown" role="button" data-bs-toggle="dropdown" aria-expanded="false" data-bs-auto-close="outside" aria-haspopup="true"> <a class="nav-link dropdown-toggle" href="#" id="languageDropdown" role="button" data-bs-toggle="dropdown" aria-expanded="false" data-bs-auto-close="outside" aria-haspopup="true">
{% if request.LANGUAGE_CODE == 'ar' %} {% if request.LANGUAGE_CODE == 'ar' %}

View File

@ -93,18 +93,21 @@
</tr> </tr>
{% endif %} {% endif %}
<tr> <tr>
<th>{% trans 'Location'|capfirst %}</th> <th>{% trans 'Location'|capfirst %}</th>
<td> <td>
{% if car.location %} {% if car.location.is_owner_showroom %} {% trans 'Our Showroom' %} {% else %} {{ car.location.showroom.get_local_name }} {% endif %} {% if car.finances and not car.get_transfer %}
<a href="{% url 'transfer' car.location.pk %}" class="btn btn-phoenix-danger btn-sm"> {% if car.location %} {% if car.location.is_owner_showroom %} {% trans 'Our Showroom' %} {% else %} {{ car.location.showroom.get_local_name }} {% endif %}
{% trans "transfer"|capfirst %} <a href="{% url 'transfer' car.location.pk %}" class="btn btn-phoenix-danger btn-sm">
</a> {% trans "transfer"|capfirst %}
{% else %} {% trans "No location available." %} </a>
<a href="{% url 'add_car_location' car.pk %}" class="btn btn-phoenix-success btn-sm ms-2"> {% else %} {% trans "No location available." %}
{% trans "Add" %} <a href="{% url 'add_car_location' car.pk %}" class="btn btn-phoenix-success btn-sm ms-2">
</a> {% trans "Add" %}
</a>
{% endif %}
{% endif %}
</td> </td>
{% endif %}
</tr> </tr>
</table> </table>
</div> </div>
@ -215,6 +218,7 @@
</div> </div>
</div> </div>
</div> </div>
{% if car.status != 'transfer' %}
<div class="card rounded shadow d-flex align-content-center mt-3"> <div class="card rounded shadow d-flex align-content-center mt-3">
<p class="card-header rounded-top fw-bold">{% trans 'Reservations Details' %}</p> <p class="card-header rounded-top fw-bold">{% trans 'Reservations Details' %}</p>
<div class="card-body"> <div class="card-body">
@ -266,6 +270,55 @@
</div> </div>
</div> </div>
</div> </div>
{% endif %}
<!-- Transfer Table -->
{% if car.status == 'transfer' and car.get_transfer %}
<div class="card rounded shadow d-flex align-content-center mt-3">
<p class="card-header rounded-top fw-bold">{% trans 'Transfer Details' %}</p>
<div class="card-body">
<div class="table-responsive scrollbar mb-3">
<table class="table table-sm fs-9 mb-0 overflow-hidden">
<thead>
<tr>
<th>{% trans "Action" %}</th>
<th>{% trans "Status" %}</th>
<th>{% trans "From Showroom" %}</th>
<th>{% trans "To Showroom" %}</th>
<th>{% trans 'Date' %}</th>
<th></th>
</tr>
</thead>
<tbody>
<tr>
<td><span class="badge badge-phoenix badge-phoenix-info">Transfer</span></td>
<td>
{% if car.get_transfer.status == "draft" %}
<span class="badge badge-phoenix badge-phoenix-warning">
waiting for approval
</span>
{% elif car.get_transfer.status == "approved" %}
<span class="badge badge-phoenix badge-phoenix-info">
waiting for dealer acceptance
</span>
{% endif %}
</td>
<td>{{ car.get_transfer.from_dealer|title }}</td>
<td>{{ car.get_transfer.to_dealer|title }}</td>
<td>{{ car.get_transfer.transfer_date|date:"Y-m-d" }}</td>
<td>
{% if car.get_transfer.status == "draft" %}
<a class="btn btn-sm btn-phoenix-success" href="{% url 'transfer_detail' car.get_transfer.pk %}">Approve</a>
{% endif %}
</td>
</tr>
</tbody>
</table>
</div>
</div>
</div>
{% endif %}
</div> </div>
</div> </div>
</div> </div>

View File

@ -1,122 +1,273 @@
{% extends 'base.html' %} {% extends "base.html" %}
{% load i18n %} {% load i18n %}
{% block title %} {% block title %}{{ _("View Bill") }}{% endblock title %}
{{ page_title }}
{% endblock %}
{% block content %} {% block content %}
<!-- Delete Modal --> <div class="modal fade" id="mark_as_approved_Modal" tabindex="-1" aria-labelledby="confirmModalLabel" aria-hidden="true">
<div class="modal fade" id="deleteModal" data-bs-backdrop="static" data-bs-keyboard="false" tabindex="-1" aria-labelledby="deleteModalLabel" aria-hidden="true"> <div class="modal-dialog modal-sm">
<div class="modal-dialog modal-sm"> <div class="modal-content">
<div class="modal-content rounded"> <div class="modal-header bg-primary">
<div class="modal-body d-flex justify-content-center"> <h5 class="modal-title text-light" id="confirmModalLabel">{% trans 'Confirm' %}</h5>
<h1 class="text-danger me-2"><i class="bi bi-exclamation-diamond-fill"></i></h1> <button type="button" class="btn-close" data-bs-dismiss="modal" aria-label="Close"></button>
<span class="text-danger">{% trans 'Are you sure you want to delete this account?' %}</span> </div>
</div> <div class="modal-body">
<div class="btn-group"> {% trans 'Are you sure' %}
<button type="button" class="btn btn-sm btn-secondary" data-bs-dismiss="modal">{% trans 'No' %}</button> <div class="modal-footer">
<div class="btn btn-sm btn-danger"> <button type="button"
<form action="{% url 'account_delete' account.pk %}" method="post"> class="btn btn-sm btn-danger"
{% csrf_token %} data-bs-dismiss="modal">
<button type="submit" class="btn btn-sm btn-danger">{% trans 'Yes' %}</button> {% trans 'No' %}
</button>
<form id="confirmForm" method="POST" action="{% url 'bill_mark_as_approved' bill.pk %}" class="d-inline">
{% csrf_token %}
<button type="submit" class="btn btn-success btn-sm">{% trans "Yes" %}</button>
</form>
</div>
</div>
</div>
</div>
</div>
<!-- ============================================-->
<div class="modal fade" id="mark_as_paid_Modal" tabindex="-1" aria-labelledby="confirmModalLabel" aria-hidden="true">
<div class="modal-dialog modal-sm">
<div class="modal-content">
<div class="modal-header bg-primary">
<h5 class="modal-title text-light" id="confirmModalLabel">{% trans 'Confirm' %}</h5>
<button type="button" class="btn-close" data-bs-dismiss="modal" aria-label="Close"></button>
</div>
<div class="modal-body">
{% trans 'Are you sure' %}
<div class="modal-footer">
<button type="button"
class="btn btn-sm btn-danger"
data-bs-dismiss="modal">
{% trans 'No' %}
</button>
<form id="confirmForm" method="POST" action="{% url 'bill_mark_as_paid' bill.pk %}" class="d-inline">
{% csrf_token %}
<button type="submit" class="btn btn-success btn-sm">{% trans "Yes" %}</button>
</form> </form>
</div> </div>
</div> </div>
</div> </div>
</div> </div>
</div> </div>
<!-- ============================================-->
<div class="row my-5"> <!-- <section> begin ============================-->
<div class="card rounded"> <section class="pt-5 pb-9 bg-body-emphasis dark__bg-gray-1200 border-top">
<div class="card-header"> <div class="row-small mt-3">
<p class="mb-0">{{ header_title|upper }}</p> <div class="d-flex justify-content-between align-items-end mb-4">
</div> <h2 class="mb-0">{% trans 'Bill' %}</h2>
<div class="card-body"> <div class="d-flex align-items-center gap-2">
<div class="row"> {% if bill.is_draft %}
<div class="col-md-6"> <a href="{% url 'in_review_bill' bill.pk %}" class="btn btn-phoenix-primary"><span class="d-none d-sm-inline-block">{% trans 'Review Bill' %}</span></a>
<p> {% endif %}
<strong>{{ _('Account Name') }}:</strong> {{ account.name }} {% if bill.is_review %}
</p> <button id="mark_bill_as_approved" class="btn btn-phoenix-secondary" data-bs-toggle="modal" data-bs-target="#mark_as_approved_Modal"><span class="d-none d-sm-inline-block">{% trans 'Mark as Approved' %}</span></button>
<p> {% endif %}
<strong>{{ _('Account Code') }}:</strong> {{ account.code }} {% if bill.is_approved %}
</p> <a href="{% url 'payment_create' bill.pk %}" class="btn btn-phoenix-primary"><span class="d-none d-sm-inline-block">{% trans 'Record Payment' %}</span></a>
</div> {% endif %}
<div class="col-md-6"> {% if bill.is_approved %}
<p> <button id="mark_bill_as_paid" class="btn btn-phoenix-secondary" data-bs-toggle="modal" data-bs-target="#mark_as_paid_Modal"><span class="d-none d-sm-inline-block">{% trans 'Mark as Paid' %}</span></button>
<strong>{{ _('Balance Type') }}:</strong> {{ account.balance_type }} {% endif %}
</p> </div>
<p> </div>
<strong>{{ _('Active') }}:</strong> {{ account.active }}
</p> <!-- ============================================-->
</div> <div class="card mb-5">
</div> <div class="card-body">
<div class="row"> <div class="row g-4 g-xl-1 g-xxl-3 justify-content-between">
<div class="col"> <div class="col-sm-auto">
<table class="table is-fullwidth is-narrow is-striped is-bordered django-ledger-table-bottom-margin-75"> <div class="d-sm-block d-inline-flex d-md-flex flex-xl-column flex-xxl-row align-items-center align-items-xl-start align-items-xxl-center">
<tr> <div class="d-flex bg-success-subtle rounded flex-center me-3 mb-sm-3 mb-md-0 mb-xl-3 mb-xxl-0" style="width:32px; height:32px"><span class="text-success-dark" data-feather="dollar-sign" style="width:24px; height:24px"></span></div>
<th class="has-text-centered">{{ _('JE Number') }}</th> <div>
<th class="has-text-centered">{{ _('Date') }}</th> <p class="fw-bold mb-1">{% trans 'Paid Amount' %}</p>
<th class="has-text-centered">{{ _('Debit') }}</th> <h4 class="fw-bolder text-nowrap {% if bill.is_paid %}text-success{% endif %}">${{bill.amount_paid}}</h4>
<th class="has-text-centered">{{ _('Credit') }}</th> <h6 class="fw-bolder text-nowrap">Owned <span class="fw-semibold text-nowrap text-success">${{bill.get_amount_open}}</span></h6>
<th class="has-text-centered">{{ _('Description') }}</th> <div class="progress" style="height:17px">
<th class="has-text-centered">{{ _('Unit') }}</th> <div class="progress-bar fw-semibold bg-{% if bill.get_progress_percent < 100 %}secondary{% else %}success{% endif %} rounded-2" role="progressbar" style="width: {{bill.get_progress_percent}}%" aria-valuenow="{{bill.get_progress_percent}}" aria-valuemin="0" aria-valuemax="100">{{bill.get_progress_percent}}%</div>
<th class="has-text-centered">{{ _('Actions') }}</th> </div>
</tr> </div>
</div>
{% for tx in account.transactionmodel_set.all %} </div>
<tr class="has-text-centered"> {% if 'net' in bill.terms %}
<td>{{ tx.journal_entry.je_number }}</td> <div class="col-sm-auto">
<td>{{ tx.journal_entry.timestamp }}</td> <div class="d-sm-block d-inline-flex d-md-flex flex-xl-column flex-xxl-row align-items-center align-items-xl-start align-items-xxl-center border-start-sm ps-sm-5 border-translucent">
<td> <div class="d-flex bg-primary-subtle rounded flex-center me-3 mb-sm-3 mb-md-0 mb-xl-3 mb-xxl-0" style="width:32px; height:32px"><span class="text-primary-dark" data-feather="layout" style="width:24px; height:24px"></span></div>
{% if tx.tx_type == 'debit' %} <div>
${{ tx.amount }} <p class="fw-bold mb-1"></p>
{% endif %} <div class="fs-9 text-body-secondary fw-semibold mb-0 d-sm-block d-inline-flex d-md-flex flex-xl-column ">
</td> <table>
<td> <tr>
{% if tx.tx_type == 'credit' %} <td>{% trans 'Terms' %}:</td>
${{ tx.amount }} <td><span class="badge rounded bg-success">{{bill.bill_number}}</span></td>
{% endif %} </tr>
</td> <tr>
<td>{{ tx.description }}</td> <td>{% trans 'Date Due' %}:</td>
<td>{{ tx.journal_entry.entity_unit.name }}</td> <td><span class="badge rounded bg-success">{{bill.date_due}}</span></td>
<td> </tr>
<div class="btn-reveal-trigger position-static"> <tr>
<button class="btn btn-sm dropdown-toggle dropdown-caret-none transition-none btn-reveal fs-10" type="button" data-bs-toggle="dropdown" data-boundary="window" aria-haspopup="true" aria-expanded="false" data-bs-reference="parent"><span class="fas fa-ellipsis-h fs-10"></span></button> <td>{% trans 'Due in Days' %}:</td>
<div class="dropdown-menu dropdown-menu-end py-2"> <td>
<a class="dropdown-item" href="{% url 'payment_details' tx.journal_entry.pk %}">{% trans 'view'|capfirst %}</a> <span class="badge rounded bg-success">{{bill.due_in_days}}</span>
</td>
</tr>
<tr>
<td>{% trans 'Is Past Due' %}:</td>
<td>
{% if bill.is_past_due %}
<span class="badge rounded bg-danger">{% trans 'Yes' %}</span>
{% else %}
<span class="badge rounded bg-success">{% trans 'No' %}</span>
{% endif %}
</td>
</tr>
</table>
</div>
</div> </div>
</div> </div>
</td> </div>
</tr> {% endif %}
{% endfor %} <div class="col-sm-auto">
<tr class="has-text-weight-bold"> <div class="d-sm-block d-inline-flex d-md-flex flex-xl-column flex-xxl-row align-items-center align-items-xl-start align-items-xxl-center border-start-sm ps-sm-5 border-translucent">
<td></td> <div class="d-flex bg-primary-subtle rounded flex-center me-3 mb-sm-3 mb-md-0 mb-xl-3 mb-xxl-0" style="width:32px; height:32px"><span class="text-primary-dark" data-feather="layout" style="width:24px; height:24px"></span></div>
<td class="has-text-right">Total</td> <div>
<td class="has-text-centered">${{ total_debits }}</td> <p class="fw-bold mb-1">{% trans 'Due Amount' %}</p>
<td class="has-text-centered">${{ total_credits }}</td> {% if bill.is_paid %}
<td></td> <s><h4 class="fw-bolder text-nowrap">${{bill.amount_due}} </h4></s>
<td></td> {% else %}
<td></td> <h4 class="fw-bolder text-nowrap">${{bill.amount_due}} </h4>
</tr> {% endif %}
</table> </div>
</div>
</div>
</div>
</div>
</div>
<!-- <section> begin ============================-->
<div class="bg-body dark__bg-gray-1100 p-4 mb-4 rounded-2 text-body-tertiary">
<div class="row g-4">
<div class="col-12 col-lg-3">
<div class="row g-4 g-lg-2">
<div class="col-12 col-sm-6 col-lg-12">
<div class="row align-items-center g-0">
<div class="col-auto col-lg-6 col-xl-5">
<h6 class="mb-0 me-3">{% trans "Bill Number" %} :</h6>
</div>
<div class="col-auto col-lg-6 col-xl-7">
<p class="fs-9 text-body-secondary fw-semibold mb-0">#{{bill.bill_number}}</p>
</div>
</div>
</div>
<div class="col-12 col-sm-6 col-lg-12">
<div class="row align-items-center g-0">
<div class="col-auto col-lg-6 col-xl-5">
<h6 class="me-3">{% trans "Bill Date" %} :</h6>
</div>
<div class="col-auto col-lg-6 col-xl-7">
<p class="fs-9 text-body-secondary fw-semibold mb-0">{{bill.created}}</p>
</div>
</div>
</div>
</div>
</div>
<div class="col-12 col-sm-6 col-lg-5">
<div class="row align-items-center g-0">
<div class="col-auto col-lg-6 col-xl-5">
<h6 class="mb-2 me-3">{% trans "Customer Name" %} :</h6>
<p class="fs-9 text-body-secondary fw-semibold mb-0">{{bill.vendor.vendor_name}}</p>
</div>
</div>
</div>
<div class="col-12 col-sm-6 col-lg-4">
<div class="row g-4">
<div class="col-12 col-lg-6">
<h6 class="mb-2"> {% trans "bill Status" %} :</h6>
<div class="fs-9 text-body-secondary fw-semibold mb-0">
{% if bill.bill_status == 'draft' %}
<span class="badge text-bg-warning">{% trans "Draft" %}</span>
{% elif bill.bill_status == 'in_review' %}
<span class="badge text-bg-info">{% trans "In Review" %}</span>
{% elif bill.bill_status == 'approved' %}
<span class="badge text-bg-info">{% trans "Approved" %}</span>
{% elif bill.bill_status == 'declined' %}
<span class="badge text-bg-danger">{% trans "Declined" %}</span>
{% elif bill.bill_status == 'paid' %}
<span class="badge text-bg-success">{% trans "Paid" %}</span>
{% endif %}
</div>
</div>
</div>
</div>
</div>
</div>
<div class="px-0">
<div class="table-responsive scrollbar">
<table id="bill-table" class="table fs-9 text-body mb-0">
<thead class="bg-body-secondary">
<tr>
<th scope="col" style="width: 24px;">#</th>
<th scope="col" style="min-width: 260px;">{% trans "Item" %}</th>
<th scope="col" style="min-width: 60px;">{% trans "Quantity" %}</th>
<th scope="col" style="min-width: 60px;">{% trans "Unit Price" %}</th>
<th scope="col" style="min-width: 60px;">{% trans "Total" %}</th>
</tr>
</thead>
<tbody>
{% for item in car_and_item_info %}
<tr>
<td class="">{{forloop.counter}}</td>
<td class="">{{item.car.id_car_model}}</td>
<td class="align-middle">{{item.itemmodel.quantity}}</td>
<td class="align-middle ps-5">{{item.car.finances.cost_price}}</td>
<td class="align-middle text-body-tertiary fw-semibold">{{item.total}}</td>
</tr>
{% endfor %}
<tr class="bg-body-secondary total-sum">
<td class="align-middle ps-4 fw-bolder text-body-highlight" colspan="4">{% trans "Grand Total" %}</td>
<td class="align-middle text-start fw-bolder">
<span id="grand-total">{{grand_total}}</span>
</td>
</tr>
</tbody>
</table>
</div>
</div>
</div>
</div> </div>
</div>
</div>
<div class="card-footer d-flex">
<a class="btn btn-sm btn-primary me-1" href="{% url 'account_update' account.pk %}">
<!-- <i class="bi bi-pencil-square"></i> -->
{{ _('Edit') }}
</a>
<a class="btn btn-sm btn-danger me-1" data-bs-toggle="modal" data-bs-target="#deleteModal">
<!-- <i class="bi bi-trash-fill"></i> -->
{{ _('Delete') }}
</a>
<a class="btn btn-sm btn-secondary" href="{% url 'account_list' %}">
<!-- <i class="bi bi-arrow-left-square-fill"></i> -->
{% trans 'Back to List' %}
</a>
</div>
</div>
</div>
{% endblock %} {% endblock %}
{% block customJS %}
<script>
function calculateTotals() {
const table = document.getElementById('estimate-table');
const rows = table.getElementsByTagName('tbody')[0].rows;
let grandTotal = 0;
for (let row of rows) {
// Ensure the row has the expected number of cells
if (row.cells.length >= 5) {
const quantity = parseFloat(row.cells[2].textContent); // Quantity column
const unitPrice = parseFloat(row.cells[3].textContent); // Unit Price column
if (!isNaN(quantity) && !isNaN(unitPrice)) {
const total = quantity * unitPrice;
row.cells[4].textContent = total.toFixed(2); // Populate Total column
grandTotal += total; // Add to grand total
}
}
}
// Display the grand total
document.getElementById('grand-total').textContent = grandTotal.toFixed(2);
}
// Run the function on page load
window.onload = calculateTotals;
</script>
{% endblock %}

View File

@ -1,38 +1,158 @@
{% extends "base.html" %} {% extends "base.html" %}
{% load i18n %}
{% load crispy_forms_filters %} {% load crispy_forms_filters %}
{% block title %}{% trans "account" %}{% endblock title %} {% load i18n static %}
{% block title %}{{ _("Create Bill") }}{% endblock title %}
{% block content %} {% block content %}
<div class="row my-5"> <div class="row mt-4">
<!-- Display Form Errors --> <h3 class="text-center">{% trans "Create Bill" %}</h3>
<div class="card shadow rounded"> <form id="mainForm" method="post" class="needs-validation">
<div class="card-header bg-primary text-white"> {% csrf_token %}
<p class="mb-0"> <div class="row g-3">
{% if account.created %} {{ form|crispy }}
<!--<i class="bi bi-pencil-square"></i>--> <div class="row mt-5">
{{ _("Edit Account") }} <div id="formrow">
{% else %} <h3 class="text-start">Unit Items</h3>
<!--<i class="bi bi-person-plus"></i> --> <div class="form-row row g-3 mb-3 mt-5">
{{ _("Add Account") }} <div class="mb-2 col-sm-2">
{% endif %} <select class="form-control item" name="item[]" required>
</p> {% for item in items %}
</div> <option value="{{ item.product.pk }}">{{ item.car.id_car_model }}</option>
<div class="card-body"> {% endfor %}
<form method="post" class="form" novalidate> </select>
{% csrf_token %} </div>
{{ form|crispy }} <div class="mb-2 col-sm-2">
{% for error in form.errors %} <input class="form-control quantity" type="number" placeholder="Quantity" name="quantity[]" required>
<div class="text-danger">{{ error }}</div> </div>
{% endfor %} <div class="mb-2 col-sm-1">
<div class="d-flex justify-content-end"> <button class="btn btn-danger removeBtn">Remove</button>
<button class="btn btn-sm btn-success me-1" type="submit"> </div>
{{ _("Save") }} </div>
</button>
<a href="{{request.META.HTTP_REFERER}}" class="btn btn-sm btn-danger">{% trans "Cancel"|capfirst %}</a>
</div> </div>
</form> <div class="col-12">
<button id="addMoreBtn" class="btn btn-primary">Add More</button>
</div>
</div>
</div> </div>
</div>
<!-- Buttons -->
<div class="mt-5 text-center">
<button type="submit" class="btn btn-success me-2" {% if not items %}disabled{% endif %}>{% trans "Save" %}</button>
<a href="{% url 'bill_list' %}" class="btn btn-secondary">{% trans "Cancel" %}</a>
</div>
</form>
</div> </div>
{% endblock %} {% endblock content %}
{% block customJS %}
<script>
const Toast = Swal.mixin({
toast: true,
position: "top-end",
showConfirmButton: false,
timer: 2000,
timerProgressBar: false,
didOpen: (toast) => {
toast.onmouseenter = Swal.stopTimer;
toast.onmouseleave = Swal.resumeTimer;
}
});
// Add new form fields
document.getElementById('addMoreBtn').addEventListener('click', function(e) {
e.preventDefault();
const formrow = document.getElementById('formrow');
const newForm = document.createElement('div');
newForm.className = 'form-row row g-3 mb-3 mt-5';
newForm.innerHTML = `
<div class="mb-2 col-sm-2">
<select class="form-control item" name="item[]" required>
{% for item in items %}
<option value="{{ item.product.pk }}">{{ item.car.id_car_model }}</option>
{% endfor %}
</select>
</div>
<div class="mb-2 col-sm-2">
<input class="form-control quantity" type="number" placeholder="Quantity" name="quantity[]" required>
</div>
<div class="mb-2 col-sm-1">
<button class="btn btn-danger removeBtn">Remove</button>
</div>
`;
formrow.appendChild(newForm);
// Add remove button functionality
newForm.querySelector('.removeBtn').addEventListener('click', function() {
newForm.remove();
});
});
// Add remove button functionality to the initial form
document.querySelectorAll('.form-row').forEach(row => {
row.querySelector('.removeBtn').addEventListener('click', function() {
row.remove();
});
});
// Handle form submission
document.getElementById('mainForm').addEventListener('submit', async function(e) {
e.preventDefault();
/*const titleInput = document.querySelector('[name="title"]');
if (titleInput.value.length < 5) {
notify("error", "Customer billte Title must be at least 5 characters long.");
return; // Stop form submission
}*/
// Collect all form data
const formData = {
csrfmiddlewaretoken: document.querySelector('[name=csrfmiddlewaretoken]').value,
vendor: document.querySelector('[name=vendor]').value,
xref: document.querySelector('[name=xref]').value,
date_draft: document.querySelector('[name=date_draft]').value,
terms: document.querySelector('[name=terms]').value,
item: [],
quantity: []
};
// Collect multi-value fields (e.g., item[], quantity[])
document.querySelectorAll('[name="item[]"]').forEach(input => {
formData.item.push(input.value);
});
document.querySelectorAll('[name="quantity[]"]').forEach(input => {
formData.quantity.push(input.value);
});
try {
// Send data to the server using fetch
const response = await fetch("{% url 'bill_create' %}", {
method: 'POST',
headers: {
'X-CSRFToken': formData.csrfmiddlewaretoken,
'Content-Type': 'application/json',
},
body: JSON.stringify(formData)
});
// Parse the JSON response
const data = await response.json();
// Handle the response
if (data.status === "error") {
notify("error", data.message); // Display an error message
} else if (data.status === "success") {
notify("success","Bill created successfully");
setTimeout(() => {
window.location.assign(data.url); // Redirect to the provided URL
}, 1000);
} else {
notify("error","Unexpected response from the server");
}
} catch (error) {
notify("error", error);
}
});
</script>
{% endblock customJS %}

View File

@ -30,30 +30,25 @@
</div> </div>
</div> </div>
<!-- Customer Table --> <!-- Customer Table -->
{% if page_obj.object_list %}
<div id="accountsTable"> <div id="accountsTable">
<div class="table-responsive"> <div class="table-responsive">
<table class="table table-sm fs-9 mb-0"> <table class="table table-sm fs-9 mb-0">
<thead> <thead>
<tr class="bg-body-highlight"> <tr class="bg-body-highlight">
<th class="border-top border-translucent ps-3"> <th class="border-top border-translucent ps-3">
{% trans 'Account Name' %} {% trans 'Bill Number' %}
</th> </th>
<th class="border-top border-translucent"> <th class="border-top border-translucent">
{% trans 'Code' %} {% trans 'Bill Status' %}
</th> </th>
<th class="border-top border-translucent text-end pe-3"> <th class="border-top border-translucent text-end pe-3">
{% trans 'Balance Type' %} {% trans 'Vendor' %}
</th>
<th class="border-top border-translucent text-end pe-3">
{% trans 'Active' %}
</th> </th>
<th class="border-top border-translucent text-end align-middle pe-0 ps-4" scope="col"></th> <th class="border-top border-translucent text-end align-middle pe-0 ps-4" scope="col"></th>
</tr> </tr>
</thead> </thead>
<tbody class="list"> <tbody class="list">
{% for bill in bills %} {% for bill in bills %}
<div class="modal fade" id="deleteModal" data-bs-backdrop="static" data-bs-keyboard="false" tabindex="-1" aria-labelledby="deleteModalLabel" aria-hidden="true"> <div class="modal fade" id="deleteModal" data-bs-backdrop="static" data-bs-keyboard="false" tabindex="-1" aria-labelledby="deleteModalLabel" aria-hidden="true">
<div class="modal-dialog modal-sm"> <div class="modal-dialog modal-sm">
@ -78,31 +73,16 @@
</div> </div>
</div> </div>
<tr> <tr>
<td class="align-middle ps-3">{{ bill.name }}</td> <td class="align-middle ps-3">{{ bill.bill_number }}</td>
<td class="align-middle">{{ bill.code }}</td> <td class="align-middle">{{ bill.bill_status }}</td>
<td class="align-middle text-end py-3 pe-3"> <td class="align-middle text-end py-3 pe-3">
{% if bill.balance_type == 'debit' %} {{bill.vendor.vendor_name}}
<div class="badge badge-phoenix fs-10 badge-phoenix-success">
<span class="fw-bold">{{ _('Debit') }}</span><span class="ms-1 fas fa-arrow-circle-down"></span>
</div>
{% else %}
<div class="badge badge-phoenix fs-10 badge-phoenix-danger">
<span class="fw-bold">{{ _('Credit') }}</span><span class="ms-1 fas fa-arrow-circle-up"></span>
</div>
{% endif %}
</td>
<td class="align-middle text-end py-3 pe-3">
{% if bill.active %}
<span class="fw-bold text-success fas fa-check-circle"></span>
{% else %}
<span class="fw-bold text-danger far fa-times-circle"></span>
{% endif %}
</td> </td>
<td> <td>
<div class="btn-reveal-trigger position-static"> <div class="btn-reveal-trigger position-static">
<button class="btn btn-sm dropdown-toggle dropdown-caret-none transition-none btn-reveal fs-10" type="button" data-bs-toggle="dropdown" data-boundary="window" aria-haspopup="true" aria-expanded="false" data-bs-reference="parent"><span class="fas fa-ellipsis-h fs-10"></span></button> <button class="btn btn-sm dropdown-toggle dropdown-caret-none transition-none btn-reveal fs-10" type="button" data-bs-toggle="dropdown" data-boundary="window" aria-haspopup="true" aria-expanded="false" data-bs-reference="parent"><span class="fas fa-ellipsis-h fs-10"></span></button>
<div class="dropdown-menu dropdown-menu-end py-2"> <div class="dropdown-menu dropdown-menu-end py-2">
<a href="{% url 'bill_detail' bill.uuid %}" class="dropdown-item text-success-dark">{% trans 'View' %}</a> <a href="{% url 'bill_detail' bill.pk %}" class="dropdown-item text-success-dark">{% trans 'View' %}</a>
<div class="dropdown-divider"></div><button class="dropdown-item text-danger" data-bs-toggle="modal" data-bs-target="#deleteModal">{% trans 'Delete' %}</button> <div class="dropdown-divider"></div><button class="dropdown-item text-danger" data-bs-toggle="modal" data-bs-target="#deleteModal">{% trans 'Delete' %}</button>
</div> </div>
</div> </div>
@ -157,8 +137,7 @@
</nav> </nav>
</div> </div>
</div> </div>
</div> </div>
{% endif %}
</main> </main>
</div> </div>
</div> </div>

View File

@ -0,0 +1,21 @@
{% extends 'base.html' %}
{% load i18n %}
{% load crispy_forms_filters %}
{% block title %}{% trans "Bill" %}{% endblock title %}
{% block content %}
<div class="row my-4">
<h2>{% trans "Bill" %}</h2>
<form method="post">
{% csrf_token %}
{{ form|crispy }}
<button type="submit" class="btn btn-sm btn-primary">
{% if bill.is_draft %}
{% trans "Mark As Review" %}
{% elif bill.is_review or bill.is_approved %}
{% trans "Save" %}
{% endif %}
</button>
<a href="{% url 'bill_detail' pk=bill.pk %}" class="btn btn-sm btn-secondary">{% trans "Cancel" %}</a>
</form>
</div>
{% endblock %}

View File

@ -0,0 +1,50 @@
<li class="nav-item dropdown">
<div class="notification-count" hx-get="{% url 'fetch_notifications' %}" hx-trigger="every 10s" hx-swap="innerHTML" hx-select=".notification-count">
{% if notifications_ %}
<span class="badge bg-danger rounded-pill " id="notification-counter" style="position: absolute; top: 8px; right: 3px; font-size: 0.50rem;">{{ notifications_.count }}</span>
{% endif %}
</div>
<a class="nav-link" href="{% url 'fetch_notifications' %}" hx-get="{% url 'fetch_notifications' %}" hx-swap="innerHTML" hx-target=".card-body" hx-select=".card-body" style="min-width: 2.25rem" role="button" data-bs-toggle="dropdown" aria-haspopup="true" aria-expanded="false" data-bs-auto-close="outside"><span class="d-block" style="height:20px;width:20px;"><span data-feather="bell" style="height:20px;width:20px;"></span></span></span>
</a>
<div class="dropdown-menu dropdown-menu-end notification-dropdown-menu py-0 shadow border navbar-dropdown-caret" id="navbarDropdownNotfication" aria-labelledby="navbarDropdownNotfication">
<div class="card position-relative border-0">
<div class="card-header p-2">
<div class="d-flex justify-content-between">
<h5 class="text-body-emphasis mb-0">Notifications</h5>
<button class="btn btn-link p-0 fs-9 fw-normal" type="button">Mark all as read</button>
</div>
</div>
<div class="card-body p-0">
<div class="scrollbar-overlay" style="height: 27rem;">
{% for notification in notifications_ %}
<div class="px-2 px-sm-3 py-3 notification-card position-relative read border-bottom">
<div class="d-flex align-items-center justify-content-between position-relative">
<div class="d-flex">
<div class="flex-1 me-sm-3">
<h4 class="fs-9 text-body-emphasis">System</h4>
<p class="fs-9 text-body-highlight mb-2 mb-sm-3 fw-normal">
<span class="me-1 fs-10">💬</span>{{notification.message|safe}}<span class="ms-2 text-body-quaternary text-opacity-75 fw-bold fs-10">10m</span>
</p>
<p class="text-body-secondary fs-9 mb-0">
<span class="me-1 fas fa-clock"></span><span class="fw-bold">10:41 AM</span>{{notification.created}}
</p>
</div>
</div>
<div class="dropdown notification-dropdown">
<button class="btn fs-10 btn-sm dropdown-toggle dropdown-caret-none transition-none" type="button" data-bs-toggle="dropdown" data-boundary="window" aria-haspopup="true" aria-expanded="false" data-bs-reference="parent"><span class="fas fa-ellipsis-h fs-10 text-body"></span></button>
<div class="dropdown-menu py-2">
<a class="dropdown-item" href="{% url 'mark_notification_as_read' notification.id %}">Mark as read</a>
</div>
</div>
</div>
</div>
{% endfor %}
</div>
</div>
<div class="card-footer p-0 border-top border-translucent border-0">
<div class="my-2 text-center fw-bold fs-10 text-body-tertiary text-opactity-85"><a class="fw-bolder" href="{% url 'notifications_history' %}">Notification history</a></div>
</div>
</div>
</div>
</li>

View File

@ -174,7 +174,7 @@
<!-- ============================================--> <!-- ============================================-->
{% endblock %} {% endblock %}
{% block extra_js %} {% block customJS %}
<script> <script>
function calculateTotals() { function calculateTotals() {
const table = document.getElementById('estimate-table'); const table = document.getElementById('estimate-table');

View File

@ -45,7 +45,7 @@
</div> </div>
{% endblock content %} {% endblock content %}
{% block extra_js %} {% block customJS %}
<script> <script>
const Toast = Swal.mixin({ const Toast = Swal.mixin({
toast: true, toast: true,
@ -155,4 +155,4 @@
} }
}); });
</script> </script>
{% endblock extra_js %} {% endblock customJS %}

View File

@ -262,7 +262,7 @@
</div> </div>
{% endblock %} {% endblock %}
{% block extra_js %} {% block customJS %}
<script> <script>
function calculateTotals() { function calculateTotals() {
const table = document.getElementById('estimate-table'); const table = document.getElementById('estimate-table');

View File

@ -12,20 +12,18 @@
</style> </style>
{% endblock extra_css %} {% endblock extra_css %}
{% block content %} {% block content %}
<div class="row {% if invoice.invoice_status == 'paid' %}paid{% endif %}"> <div class="row {% if model.is_paid %}paid{% endif %}">
<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">
{% if invoice.invoice_status == 'paid' %} {% if model.is_paid %}
<div class="card-header">{{ _("Payment Already Made") }}</div> <div class="card-header">{{ _("Payment Already Made") }}</div>
{% else %} {% else %}
<div class="card-header">{{ _("Make Payment") }}</div> <div class="card-header">{{ _("Make Payment") }}</div>
{% endif %} {% endif %}
<div class="card-body"> <div class="card-body">
{% if invoice %} {% if model %}
<form method="post" action="{% url 'payment_create' pk=invoice.pk %}"> <form method="post" action="{% url 'payment_create' pk=model.pk %}">
{% else %}
<form method="post" action="{% url 'payment_create' %}">
{% endif %} {% endif %}
{% csrf_token %} {% csrf_token %}
{{ form|crispy }} {{ form|crispy }}

View File

@ -7,7 +7,7 @@
<div class="row mt-4"> <div class="row mt-4">
<div class="d-flex justify-content-between mb-2"> <div class="d-flex justify-content-between mb-2">
<h3 class="mb-3">{% trans "Payments" %}</h3> <h3 class="mb-3">{% trans "Payments" %}</h3>
<a href="{% url 'payment_create' %}" class="btn btn-sm btn-phoenix-success ">{% trans "Add Payment" %}</a> {% comment %} <a href="{% url 'payment_create' %}" class="btn btn-sm btn-phoenix-success ">{% trans "Add Payment" %}</a> {% endcomment %}
</div> </div>