update
This commit is contained in:
commit
ca8e5a7190
BIN
db.sqlite3-shm
BIN
db.sqlite3-shm
Binary file not shown.
BIN
db.sqlite3.backup
Normal file
BIN
db.sqlite3.backup
Normal file
Binary file not shown.
@ -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
|
||||
|
||||
|
||||
@ -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
|
||||
import django.db.models.deletion
|
||||
@ -9,8 +9,8 @@ class Migration(migrations.Migration):
|
||||
initial = True
|
||||
|
||||
dependencies = [
|
||||
('inventory', '0001_initial'),
|
||||
('haikalbot', '0001_initial'),
|
||||
('inventory', '0001_initial'),
|
||||
]
|
||||
|
||||
operations = [
|
||||
|
||||
@ -9,6 +9,7 @@ from phonenumber_field.phonenumber import PhoneNumber
|
||||
from .mixins import AddClassMixin
|
||||
from django.forms.models import inlineformset_factory
|
||||
from django_ledger.forms.invoice import InvoiceModelCreateForm as InvoiceModelCreateFormBase
|
||||
from django_ledger.forms.bill import BillModelCreateForm as BillModelCreateFormBase
|
||||
from .models import (
|
||||
Dealer,
|
||||
# Branch,
|
||||
@ -31,7 +32,7 @@ from .models import (
|
||||
Staff,
|
||||
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.utils.translation import gettext_lazy as _
|
||||
import django_tables2 as tables
|
||||
@ -543,7 +544,10 @@ class ItemForm(forms.Form):
|
||||
|
||||
class PaymentForm(forms.Form):
|
||||
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)
|
||||
payment_method = forms.ChoiceField(
|
||||
@ -561,17 +565,19 @@ class PaymentForm(forms.Form):
|
||||
|
||||
def clean_amount(self):
|
||||
invoice = self.cleaned_data['invoice']
|
||||
bill = self.cleaned_data['bill']
|
||||
model = invoice if invoice else bill
|
||||
amount = self.cleaned_data['amount']
|
||||
if amount < invoice.amount_due:
|
||||
raise forms.ValidationError("Payment amount is greater than invoice amount due")
|
||||
if amount + model.amount_paid > model.amount_due:
|
||||
raise forms.ValidationError("Payment amount is greater than amount due")
|
||||
if amount <= 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")
|
||||
if amount > invoice.amount_due:
|
||||
raise forms.ValidationError("Payment amount is greater than invoice amount due")
|
||||
return amount
|
||||
|
||||
if amount > model.amount_due:
|
||||
raise forms.ValidationError("Payment amount is greater than amount due")
|
||||
return amount
|
||||
|
||||
|
||||
|
||||
class EmailForm(forms.Form):
|
||||
@ -628,4 +634,13 @@ class InvoiceModelCreateForm(InvoiceModelCreateFormBase):
|
||||
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'}))
|
||||
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'}))
|
||||
|
||||
@ -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 django.conf import settings
|
||||
@ -15,6 +15,7 @@ class Migration(migrations.Migration):
|
||||
|
||||
dependencies = [
|
||||
('contenttypes', '0002_remove_content_type_name'),
|
||||
migrations.swappable_dependency(settings.AUTH_USER_MODEL),
|
||||
('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')),
|
||||
('vin', models.CharField(max_length=17, unique=True, verbose_name='VIN')),
|
||||
('year', models.IntegerField(verbose_name='Year')),
|
||||
('status', models.CharField(choices=[('available', 'Available'), ('sold', 'Sold'), ('hold', 'Hold'), ('damaged', 'Damaged'), ('reserved', 'Reserved')], 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')),
|
||||
('remarks', models.TextField(blank=True, null=True, verbose_name='Remarks')),
|
||||
('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)),
|
||||
('logo', models.ImageField(blank=True, null=True, upload_to='car_make', verbose_name='logo')),
|
||||
('is_sa_import', models.BooleanField(default=False)),
|
||||
('car_type', models.SmallIntegerField(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={
|
||||
'verbose_name': 'Make',
|
||||
},
|
||||
bases=(models.Model, inventory.mixins.LocalizedNameMixin),
|
||||
),
|
||||
migrations.CreateModel(
|
||||
name='ExteriorColors',
|
||||
fields=[
|
||||
('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
|
||||
('name', models.CharField(max_length=255, verbose_name='Name')),
|
||||
('arabic_name', models.CharField(max_length=255, verbose_name='Arabic Name')),
|
||||
('rgb', models.CharField(blank=True, max_length=24, null=True, verbose_name='RGB')),
|
||||
],
|
||||
options={
|
||||
'verbose_name': 'Exterior Colors',
|
||||
'verbose_name_plural': 'Exterior Colors',
|
||||
},
|
||||
bases=(models.Model, inventory.mixins.LocalizedNameMixin),
|
||||
),
|
||||
migrations.CreateModel(
|
||||
name='InteriorColors',
|
||||
fields=[
|
||||
('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
|
||||
('name', models.CharField(max_length=255, verbose_name='Name')),
|
||||
('arabic_name', models.CharField(max_length=255, verbose_name='Arabic Name')),
|
||||
('rgb', models.CharField(blank=True, max_length=24, null=True, verbose_name='RGB')),
|
||||
],
|
||||
options={
|
||||
'verbose_name': 'Interior Colors',
|
||||
'verbose_name_plural': 'Interior Colors',
|
||||
},
|
||||
bases=(models.Model, inventory.mixins.LocalizedNameMixin),
|
||||
),
|
||||
migrations.CreateModel(
|
||||
name='Payment',
|
||||
fields=[
|
||||
('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
|
||||
('amount', models.DecimalField(decimal_places=2, max_digits=10, verbose_name='amount')),
|
||||
('payment_method', models.CharField(choices=[('cash', 'cash'), ('credit', 'credit'), ('transfer', 'transfer'), ('debit', 'debit'), ('SADAD', 'SADAD')], max_length=50, verbose_name='method')),
|
||||
('reference_number', models.CharField(blank=True, max_length=100, null=True, verbose_name='reference number')),
|
||||
('payment_date', models.DateField(auto_now_add=True, verbose_name='date')),
|
||||
],
|
||||
options={
|
||||
'verbose_name': 'payment',
|
||||
'verbose_name_plural': 'payments',
|
||||
},
|
||||
),
|
||||
migrations.CreateModel(
|
||||
name='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(
|
||||
name='CarModel',
|
||||
fields=[
|
||||
@ -274,7 +150,6 @@ class Migration(migrations.Migration):
|
||||
('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')),
|
||||
@ -310,51 +185,46 @@ class Migration(migrations.Migration):
|
||||
],
|
||||
),
|
||||
migrations.CreateModel(
|
||||
name='CarLocation',
|
||||
name='ExteriorColors',
|
||||
fields=[
|
||||
('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
|
||||
('description', models.TextField(blank=True, help_text='Optional description about the showroom placement.', null=True, verbose_name='Description')),
|
||||
('created_at', models.DateTimeField(auto_now_add=True, verbose_name='Created At')),
|
||||
('updated_at', models.DateTimeField(auto_now=True, verbose_name='Last Updated')),
|
||||
('car', models.OneToOneField(on_delete=django.db.models.deletion.CASCADE, related_name='location', to='inventory.car', verbose_name='Car')),
|
||||
('owner', models.ForeignKey(help_text='Dealer who owns the car.', on_delete=django.db.models.deletion.CASCADE, related_name='owned_cars', to='inventory.dealer', verbose_name='Owner')),
|
||||
('showroom', models.ForeignKey(help_text='Dealer where the car is displayed (can be the owner).', on_delete=django.db.models.deletion.CASCADE, related_name='showroom_cars', to='inventory.dealer', verbose_name='Showroom')),
|
||||
('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': 'Car Location',
|
||||
'verbose_name_plural': 'Car Locations',
|
||||
'verbose_name': 'Exterior Colors',
|
||||
'verbose_name_plural': 'Exterior Colors',
|
||||
},
|
||||
bases=(models.Model, inventory.mixins.LocalizedNameMixin),
|
||||
),
|
||||
migrations.AddField(
|
||||
model_name='car',
|
||||
name='dealer',
|
||||
field=models.ForeignKey(on_delete=django.db.models.deletion.DO_NOTHING, related_name='cars', to='inventory.dealer', verbose_name='Dealer'),
|
||||
),
|
||||
migrations.AddField(
|
||||
model_name='additionalservices',
|
||||
name='dealer',
|
||||
field=models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to='inventory.dealer', verbose_name='Dealer'),
|
||||
migrations.CreateModel(
|
||||
name='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='Lead',
|
||||
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')], 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')),
|
||||
('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')),
|
||||
('address', models.CharField(blank=True, max_length=200, null=True, verbose_name='Address')),
|
||||
('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')),
|
||||
('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')),
|
||||
('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')),
|
||||
('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')),
|
||||
@ -364,62 +234,6 @@ class Migration(migrations.Migration):
|
||||
'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(
|
||||
name='Organization',
|
||||
fields=[
|
||||
@ -431,7 +245,8 @@ class Migration(migrations.Migration):
|
||||
('phone_number', phonenumber_field.modelfields.PhoneNumberField(max_length=128, region='SA', verbose_name='Phone Number')),
|
||||
('address', models.CharField(blank=True, max_length=200, null=True, verbose_name='Address')),
|
||||
('logo', models.ImageField(blank=True, null=True, upload_to='logos', verbose_name='Logo')),
|
||||
('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')),
|
||||
],
|
||||
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')),
|
||||
],
|
||||
),
|
||||
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(
|
||||
name='Subscription',
|
||||
fields=[
|
||||
@ -719,7 +442,7 @@ class Migration(migrations.Migration):
|
||||
('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
|
||||
('name', models.CharField(max_length=255, verbose_name='Name')),
|
||||
('arabic_name', models.CharField(max_length=255, verbose_name='Arabic Name')),
|
||||
('id_number', models.CharField(max_length=10, verbose_name='ID Number')),
|
||||
('id_number', models.CharField(max_length=10, unique=True, verbose_name='ID Number')),
|
||||
('phone_number', phonenumber_field.modelfields.PhoneNumberField(max_length=128, region='SA', verbose_name='Phone Number')),
|
||||
('email', models.EmailField(max_length=255, verbose_name='Email Address')),
|
||||
('address', models.CharField(blank=True, max_length=200, null=True, verbose_name='Address')),
|
||||
@ -820,39 +543,14 @@ class Migration(migrations.Migration):
|
||||
),
|
||||
migrations.AddField(
|
||||
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'),
|
||||
),
|
||||
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(
|
||||
model_name='customer',
|
||||
name='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(
|
||||
name='CustomCard',
|
||||
fields=[
|
||||
@ -881,6 +579,27 @@ class Migration(migrations.Migration):
|
||||
},
|
||||
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(
|
||||
name='CarSpecificationValue',
|
||||
fields=[
|
||||
|
||||
@ -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),
|
||||
),
|
||||
]
|
||||
@ -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),
|
||||
),
|
||||
]
|
||||
@ -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'),
|
||||
),
|
||||
]
|
||||
@ -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 = [
|
||||
]
|
||||
@ -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',
|
||||
# ),
|
||||
]
|
||||
@ -1,196 +1,531 @@
|
||||
from django.urls import path
|
||||
from . import views
|
||||
from allauth.account import views as allauth_views
|
||||
from django.conf.urls import (
|
||||
handler400, handler403, handler404, handler500
|
||||
)
|
||||
from django.conf.urls import handler400, handler403, handler404, handler500
|
||||
|
||||
urlpatterns = [
|
||||
# main URLs
|
||||
path('', views.HomeView.as_view(), name='landing_page'),
|
||||
path('welcome/', views.WelcomeView.as_view(), name='welcome'),
|
||||
|
||||
path("", views.HomeView.as_view(), name="landing_page"),
|
||||
path("welcome/", views.WelcomeView.as_view(), name="welcome"),
|
||||
# Accounts URLs
|
||||
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('signup/', allauth_views.SignupView.as_view(template_name='account/signup.html'), name='account_signup'),
|
||||
path('signup/', views.dealer_signup, name='account_signup'),
|
||||
path('password/change/',
|
||||
allauth_views.PasswordChangeView.as_view(template_name='account/password_change.html'),
|
||||
name='account_change_password'),
|
||||
path('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'),
|
||||
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('signup/', allauth_views.SignupView.as_view(template_name='account/signup.html'), name='account_signup'),
|
||||
path("signup/", views.dealer_signup, name="account_signup"),
|
||||
path(
|
||||
"password/change/",
|
||||
allauth_views.PasswordChangeView.as_view(
|
||||
template_name="account/password_change.html"
|
||||
),
|
||||
name="account_change_password",
|
||||
),
|
||||
path(
|
||||
"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
|
||||
path('dealers/<int:pk>/', views.DealerDetailView.as_view(), name='dealer_detail'),
|
||||
path('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>/", views.DealerDetailView.as_view(), name="dealer_detail"),
|
||||
path(
|
||||
"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'),
|
||||
|
||||
# CRM URLs
|
||||
path('customers/', views.CustomerListView.as_view(), name='customer_list'),
|
||||
path('customers/<int:pk>/', views.CustomerDetailView.as_view(), name='customer_detail'),
|
||||
path('customers/create/', views.CustomerCreateView.as_view(), name='customer_create'),
|
||||
path('customers/<int:pk>/update/', views.CustomerUpdateView.as_view(), name='customer_update'),
|
||||
path('customers/<int:pk>/delete/', views.delete_customer, name='customer_delete'),
|
||||
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('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("customers/", views.CustomerListView.as_view(), name="customer_list"),
|
||||
path(
|
||||
"customers/<int:pk>/",
|
||||
views.CustomerDetailView.as_view(),
|
||||
name="customer_detail",
|
||||
),
|
||||
path(
|
||||
"customers/create/", views.CustomerCreateView.as_view(), name="customer_create"
|
||||
),
|
||||
path(
|
||||
"customers/<int:pk>/update/",
|
||||
views.CustomerUpdateView.as_view(),
|
||||
name="customer_update",
|
||||
),
|
||||
path("customers/<int:pk>/delete/", views.delete_customer, name="customer_delete"),
|
||||
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("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/notifications/', views.NotificationListView.as_view(), name='notifications_history'),
|
||||
path('crm/fetch_notifications/', views.fetch_notifications, name='fetch_notifications'),
|
||||
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'),
|
||||
|
||||
path(
|
||||
"crm/notifications/",
|
||||
views.NotificationListView.as_view(),
|
||||
name="notifications_history",
|
||||
),
|
||||
path(
|
||||
"crm/fetch_notifications/",
|
||||
views.fetch_notifications,
|
||||
name="fetch_notifications",
|
||||
),
|
||||
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
|
||||
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('cars/inventory/stats', views.inventory_stats_view, name='inventory_stats'),
|
||||
path('cars/<int:pk>/', views.CarDetailView.as_view(), name='car_detail'),
|
||||
path('cars/<int:pk>/update/', views.CarUpdateView.as_view(), name='car_update'),
|
||||
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/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.CarLocationUpdateView.as_view(), name='transfer'),
|
||||
path('cars/inventory/search/', views.SearchCodeView.as_view(), name='car_search'),
|
||||
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("cars/inventory/stats", views.inventory_stats_view, name="inventory_stats"),
|
||||
path("cars/<int:pk>/", views.CarDetailView.as_view(), name="car_detail"),
|
||||
path("cars/<int:pk>/update/", views.CarUpdateView.as_view(), name="car_update"),
|
||||
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/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/reserve/<int:car_id>/', views.reserve_car_view, name='reserve_car'),
|
||||
path('reservations/<int:reservation_id>/', views.manage_reservation, name='reservations'),
|
||||
path('cars/<int:car_pk>/add-custom-card/', views.CustomCardCreateView.as_view(), name='add_custom_card'),
|
||||
|
||||
path("cars/reserve/<int:car_id>/", views.reserve_car_view, name="reserve_car"),
|
||||
path(
|
||||
"reservations/<int:reservation_id>/",
|
||||
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
|
||||
path('sales/quotations/create/', views.QuotationCreateView.as_view(), name='quotation_create'),
|
||||
path('sales/quotations/<int:pk>/', views.QuotationDetailView.as_view(), name='quotation_detail'),
|
||||
path('sales/quotations/', views.QuotationListView.as_view(), name='quotation_list'),
|
||||
path('sales/quotations/<int:pk>/confirm/', 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'),
|
||||
|
||||
path(
|
||||
"sales/quotations/create/",
|
||||
views.QuotationCreateView.as_view(),
|
||||
name="quotation_create",
|
||||
),
|
||||
path(
|
||||
"sales/quotations/<int:pk>/",
|
||||
views.QuotationDetailView.as_view(),
|
||||
name="quotation_detail",
|
||||
),
|
||||
path("sales/quotations/", views.QuotationListView.as_view(), name="quotation_list"),
|
||||
path(
|
||||
"sales/quotations/<int:pk>/confirm/",
|
||||
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
|
||||
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>/', views.UserDetailView.as_view(), name='user_detail'),
|
||||
path('user/', views.UserListView.as_view(), name='user_list'),
|
||||
path('user/<int:pk>/confirm/', views.UserDeleteview, name='user_delete'),
|
||||
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>/", views.UserDetailView.as_view(), name="user_detail"),
|
||||
path("user/", views.UserListView.as_view(), name="user_list"),
|
||||
path("user/<int:pk>/confirm/", views.UserDeleteview, name="user_delete"),
|
||||
# Organization URLs
|
||||
path('organizations/', views.OrganizationListView.as_view(), name='organization_list'),
|
||||
path('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'),
|
||||
|
||||
path(
|
||||
"organizations/", views.OrganizationListView.as_view(), name="organization_list"
|
||||
),
|
||||
path(
|
||||
"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
|
||||
path('representatives/', views.RepresentativeListView.as_view(), name='representative_list'),
|
||||
path('representatives/<int:pk>/', views.RepresentativeDetailView.as_view(), name='representative_detail'),
|
||||
path('representatives/create/', 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'),
|
||||
path(
|
||||
"representatives/",
|
||||
views.RepresentativeListView.as_view(),
|
||||
name="representative_list",
|
||||
),
|
||||
path(
|
||||
"representatives/<int:pk>/",
|
||||
views.RepresentativeDetailView.as_view(),
|
||||
name="representative_detail",
|
||||
),
|
||||
path(
|
||||
"representatives/create/",
|
||||
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
|
||||
path('coa_accounts/', views.AccountListView.as_view(), name='account_list'),
|
||||
path('coa_accounts/<uuid:pk>/', views.AccountDetailView.as_view(), 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'),
|
||||
path("coa_accounts/", views.AccountListView.as_view(), name="account_list"),
|
||||
path(
|
||||
"coa_accounts/<uuid:pk>/",
|
||||
views.AccountDetailView.as_view(),
|
||||
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
|
||||
path('sales/estimates/', views.EstimateListView.as_view(), name='estimate_list'),
|
||||
path('sales/estimates/<uuid:pk>/', views.EstimateDetailView.as_view(), name='estimate_detail'),
|
||||
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'),
|
||||
path("sales/estimates/", views.EstimateListView.as_view(), name="estimate_list"),
|
||||
path(
|
||||
"sales/estimates/<uuid:pk>/",
|
||||
views.EstimateDetailView.as_view(),
|
||||
name="estimate_detail",
|
||||
),
|
||||
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
|
||||
path('sales/invoices/', views.InvoiceListView.as_view(), name='invoice_list'),
|
||||
path('sales/invoices/<uuid:pk>/create/', views.invoice_create, name='invoice_create'),
|
||||
path('sales/invoices/<uuid:pk>/', views.InvoiceDetailView.as_view(), name='invoice_detail'),
|
||||
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('sales/invoices/<uuid:pk>/draft_invoice_update/', views.DraftInvoiceModelUpdateFormView.as_view(), name='draft_invoice_update'),
|
||||
path('sales/invoices/<uuid:pk>/approved_invoice_update/', views.ApprovedInvoiceModelUpdateFormView.as_view(), name='approved_invoice_update'),
|
||||
path('sales/invoices/<uuid:pk>/paid_invoice_update/', views.PaidInvoiceModelUpdateFormView.as_view(), name='paid_invoice_update'),
|
||||
|
||||
# path('sales/estimates/<uuid:pk>/preview/', views.EstimatePreviewView.as_view(), name='estimate_preview'),
|
||||
# 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.BillCreateView.as_view(), name='bill_create'),
|
||||
# path('items/bills/<uuid:pk>/update/', views.ItemExpenseUpdateView.as_view(), name='item_expense_update'),
|
||||
path("sales/invoices/", views.InvoiceListView.as_view(), name="invoice_list"),
|
||||
path(
|
||||
"sales/invoices/<uuid:pk>/create/", views.invoice_create, name="invoice_create"
|
||||
),
|
||||
path(
|
||||
"sales/invoices/<uuid:pk>/",
|
||||
views.InvoiceDetailView.as_view(),
|
||||
name="invoice_detail",
|
||||
),
|
||||
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(
|
||||
"sales/invoices/<uuid:pk>/draft_invoice_update/",
|
||||
views.DraftInvoiceModelUpdateFormView.as_view(),
|
||||
name="draft_invoice_update",
|
||||
),
|
||||
path(
|
||||
"sales/invoices/<uuid:pk>/approved_invoice_update/",
|
||||
views.ApprovedInvoiceModelUpdateFormView.as_view(),
|
||||
name="approved_invoice_update",
|
||||
),
|
||||
path(
|
||||
"sales/invoices/<uuid:pk>/paid_invoice_update/",
|
||||
views.PaidInvoiceModelUpdateFormView.as_view(),
|
||||
name="paid_invoice_update",
|
||||
),
|
||||
# path('sales/estimates/<uuid:pk>/preview/', views.EstimatePreviewView.as_view(), name='estimate_preview'),
|
||||
# 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'
|
||||
handler500 = 'inventory.views.custom_error_view'
|
||||
handler403 = 'inventory.views.custom_permission_denied_view'
|
||||
handler400 = 'inventory.views.custom_bad_request_view'
|
||||
|
||||
|
||||
|
||||
|
||||
handler404 = "inventory.views.custom_page_not_found_view"
|
||||
handler500 = "inventory.views.custom_error_view"
|
||||
handler403 = "inventory.views.custom_permission_denied_view"
|
||||
handler400 = "inventory.views.custom_bad_request_view"
|
||||
|
||||
@ -1,6 +1,13 @@
|
||||
import json
|
||||
import random
|
||||
import datetime
|
||||
from django.shortcuts import redirect
|
||||
from django.contrib import messages
|
||||
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
|
||||
from inventory import models
|
||||
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 inventory.utilities.financials import get_financial_value
|
||||
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
|
||||
|
||||
|
||||
@ -179,3 +186,287 @@ def get_financial_values(model):
|
||||
"vat_amount": vat_amount,
|
||||
"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")
|
||||
|
||||
|
||||
|
||||
@ -1,6 +1,7 @@
|
||||
from decimal import Decimal
|
||||
from django.core.paginator import Paginator
|
||||
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.views.decorators.csrf import csrf_exempt
|
||||
from django_ledger.models import (
|
||||
@ -81,6 +82,9 @@ from .utils import (
|
||||
reserve_car,
|
||||
send_email,
|
||||
get_user_type,
|
||||
set_bill_payment,
|
||||
set_invoice_payment,
|
||||
transfer_car,
|
||||
)
|
||||
from django.contrib.auth.models import User
|
||||
from allauth.account import views
|
||||
@ -775,6 +779,120 @@ class CarLocationUpdateView(UpdateView):
|
||||
def get_success_url(self):
|
||||
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):
|
||||
model = models.CustomCard
|
||||
@ -2439,8 +2557,11 @@ class InvoicePreviewView(LoginRequiredMixin, DetailView):
|
||||
# payments
|
||||
|
||||
|
||||
def PaymentCreateView(request, pk=None):
|
||||
def PaymentCreateView(request, pk):
|
||||
print(pk)
|
||||
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)
|
||||
entity = dealer.entity
|
||||
form = forms.PaymentForm()
|
||||
@ -2449,92 +2570,38 @@ def PaymentCreateView(request, pk=None):
|
||||
if form.is_valid():
|
||||
amount = form.cleaned_data.get("amount")
|
||||
invoice = form.cleaned_data.get("invoice")
|
||||
bill = form.cleaned_data.get("bill")
|
||||
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:
|
||||
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=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()
|
||||
if invoice:
|
||||
set_invoice_payment(dealer,entity,invoice,amount,payment_method)
|
||||
elif bill:
|
||||
set_bill_payment(dealer,entity,bill,amount,payment_method)
|
||||
messages.success(request, "Payment created successfully!")
|
||||
return redirect(redirect_url, pk=model.pk)
|
||||
except Exception as e:
|
||||
messages.error(request, f"Error creating payment: {str(e)}")
|
||||
else:
|
||||
messages.error(request, f"Invalid form data: {str(form.errors)}")
|
||||
return redirect("invoice_detail", pk=invoice.pk)
|
||||
messages.error(request, f"Invalid form data: {str(form.errors)}")
|
||||
# return redirect(redirect_url, pk=model.pk)
|
||||
form = forms.PaymentForm()
|
||||
form.initial["amount"] = invoice.amount_due
|
||||
|
||||
if invoice:
|
||||
form.initial["invoice"] = invoice
|
||||
if model:
|
||||
form.initial["amount"] = model.amount_due - model.amount_paid
|
||||
if isinstance(model, InvoiceModel):
|
||||
form.initial["invoice"] = model
|
||||
form.fields['bill'].widget = HiddenInput()
|
||||
elif isinstance(model, BillModel):
|
||||
form.initial["bill"] = model
|
||||
form.fields['invoice'].widget = HiddenInput()
|
||||
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):
|
||||
dealer = get_user_type(request)
|
||||
|
||||
@ -2574,7 +2641,12 @@ def payment_mark_as_paid(request, pk):
|
||||
|
||||
invoice.ledger.post()
|
||||
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:
|
||||
messages.error(request, f"Error: {str(e)}")
|
||||
return redirect("invoice_detail", pk=invoice.pk)
|
||||
@ -2793,15 +2865,16 @@ def fetch_notifications(request):
|
||||
notifications = models.Notification.objects.filter(
|
||||
user=request.user, is_read=False
|
||||
).order_by("-created")
|
||||
notifications_data = [
|
||||
{
|
||||
"id": notification.id,
|
||||
"message": notification.message,
|
||||
"created": notification.created.strftime("%Y-%m-%d %H:%M:%S"),
|
||||
}
|
||||
for notification in notifications
|
||||
]
|
||||
return JsonResponse({"notifications": notifications_data})
|
||||
# notifications_data = [
|
||||
# {
|
||||
# "id": notification.id,
|
||||
# "message": notification.message,
|
||||
# "created": notification.created.strftime("%Y-%m-%d %H:%M:%S"),
|
||||
# }
|
||||
# for notification in notifications
|
||||
# ]
|
||||
# return JsonResponse({"notifications": notifications_data})
|
||||
return render(request,'notifications.html',{'notifications_':notifications})
|
||||
|
||||
|
||||
class ItemServiceCreateView(LoginRequiredMixin, SuccessMessageMixin, CreateView):
|
||||
@ -2897,33 +2970,273 @@ class ItemExpenseListView(ListView):
|
||||
|
||||
|
||||
class BillListView(ListView):
|
||||
model = ItemModel
|
||||
model = BillModel
|
||||
template_name = "ledger/bills/bill_list.html"
|
||||
context_object_name = "bills"
|
||||
paginate_by = 20
|
||||
|
||||
def get_queryset(self):
|
||||
dealer = get_user_type(self.request)
|
||||
return dealer.entity.get_bills()
|
||||
qs = dealer.entity.get_bills()
|
||||
return qs
|
||||
|
||||
|
||||
class BillCreateView(LoginRequiredMixin,SuccessMessageMixin,CreateView):
|
||||
class BillDetailView(LoginRequiredMixin, DetailView):
|
||||
model = BillModel
|
||||
form_class = BillModelCreateForm
|
||||
template_name = "ledger/bills/bill_form.html"
|
||||
template_name = "ledger/bills/bill_detail.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_message = _("Bill created successfully.")
|
||||
|
||||
success_message = _("Bill updated successfully.")
|
||||
context_object_name = "bill"
|
||||
|
||||
def get_form_kwargs(self):
|
||||
dealer = get_user_type(self.request)
|
||||
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
|
||||
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)
|
||||
# form.instance.entity = dealer.entity
|
||||
# return super().form_valid(form)
|
||||
# context = super().get_context_data(**kwargs)
|
||||
# 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):
|
||||
|
||||
@ -79,7 +79,7 @@
|
||||
|
||||
|
||||
</script>
|
||||
{% block extra_js %}{% endblock extra_js %}
|
||||
{% block customJS %}{% endblock customJS %}
|
||||
|
||||
|
||||
<script>
|
||||
|
||||
@ -71,7 +71,6 @@
|
||||
{% endblock %}
|
||||
|
||||
|
||||
|
||||
<!-- ===============================================-->
|
||||
<!-- JavaScripts-->
|
||||
<!-- ===============================================-->
|
||||
@ -90,8 +89,10 @@
|
||||
<script src="{% static 'js/main.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/htmx.org@2.0.4"></script>
|
||||
<script src="{% static 'vendors/swiper/swiper-bundle.min.js' %}"></script>
|
||||
<script src="{% static 'vendors/flatpickr/flatpickr.min.js' %}"></script>
|
||||
|
||||
<script>
|
||||
|
||||
|
||||
|
||||
@ -15,7 +15,7 @@
|
||||
{% 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>
|
||||
{% 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 %}
|
||||
<p class="text-body-secondary fs-9 mb-0"><span class="me-1 far fa-clock"></span>{{ notification.created }}</p>
|
||||
</div>
|
||||
|
||||
@ -230,6 +230,13 @@
|
||||
</div>
|
||||
</a>
|
||||
</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>
|
||||
</div>
|
||||
</div>
|
||||
@ -293,11 +300,8 @@
|
||||
</label>
|
||||
</div>
|
||||
</li>
|
||||
<li class="nav-item dropdown">
|
||||
<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>
|
||||
{% include "notifications.html" %}
|
||||
|
||||
<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">
|
||||
{% if request.LANGUAGE_CODE == 'ar' %}
|
||||
|
||||
@ -93,18 +93,21 @@
|
||||
</tr>
|
||||
{% endif %}
|
||||
<tr>
|
||||
|
||||
<th>{% trans 'Location'|capfirst %}</th>
|
||||
<td>
|
||||
{% if car.location %} {% if car.location.is_owner_showroom %} {% trans 'Our Showroom' %} {% else %} {{ car.location.showroom.get_local_name }} {% endif %}
|
||||
<a href="{% url 'transfer' car.location.pk %}" class="btn btn-phoenix-danger btn-sm">
|
||||
{% trans "transfer"|capfirst %}
|
||||
</a>
|
||||
{% else %} {% trans "No location available." %}
|
||||
<a href="{% url 'add_car_location' car.pk %}" class="btn btn-phoenix-success btn-sm ms-2">
|
||||
{% trans "Add" %}
|
||||
</a>
|
||||
{% if car.finances and not car.get_transfer %}
|
||||
{% if car.location %} {% if car.location.is_owner_showroom %} {% trans 'Our Showroom' %} {% else %} {{ car.location.showroom.get_local_name }} {% endif %}
|
||||
<a href="{% url 'transfer' car.location.pk %}" class="btn btn-phoenix-danger btn-sm">
|
||||
{% trans "transfer"|capfirst %}
|
||||
</a>
|
||||
{% else %} {% trans "No location available." %}
|
||||
<a href="{% url 'add_car_location' car.pk %}" class="btn btn-phoenix-success btn-sm ms-2">
|
||||
{% trans "Add" %}
|
||||
</a>
|
||||
{% endif %}
|
||||
{% endif %}
|
||||
</td>
|
||||
{% endif %}
|
||||
</tr>
|
||||
</table>
|
||||
</div>
|
||||
@ -215,6 +218,7 @@
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
{% if car.status != 'transfer' %}
|
||||
<div class="card rounded shadow d-flex align-content-center mt-3">
|
||||
<p class="card-header rounded-top fw-bold">{% trans 'Reservations Details' %}</p>
|
||||
<div class="card-body">
|
||||
@ -266,6 +270,55 @@
|
||||
</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>
|
||||
|
||||
@ -1,122 +1,273 @@
|
||||
{% extends 'base.html' %}
|
||||
{% extends "base.html" %}
|
||||
{% load i18n %}
|
||||
|
||||
{% block title %}
|
||||
{{ page_title }}
|
||||
{% endblock %}
|
||||
{% block title %}{{ _("View Bill") }}{% endblock title %}
|
||||
|
||||
{% block content %}
|
||||
<!-- Delete Modal -->
|
||||
<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-content rounded">
|
||||
<div class="modal-body d-flex justify-content-center">
|
||||
<h1 class="text-danger me-2"><i class="bi bi-exclamation-diamond-fill"></i></h1>
|
||||
<span class="text-danger">{% trans 'Are you sure you want to delete this account?' %}</span>
|
||||
</div>
|
||||
<div class="btn-group">
|
||||
<button type="button" class="btn btn-sm btn-secondary" data-bs-dismiss="modal">{% trans 'No' %}</button>
|
||||
<div class="btn btn-sm btn-danger">
|
||||
<form action="{% url 'account_delete' account.pk %}" method="post">
|
||||
{% csrf_token %}
|
||||
<button type="submit" class="btn btn-sm btn-danger">{% trans 'Yes' %}</button>
|
||||
<div class="modal fade" id="mark_as_approved_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_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>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="row my-5">
|
||||
<div class="card rounded">
|
||||
<div class="card-header">
|
||||
<p class="mb-0">{{ header_title|upper }}</p>
|
||||
</div>
|
||||
<div class="card-body">
|
||||
<div class="row">
|
||||
<div class="col-md-6">
|
||||
<p>
|
||||
<strong>{{ _('Account Name') }}:</strong> {{ account.name }}
|
||||
</p>
|
||||
<p>
|
||||
<strong>{{ _('Account Code') }}:</strong> {{ account.code }}
|
||||
</p>
|
||||
</div>
|
||||
<div class="col-md-6">
|
||||
<p>
|
||||
<strong>{{ _('Balance Type') }}:</strong> {{ account.balance_type }}
|
||||
</p>
|
||||
<p>
|
||||
<strong>{{ _('Active') }}:</strong> {{ account.active }}
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
<div class="row">
|
||||
<div class="col">
|
||||
<table class="table is-fullwidth is-narrow is-striped is-bordered django-ledger-table-bottom-margin-75">
|
||||
<tr>
|
||||
<th class="has-text-centered">{{ _('JE Number') }}</th>
|
||||
<th class="has-text-centered">{{ _('Date') }}</th>
|
||||
<th class="has-text-centered">{{ _('Debit') }}</th>
|
||||
<th class="has-text-centered">{{ _('Credit') }}</th>
|
||||
<th class="has-text-centered">{{ _('Description') }}</th>
|
||||
<th class="has-text-centered">{{ _('Unit') }}</th>
|
||||
<th class="has-text-centered">{{ _('Actions') }}</th>
|
||||
</tr>
|
||||
|
||||
{% for tx in account.transactionmodel_set.all %}
|
||||
<tr class="has-text-centered">
|
||||
<td>{{ tx.journal_entry.je_number }}</td>
|
||||
<td>{{ tx.journal_entry.timestamp }}</td>
|
||||
<td>
|
||||
{% if tx.tx_type == 'debit' %}
|
||||
${{ tx.amount }}
|
||||
{% endif %}
|
||||
</td>
|
||||
<td>
|
||||
{% if tx.tx_type == 'credit' %}
|
||||
${{ tx.amount }}
|
||||
{% endif %}
|
||||
</td>
|
||||
<td>{{ tx.description }}</td>
|
||||
<td>{{ tx.journal_entry.entity_unit.name }}</td>
|
||||
<td>
|
||||
<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>
|
||||
<div class="dropdown-menu dropdown-menu-end py-2">
|
||||
<a class="dropdown-item" href="{% url 'payment_details' tx.journal_entry.pk %}">{% trans 'view'|capfirst %}</a>
|
||||
<!-- ============================================-->
|
||||
<!-- <section> begin ============================-->
|
||||
<section class="pt-5 pb-9 bg-body-emphasis dark__bg-gray-1200 border-top">
|
||||
<div class="row-small mt-3">
|
||||
<div class="d-flex justify-content-between align-items-end mb-4">
|
||||
<h2 class="mb-0">{% trans 'Bill' %}</h2>
|
||||
<div class="d-flex align-items-center gap-2">
|
||||
{% if bill.is_draft %}
|
||||
<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>
|
||||
{% endif %}
|
||||
{% if bill.is_review %}
|
||||
<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>
|
||||
{% endif %}
|
||||
{% if bill.is_approved %}
|
||||
<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>
|
||||
{% endif %}
|
||||
{% if bill.is_approved %}
|
||||
<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>
|
||||
{% endif %}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- ============================================-->
|
||||
<div class="card mb-5">
|
||||
<div class="card-body">
|
||||
<div class="row g-4 g-xl-1 g-xxl-3 justify-content-between">
|
||||
<div class="col-sm-auto">
|
||||
<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">
|
||||
<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>
|
||||
<div>
|
||||
<p class="fw-bold mb-1">{% trans 'Paid Amount' %}</p>
|
||||
<h4 class="fw-bolder text-nowrap {% if bill.is_paid %}text-success{% endif %}">${{bill.amount_paid}}</h4>
|
||||
<h6 class="fw-bolder text-nowrap">Owned <span class="fw-semibold text-nowrap text-success">${{bill.get_amount_open}}</span></h6>
|
||||
<div class="progress" style="height:17px">
|
||||
<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>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
{% if 'net' in bill.terms %}
|
||||
<div class="col-sm-auto">
|
||||
<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">
|
||||
<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>
|
||||
<div>
|
||||
<p class="fw-bold mb-1"></p>
|
||||
<div class="fs-9 text-body-secondary fw-semibold mb-0 d-sm-block d-inline-flex d-md-flex flex-xl-column ">
|
||||
<table>
|
||||
<tr>
|
||||
<td>{% trans 'Terms' %}:</td>
|
||||
<td><span class="badge rounded bg-success">{{bill.bill_number}}</span></td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>{% trans 'Date Due' %}:</td>
|
||||
<td><span class="badge rounded bg-success">{{bill.date_due}}</span></td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>{% trans 'Due in Days' %}:</td>
|
||||
<td>
|
||||
<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>
|
||||
</td>
|
||||
</tr>
|
||||
{% endfor %}
|
||||
<tr class="has-text-weight-bold">
|
||||
<td></td>
|
||||
<td class="has-text-right">Total</td>
|
||||
<td class="has-text-centered">${{ total_debits }}</td>
|
||||
<td class="has-text-centered">${{ total_credits }}</td>
|
||||
<td></td>
|
||||
<td></td>
|
||||
<td></td>
|
||||
</tr>
|
||||
</table>
|
||||
</div>
|
||||
{% endif %}
|
||||
<div class="col-sm-auto">
|
||||
<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">
|
||||
<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>
|
||||
<div>
|
||||
<p class="fw-bold mb-1">{% trans 'Due Amount' %}</p>
|
||||
{% if bill.is_paid %}
|
||||
<s><h4 class="fw-bolder text-nowrap">${{bill.amount_due}} </h4></s>
|
||||
{% else %}
|
||||
<h4 class="fw-bolder text-nowrap">${{bill.amount_due}} </h4>
|
||||
{% endif %}
|
||||
</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 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 %}
|
||||
|
||||
{% 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 %}
|
||||
@ -1,38 +1,158 @@
|
||||
{% extends "base.html" %}
|
||||
{% load i18n %}
|
||||
{% load crispy_forms_filters %}
|
||||
{% block title %}{% trans "account" %}{% endblock title %}
|
||||
{% load i18n static %}
|
||||
|
||||
{% block title %}{{ _("Create Bill") }}{% endblock title %}
|
||||
|
||||
{% block content %}
|
||||
<div class="row my-5">
|
||||
<!-- Display Form Errors -->
|
||||
<div class="card shadow rounded">
|
||||
<div class="card-header bg-primary text-white">
|
||||
<p class="mb-0">
|
||||
{% if account.created %}
|
||||
<!--<i class="bi bi-pencil-square"></i>-->
|
||||
{{ _("Edit Account") }}
|
||||
{% else %}
|
||||
<!--<i class="bi bi-person-plus"></i> -->
|
||||
{{ _("Add Account") }}
|
||||
{% endif %}
|
||||
</p>
|
||||
</div>
|
||||
<div class="card-body">
|
||||
<form method="post" class="form" novalidate>
|
||||
{% csrf_token %}
|
||||
{{ form|crispy }}
|
||||
{% for error in form.errors %}
|
||||
<div class="text-danger">{{ error }}</div>
|
||||
{% endfor %}
|
||||
<div class="d-flex justify-content-end">
|
||||
<button class="btn btn-sm btn-success me-1" type="submit">
|
||||
{{ _("Save") }}
|
||||
</button>
|
||||
<a href="{{request.META.HTTP_REFERER}}" class="btn btn-sm btn-danger">{% trans "Cancel"|capfirst %}</a>
|
||||
<div class="row mt-4">
|
||||
<h3 class="text-center">{% trans "Create Bill" %}</h3>
|
||||
<form id="mainForm" method="post" class="needs-validation">
|
||||
{% csrf_token %}
|
||||
<div class="row g-3">
|
||||
{{ form|crispy }}
|
||||
<div class="row mt-5">
|
||||
<div id="formrow">
|
||||
<h3 class="text-start">Unit Items</h3>
|
||||
<div class="form-row row g-3 mb-3 mt-5">
|
||||
<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>
|
||||
</div>
|
||||
</div>
|
||||
</form>
|
||||
<div class="col-12">
|
||||
<button id="addMoreBtn" class="btn btn-primary">Add More</button>
|
||||
</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>
|
||||
{% 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 %}
|
||||
@ -30,30 +30,25 @@
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Customer Table -->
|
||||
|
||||
{% if page_obj.object_list %}
|
||||
<!-- Customer Table -->
|
||||
<div id="accountsTable">
|
||||
<div class="table-responsive">
|
||||
<table class="table table-sm fs-9 mb-0">
|
||||
<thead>
|
||||
<tr class="bg-body-highlight">
|
||||
<th class="border-top border-translucent ps-3">
|
||||
{% trans 'Account Name' %}
|
||||
{% trans 'Bill Number' %}
|
||||
</th>
|
||||
<th class="border-top border-translucent">
|
||||
{% trans 'Code' %}
|
||||
</th>
|
||||
{% trans 'Bill Status' %}
|
||||
</th>
|
||||
<th class="border-top border-translucent text-end pe-3">
|
||||
{% trans 'Balance Type' %}
|
||||
</th>
|
||||
<th class="border-top border-translucent text-end pe-3">
|
||||
{% trans 'Active' %}
|
||||
{% trans 'Vendor' %}
|
||||
</th>
|
||||
<th class="border-top border-translucent text-end align-middle pe-0 ps-4" scope="col"></th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody class="list">
|
||||
<tbody class="list">
|
||||
{% 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-dialog modal-sm">
|
||||
@ -78,31 +73,16 @@
|
||||
</div>
|
||||
</div>
|
||||
<tr>
|
||||
<td class="align-middle ps-3">{{ bill.name }}</td>
|
||||
<td class="align-middle">{{ bill.code }}</td>
|
||||
<td class="align-middle ps-3">{{ bill.bill_number }}</td>
|
||||
<td class="align-middle">{{ bill.bill_status }}</td>
|
||||
<td class="align-middle text-end py-3 pe-3">
|
||||
{% if bill.balance_type == 'debit' %}
|
||||
<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 %}
|
||||
{{bill.vendor.vendor_name}}
|
||||
</td>
|
||||
<td>
|
||||
<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>
|
||||
<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>
|
||||
</div>
|
||||
@ -157,8 +137,7 @@
|
||||
</nav>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
{% endif %}
|
||||
</div>
|
||||
</main>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
21
templates/ledger/bills/bill_update_form.html
Normal file
21
templates/ledger/bills/bill_update_form.html
Normal 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 %}
|
||||
50
templates/notifications.html
Normal file
50
templates/notifications.html
Normal 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>
|
||||
|
||||
@ -174,7 +174,7 @@
|
||||
<!-- ============================================-->
|
||||
{% endblock %}
|
||||
|
||||
{% block extra_js %}
|
||||
{% block customJS %}
|
||||
<script>
|
||||
function calculateTotals() {
|
||||
const table = document.getElementById('estimate-table');
|
||||
|
||||
@ -45,7 +45,7 @@
|
||||
</div>
|
||||
{% endblock content %}
|
||||
|
||||
{% block extra_js %}
|
||||
{% block customJS %}
|
||||
<script>
|
||||
const Toast = Swal.mixin({
|
||||
toast: true,
|
||||
@ -155,4 +155,4 @@
|
||||
}
|
||||
});
|
||||
</script>
|
||||
{% endblock extra_js %}
|
||||
{% endblock customJS %}
|
||||
@ -262,7 +262,7 @@
|
||||
</div>
|
||||
{% endblock %}
|
||||
|
||||
{% block extra_js %}
|
||||
{% block customJS %}
|
||||
<script>
|
||||
function calculateTotals() {
|
||||
const table = document.getElementById('estimate-table');
|
||||
|
||||
@ -12,20 +12,18 @@
|
||||
</style>
|
||||
{% endblock extra_css %}
|
||||
{% 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="col-md-8">
|
||||
<div class="card">
|
||||
{% if invoice.invoice_status == 'paid' %}
|
||||
{% if model.is_paid %}
|
||||
<div class="card-header">{{ _("Payment Already Made") }}</div>
|
||||
{% else %}
|
||||
<div class="card-header">{{ _("Make Payment") }}</div>
|
||||
{% endif %}
|
||||
<div class="card-body">
|
||||
{% if invoice %}
|
||||
<form method="post" action="{% url 'payment_create' pk=invoice.pk %}">
|
||||
{% else %}
|
||||
<form method="post" action="{% url 'payment_create' %}">
|
||||
{% if model %}
|
||||
<form method="post" action="{% url 'payment_create' pk=model.pk %}">
|
||||
{% endif %}
|
||||
{% csrf_token %}
|
||||
{{ form|crispy }}
|
||||
|
||||
@ -7,7 +7,7 @@
|
||||
<div class="row mt-4">
|
||||
<div class="d-flex justify-content-between mb-2">
|
||||
<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>
|
||||
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user