add car transfer
This commit is contained in:
parent
8f50c142fc
commit
6e3a2df75b
@ -17,6 +17,7 @@ from .models import (
|
|||||||
Vendor,
|
Vendor,
|
||||||
Customer,
|
Customer,
|
||||||
Car,
|
Car,
|
||||||
|
CarTransfer,
|
||||||
CarFinance,
|
CarFinance,
|
||||||
CustomCard,
|
CustomCard,
|
||||||
CarRegistration,
|
CarRegistration,
|
||||||
@ -242,6 +243,14 @@ class CarLocationForm(forms.ModelForm):
|
|||||||
"description": forms.Textarea(attrs={"rows": 2, "class": "form-control"}),
|
"description": forms.Textarea(attrs={"rows": 2, "class": "form-control"}),
|
||||||
}
|
}
|
||||||
|
|
||||||
|
class CarTransferForm(forms.ModelForm):
|
||||||
|
class Meta:
|
||||||
|
model = CarTransfer
|
||||||
|
fields = ["car", "to_dealer", "remarks"]
|
||||||
|
widgets = {
|
||||||
|
"remarks": forms.Textarea(attrs={"rows": 2, "class": "form-control"}),
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
# Custom Card Form
|
# Custom Card Form
|
||||||
class CustomCardForm(forms.ModelForm):
|
class CustomCardForm(forms.ModelForm):
|
||||||
|
|||||||
14
inventory/migrations/0005_merge_20250119_1555.py
Normal file
14
inventory/migrations/0005_merge_20250119_1555.py
Normal file
@ -0,0 +1,14 @@
|
|||||||
|
# Generated by Django 4.2.17 on 2025-01-19 12:55
|
||||||
|
|
||||||
|
from django.db import migrations
|
||||||
|
|
||||||
|
|
||||||
|
class Migration(migrations.Migration):
|
||||||
|
|
||||||
|
dependencies = [
|
||||||
|
('inventory', '0004_invoicemodelbase'),
|
||||||
|
('inventory', '0004_rename_assigned_lead_staff_remove_customer_city_and_more'),
|
||||||
|
]
|
||||||
|
|
||||||
|
operations = [
|
||||||
|
]
|
||||||
@ -0,0 +1,32 @@
|
|||||||
|
# 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',
|
||||||
|
),
|
||||||
|
]
|
||||||
@ -0,0 +1,24 @@
|
|||||||
|
# Generated by Django 4.2.17 on 2025-01-19 14:15
|
||||||
|
|
||||||
|
from django.db import migrations, models
|
||||||
|
import django.db.models.deletion
|
||||||
|
|
||||||
|
|
||||||
|
class Migration(migrations.Migration):
|
||||||
|
|
||||||
|
dependencies = [
|
||||||
|
('inventory', '0006_cartransferlog_delete_invoicemodelbase'),
|
||||||
|
]
|
||||||
|
|
||||||
|
operations = [
|
||||||
|
migrations.RemoveField(
|
||||||
|
model_name='cartransferlog',
|
||||||
|
name='cars',
|
||||||
|
),
|
||||||
|
migrations.AddField(
|
||||||
|
model_name='cartransferlog',
|
||||||
|
name='car',
|
||||||
|
field=models.ForeignKey(default=4, on_delete=django.db.models.deletion.CASCADE, related_name='transfer_logs', to='inventory.car', verbose_name='Car'),
|
||||||
|
preserve_default=False,
|
||||||
|
),
|
||||||
|
]
|
||||||
@ -0,0 +1,35 @@
|
|||||||
|
# Generated by Django 4.2.17 on 2025-01-19 14:29
|
||||||
|
|
||||||
|
import datetime
|
||||||
|
from django.db import migrations, models
|
||||||
|
|
||||||
|
|
||||||
|
class Migration(migrations.Migration):
|
||||||
|
|
||||||
|
dependencies = [
|
||||||
|
('inventory', '0007_remove_cartransferlog_cars_cartransferlog_car'),
|
||||||
|
]
|
||||||
|
|
||||||
|
operations = [
|
||||||
|
migrations.AddField(
|
||||||
|
model_name='cartransferlog',
|
||||||
|
name='created_at',
|
||||||
|
field=models.DateTimeField(auto_now_add=True, default=datetime.datetime(2025, 1, 19, 14, 29, 29, 771881, tzinfo=datetime.timezone.utc), verbose_name='Created At'),
|
||||||
|
preserve_default=False,
|
||||||
|
),
|
||||||
|
migrations.AddField(
|
||||||
|
model_name='cartransferlog',
|
||||||
|
name='is_approved',
|
||||||
|
field=models.BooleanField(default=False),
|
||||||
|
),
|
||||||
|
migrations.AddField(
|
||||||
|
model_name='cartransferlog',
|
||||||
|
name='status',
|
||||||
|
field=models.CharField(default='pending', max_length=10, verbose_name=[('draft', 'Draft'), ('approved', 'Approved'), ('pending', 'Pending'), ('success', 'Success'), ('failure', 'Failure')]),
|
||||||
|
),
|
||||||
|
migrations.AddField(
|
||||||
|
model_name='cartransferlog',
|
||||||
|
name='updated_at',
|
||||||
|
field=models.DateTimeField(auto_now=True, verbose_name='Updated At'),
|
||||||
|
),
|
||||||
|
]
|
||||||
@ -0,0 +1,36 @@
|
|||||||
|
# Generated by Django 4.2.17 on 2025-01-19 14:31
|
||||||
|
|
||||||
|
from django.db import migrations, models
|
||||||
|
import django.db.models.deletion
|
||||||
|
|
||||||
|
|
||||||
|
class Migration(migrations.Migration):
|
||||||
|
|
||||||
|
dependencies = [
|
||||||
|
('inventory', '0008_cartransferlog_created_at_cartransferlog_is_approved_and_more'),
|
||||||
|
]
|
||||||
|
|
||||||
|
operations = [
|
||||||
|
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')),
|
||||||
|
('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'), ('success', 'Success'), ('failure', 'Failure')])),
|
||||||
|
('is_approved', models.BooleanField(default=False)),
|
||||||
|
('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.DeleteModel(
|
||||||
|
name='CarTransferLog',
|
||||||
|
),
|
||||||
|
]
|
||||||
@ -0,0 +1,23 @@
|
|||||||
|
# Generated by Django 4.2.17 on 2025-01-19 15:20
|
||||||
|
|
||||||
|
from django.db import migrations, models
|
||||||
|
|
||||||
|
|
||||||
|
class Migration(migrations.Migration):
|
||||||
|
|
||||||
|
dependencies = [
|
||||||
|
('inventory', '0009_cartransfer_delete_cartransferlog'),
|
||||||
|
]
|
||||||
|
|
||||||
|
operations = [
|
||||||
|
migrations.AddField(
|
||||||
|
model_name='cartransfer',
|
||||||
|
name='active',
|
||||||
|
field=models.BooleanField(default=True),
|
||||||
|
),
|
||||||
|
migrations.AlterField(
|
||||||
|
model_name='car',
|
||||||
|
name='status',
|
||||||
|
field=models.CharField(choices=[('available', 'Available'), ('sold', 'Sold'), ('hold', 'Hold'), ('damaged', 'Damaged'), ('reserved', 'Reserved'), ('transfer', 'Transfer')], default='available', max_length=10, verbose_name='Status'),
|
||||||
|
),
|
||||||
|
]
|
||||||
18
inventory/migrations/0011_alter_cartransfer_status.py
Normal file
18
inventory/migrations/0011_alter_cartransfer_status.py
Normal file
@ -0,0 +1,18 @@
|
|||||||
|
# Generated by Django 4.2.17 on 2025-01-20 08:11
|
||||||
|
|
||||||
|
from django.db import migrations, models
|
||||||
|
|
||||||
|
|
||||||
|
class Migration(migrations.Migration):
|
||||||
|
|
||||||
|
dependencies = [
|
||||||
|
('inventory', '0010_cartransfer_active_alter_car_status'),
|
||||||
|
]
|
||||||
|
|
||||||
|
operations = [
|
||||||
|
migrations.AlterField(
|
||||||
|
model_name='cartransfer',
|
||||||
|
name='status',
|
||||||
|
field=models.CharField(default='draft', max_length=10, verbose_name=[('draft', 'Draft'), ('approved', 'Approved'), ('pending', 'Pending'), ('accept', 'Accept'), ('success', 'Success'), ('failure', 'Failure')]),
|
||||||
|
),
|
||||||
|
]
|
||||||
18
inventory/migrations/0012_cartransfer_quantity.py
Normal file
18
inventory/migrations/0012_cartransfer_quantity.py
Normal file
@ -0,0 +1,18 @@
|
|||||||
|
# Generated by Django 4.2.17 on 2025-01-20 08:21
|
||||||
|
|
||||||
|
from django.db import migrations, models
|
||||||
|
|
||||||
|
|
||||||
|
class Migration(migrations.Migration):
|
||||||
|
|
||||||
|
dependencies = [
|
||||||
|
('inventory', '0011_alter_cartransfer_status'),
|
||||||
|
]
|
||||||
|
|
||||||
|
operations = [
|
||||||
|
migrations.AddField(
|
||||||
|
model_name='cartransfer',
|
||||||
|
name='quantity',
|
||||||
|
field=models.IntegerField(default=1, verbose_name='Quantity'),
|
||||||
|
),
|
||||||
|
]
|
||||||
18
inventory/migrations/0013_alter_cartransfer_status.py
Normal file
18
inventory/migrations/0013_alter_cartransfer_status.py
Normal file
@ -0,0 +1,18 @@
|
|||||||
|
# Generated by Django 4.2.17 on 2025-01-20 09:17
|
||||||
|
|
||||||
|
from django.db import migrations, models
|
||||||
|
|
||||||
|
|
||||||
|
class Migration(migrations.Migration):
|
||||||
|
|
||||||
|
dependencies = [
|
||||||
|
('inventory', '0012_cartransfer_quantity'),
|
||||||
|
]
|
||||||
|
|
||||||
|
operations = [
|
||||||
|
migrations.AlterField(
|
||||||
|
model_name='cartransfer',
|
||||||
|
name='status',
|
||||||
|
field=models.CharField(default='draft', max_length=10, verbose_name=[('draft', 'Draft'), ('approved', 'Approved'), ('pending', 'Pending'), ('accept', 'Accept'), ('success', 'Success'), ('reject', 'Reject')]),
|
||||||
|
),
|
||||||
|
]
|
||||||
File diff suppressed because it is too large
Load Diff
@ -155,19 +155,7 @@ def create_ledger_entity(sender, instance, created, **kwargs):
|
|||||||
balance_type="debit",
|
balance_type="debit",
|
||||||
active=True,
|
active=True,
|
||||||
)
|
)
|
||||||
|
|
||||||
# Inventory Account
|
|
||||||
asset_ca_inventory = entity.create_account(
|
|
||||||
coa_model=coa,
|
|
||||||
code="1106",
|
|
||||||
role=roles.ASSET_CA_INVENTORY,
|
|
||||||
name=_("Inventory"),
|
|
||||||
balance_type="debit",
|
|
||||||
active=True,
|
|
||||||
)
|
|
||||||
asset_ca_inventory.role_default = True
|
|
||||||
asset_ca_inventory.save()
|
|
||||||
|
|
||||||
# VAT Payable Account
|
# VAT Payable Account
|
||||||
liability_ltl_vat_receivable = entity.create_account(
|
liability_ltl_vat_receivable = entity.create_account(
|
||||||
coa_model=coa,
|
coa_model=coa,
|
||||||
@ -337,7 +325,7 @@ def create_ledger_entity(sender, instance, created, **kwargs):
|
|||||||
# Mortgage Payable Account
|
# Mortgage Payable Account
|
||||||
liability_ltl_mortgage_payable = entity.create_account(
|
liability_ltl_mortgage_payable = entity.create_account(
|
||||||
coa_model=coa,
|
coa_model=coa,
|
||||||
code="2202",
|
code="2203",
|
||||||
role=roles.LIABILITY_LTL_MORTGAGE_PAYABLE,
|
role=roles.LIABILITY_LTL_MORTGAGE_PAYABLE,
|
||||||
name=_("Mortgage Payable"),
|
name=_("Mortgage Payable"),
|
||||||
balance_type="credit",
|
balance_type="credit",
|
||||||
|
|||||||
@ -193,9 +193,29 @@ urlpatterns = [
|
|||||||
),
|
),
|
||||||
path(
|
path(
|
||||||
"cars/<int:pk>/location/update/",
|
"cars/<int:pk>/location/update/",
|
||||||
views.CarLocationUpdateView.as_view(),
|
views.CarTransferCreateView.as_view(),
|
||||||
name="transfer",
|
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/inventory/search/", views.SearchCodeView.as_view(), name="car_search"),
|
||||||
# path('cars/<int:car_pk>/colors/<int:pk>/update/',views.CarColorUpdateView.as_view(),name='color_update'),
|
# path('cars/<int:car_pk>/colors/<int:pk>/update/',views.CarColorUpdateView.as_view(),name='color_update'),
|
||||||
path("cars/reserve/<int:car_id>/", views.reserve_car_view, name="reserve_car"),
|
path("cars/reserve/<int:car_id>/", views.reserve_car_view, name="reserve_car"),
|
||||||
|
|||||||
@ -1,6 +1,9 @@
|
|||||||
|
import json
|
||||||
|
import datetime
|
||||||
from django.shortcuts import redirect
|
from django.shortcuts import redirect
|
||||||
from django.contrib import messages
|
from django.contrib import messages
|
||||||
from django.utils import timezone
|
from django.utils import timezone
|
||||||
|
from django_ledger.models.entity import UnitOfMeasureModel
|
||||||
from django_ledger.models.journal_entry import JournalEntryModel
|
from django_ledger.models.journal_entry import JournalEntryModel
|
||||||
from django_ledger.models.ledger import LedgerModel
|
from django_ledger.models.ledger import LedgerModel
|
||||||
from django_ledger.models.transactions import TransactionModel
|
from django_ledger.models.transactions import TransactionModel
|
||||||
@ -11,7 +14,7 @@ from django.core.mail import send_mail
|
|||||||
from django.utils.translation import gettext_lazy as _
|
from django.utils.translation import gettext_lazy as _
|
||||||
from inventory.utilities.financials import get_financial_value
|
from inventory.utilities.financials import get_financial_value
|
||||||
from django_ledger.models.items import ItemModel
|
from django_ledger.models.items import ItemModel
|
||||||
from django_ledger.models import InvoiceModel, EstimateModel
|
from django_ledger.models import InvoiceModel, EstimateModel,BillModel
|
||||||
from decimal import Decimal
|
from decimal import Decimal
|
||||||
|
|
||||||
|
|
||||||
@ -253,18 +256,10 @@ def set_invoice_payment(dealer, entity, invoice, amount, payment_method):
|
|||||||
|
|
||||||
|
|
||||||
def set_bill_payment(dealer, entity, bill, amount, payment_method):
|
def set_bill_payment(dealer, entity, bill, amount, payment_method):
|
||||||
vat_amount = 0
|
|
||||||
total_amount = 0
|
total_amount = 0
|
||||||
|
for x in bill.get_itemtxs_data()[0].all():
|
||||||
|
total_amount += Decimal(x.unit_cost) * Decimal(x.quantity)
|
||||||
|
|
||||||
if bill.terms == "on_receipt":
|
|
||||||
for x in bill.get_itemtxs_data()[0].all():
|
|
||||||
vat_amount += models.Car.objects.get(
|
|
||||||
vin=x.item_model.name
|
|
||||||
).finances.cost_price * Decimal(x.quantity)
|
|
||||||
total_amount += Decimal(x.unit_cost) * Decimal(x.quantity)
|
|
||||||
|
|
||||||
# grand_total = total_amount - Decimal(vat_amount)
|
|
||||||
|
|
||||||
journal = JournalEntryModel.objects.create(
|
journal = JournalEntryModel.objects.create(
|
||||||
posted=False,
|
posted=False,
|
||||||
description=f"Payment for bill {bill.bill_number}",
|
description=f"Payment for bill {bill.bill_number}",
|
||||||
@ -279,9 +274,7 @@ def set_bill_payment(dealer, entity, bill, amount, payment_method):
|
|||||||
name="Accounts Payable", active=True
|
name="Accounts Payable", active=True
|
||||||
)
|
)
|
||||||
|
|
||||||
# vat_payable_account = entity.get_default_coa_accounts().get(
|
|
||||||
# name="VAT Payable", active=True
|
|
||||||
# )
|
|
||||||
TransactionModel.objects.create(
|
TransactionModel.objects.create(
|
||||||
journal_entry=journal,
|
journal_entry=journal,
|
||||||
account=cash_account, # Debit Cash
|
account=cash_account, # Debit Cash
|
||||||
@ -298,14 +291,182 @@ def set_bill_payment(dealer, entity, bill, amount, payment_method):
|
|||||||
description="Payment Received",
|
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 bill",
|
|
||||||
# )
|
|
||||||
|
|
||||||
bill.make_payment(amount)
|
bill.make_payment(amount)
|
||||||
bill.save()
|
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()
|
||||||
|
|
||||||
|
import random
|
||||||
|
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=to_dealer.phone_number,
|
||||||
|
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")
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
@ -83,6 +83,7 @@ from .utils import (
|
|||||||
get_user_type,
|
get_user_type,
|
||||||
set_bill_payment,
|
set_bill_payment,
|
||||||
set_invoice_payment,
|
set_invoice_payment,
|
||||||
|
transfer_car,
|
||||||
)
|
)
|
||||||
from django.contrib.auth.models import User
|
from django.contrib.auth.models import User
|
||||||
from allauth.account import views
|
from allauth.account import views
|
||||||
@ -777,6 +778,112 @@ class CarLocationUpdateView(UpdateView):
|
|||||||
def get_success_url(self):
|
def get_success_url(self):
|
||||||
return reverse_lazy("car_detail", kwargs={"pk": self.object.car.pk})
|
return reverse_lazy("car_detail", kwargs={"pk": self.object.car.pk})
|
||||||
|
|
||||||
|
class CarTransferCreateView(CreateView):
|
||||||
|
model = models.CarTransfer
|
||||||
|
form_class = forms.CarTransferForm
|
||||||
|
template_name = "inventory/car_location_form.html"
|
||||||
|
|
||||||
|
def get_form(self, form_class = None):
|
||||||
|
form = super().get_form(form_class)
|
||||||
|
form.fields['to_dealer'].queryset = models.Dealer.objects.exclude(pk=get_user_type(self.request).pk).all()
|
||||||
|
form.fields['car'].queryset = models.Car.objects.filter(pk=self.kwargs["pk"])
|
||||||
|
return form
|
||||||
|
def get_initial(self):
|
||||||
|
initial = super().get_initial()
|
||||||
|
initial["car"] = get_object_or_404(models.Car, pk=self.kwargs["pk"])
|
||||||
|
return initial
|
||||||
|
|
||||||
|
def form_valid(self, form):
|
||||||
|
form.instance.from_dealer = get_user_type(self.request)
|
||||||
|
form.instance.car.status = "transfer"
|
||||||
|
form.instance.car.save()
|
||||||
|
return super().form_valid(form)
|
||||||
|
|
||||||
|
def get_success_url(self):
|
||||||
|
return reverse_lazy("car_detail", kwargs={"pk": self.object.car.pk})
|
||||||
|
|
||||||
|
def CarTransferDetailView(request, pk):
|
||||||
|
transfer = get_object_or_404(models.CarTransfer, pk=pk)
|
||||||
|
context = {"transfer":transfer}
|
||||||
|
return render(request,'inventory/transfer_details.html',context)
|
||||||
|
|
||||||
|
def car_transfer_approve(request, car_pk,transfer_pk):
|
||||||
|
car = get_object_or_404(models.Car, pk=car_pk)
|
||||||
|
transfer = get_object_or_404(models.CarTransfer, pk=transfer_pk)
|
||||||
|
transfer.status = "approved"
|
||||||
|
transfer.save()
|
||||||
|
url = request.build_absolute_uri(reverse('transfer_preview',kwargs={'car_pk':car.pk,"transfer_pk":transfer.pk}))
|
||||||
|
models.Notification.objects.create(
|
||||||
|
user=transfer.to_dealer.user,
|
||||||
|
message=f"Car transfer request from {transfer.from_dealer} is waiting for your acceptance. <a href='{url}'> Accept</a>",
|
||||||
|
)
|
||||||
|
messages.success(request, _("Car transfer approved successfully."))
|
||||||
|
return redirect("car_detail", pk=car.pk)
|
||||||
|
|
||||||
|
def car_transfer_accept_reject(request, car_pk,transfer_pk):
|
||||||
|
car = get_object_or_404(models.Car, pk=car_pk)
|
||||||
|
transfer = get_object_or_404(models.CarTransfer, pk=transfer_pk)
|
||||||
|
status = request.GET.get("status")
|
||||||
|
if status == "rejected":
|
||||||
|
transfer.status = "reject"
|
||||||
|
transfer.active = False
|
||||||
|
messages.success(request, _("Car transfer rejected successfully."))
|
||||||
|
transfer.save()
|
||||||
|
elif status == "accepted":
|
||||||
|
transfer.status = "accept"
|
||||||
|
transfer.save()
|
||||||
|
success = transfer_car(car,transfer)
|
||||||
|
if success:
|
||||||
|
messages.success(request, _("Car Transfer Completed successfully."))
|
||||||
|
return redirect("inventory_stats")
|
||||||
|
|
||||||
|
def CarTransferPreviewView(request, car_pk,transfer_pk):
|
||||||
|
transfer = get_object_or_404(models.CarTransfer, pk=transfer_pk)
|
||||||
|
if transfer.to_dealer != get_user_type(request):
|
||||||
|
return redirect("car_detail", pk=car_pk)
|
||||||
|
return render(request,'inventory/transfer_preview.html',{"transfer":transfer})
|
||||||
|
# def get_context_data(self, **kwargs):
|
||||||
|
# estimate = kwargs.get("object")
|
||||||
|
# if estimate.get_itemtxs_data():
|
||||||
|
# data = get_financial_values(estimate)
|
||||||
|
|
||||||
|
# kwargs["vat_amount"] = data["vat_amount"]
|
||||||
|
# kwargs["total"] = data["grand_total"]
|
||||||
|
# kwargs["discount_amount"] = data["discount_amount"]
|
||||||
|
# kwargs["vat"] = data["vat"]
|
||||||
|
# kwargs["car_and_item_info"] = data["car_and_item_info"]
|
||||||
|
# kwargs["additional_services"] = data["additional_services"]
|
||||||
|
# return super().get_context_data(**kwargs)
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
# class CarTransferView(View):
|
||||||
|
# template_name = "inventory/car_location_form.html"
|
||||||
|
|
||||||
|
# def get(self, request, *args, **kwargs):
|
||||||
|
# form = forms.CarTransferForm()
|
||||||
|
# car = models.Car.objects.filter(pk=self.kwargs["pk"])
|
||||||
|
# form.fields['to_dealer'].queryset = form.fields['to_dealer'].queryset.exclude(pk=get_user_type(request).pk)
|
||||||
|
# form.fields['car'].queryset = car
|
||||||
|
# form.initial['car'] = car.first()
|
||||||
|
# context = {"form": form}
|
||||||
|
# return render(request, self.template_name,context)
|
||||||
|
|
||||||
|
# def post(self, request, *args, **kwargs):
|
||||||
|
# form = forms.CarTransferForm(request.POST)
|
||||||
|
# if form.is_valid():
|
||||||
|
# from_dealer = get_user_type(request)
|
||||||
|
# car = form.cleaned_data['car']
|
||||||
|
# to_dealer = form.cleaned_data['to_dealer']
|
||||||
|
# remarks = form.cleaned_data['remarks']
|
||||||
|
# models.CarTransferLog.objects.create(car=car, from_dealer=from_dealer, to_dealer=to_dealer, remarks=remarks)
|
||||||
|
# # car = models.Car.objects.filter(pk=self.kwargs["pk"])
|
||||||
|
# # form.instance.car = car.first()
|
||||||
|
# # form.instance.to_dealer = get_user_type(request)
|
||||||
|
# # form.save()
|
||||||
|
# # messages.success(request, "Car transfered successfully.")
|
||||||
|
# return redirect("car_detail", pk=self.kwargs["pk"])
|
||||||
|
|
||||||
class CustomCardCreateView(LoginRequiredMixin, CreateView):
|
class CustomCardCreateView(LoginRequiredMixin, CreateView):
|
||||||
model = models.CustomCard
|
model = models.CustomCard
|
||||||
|
|||||||
@ -13,7 +13,7 @@
|
|||||||
<div class="me-3 flex-1 mt-2">
|
<div class="me-3 flex-1 mt-2">
|
||||||
<h4 class="fs-9 text-body-emphasis">{{ _("System")}}:</h4>
|
<h4 class="fs-9 text-body-emphasis">{{ _("System")}}:</h4>
|
||||||
{% if not notification.is_read %}
|
{% if not notification.is_read %}
|
||||||
<p class="fs-9 text-body-highlight"><span class="far fa-envelope text-success-dark fs-8 me-1"></span><span class="me-1">{{ notification.message }}</span> <span class="ms-2 text-body-tertiary text-opacity-85 fw-bold fs-10 text-end">{{ notification.created|timesince }}</span></p>
|
<p class="fs-9 text-body-highlight"><span class="far fa-envelope text-success-dark fs-8 me-1"></span><span class="me-1">{{ notification.message|safe }}</span> <span class="ms-2 text-body-tertiary text-opacity-85 fw-bold fs-10 text-end">{{ notification.created|timesince }}</span></p>
|
||||||
{% else %}
|
{% else %}
|
||||||
<p class="fs-9 text-body-highlight"><span class="far fa-envelope-open text-danger-dark fs-8 me-1"></span><span>{{ notification.message }}</span> <span class="ms-2 text-body-tertiary text-opacity-85 fw-bold fs-10 text-end">{{ notification.created|timesince }}</span></p>
|
<p class="fs-9 text-body-highlight"><span class="far fa-envelope-open text-danger-dark fs-8 me-1"></span><span>{{ notification.message }}</span> <span class="ms-2 text-body-tertiary text-opacity-85 fw-bold fs-10 text-end">{{ notification.created|timesince }}</span></p>
|
||||||
{% endif %}
|
{% endif %}
|
||||||
|
|||||||
@ -339,7 +339,7 @@
|
|||||||
<div class="text-center pt-4 pb-3">
|
<div class="text-center pt-4 pb-3">
|
||||||
|
|
||||||
<div class="avatar avatar-xl">
|
<div class="avatar avatar-xl">
|
||||||
{% if user.dealer.logo %}
|
{% if user.dealer.logo %}
|
||||||
<img class="rounded-circle" src="{{ user.dealer.logo.url }}" alt="" />
|
<img class="rounded-circle" src="{{ user.dealer.logo.url }}" alt="" />
|
||||||
{% elif user.staff.dealer.logo %}
|
{% elif user.staff.dealer.logo %}
|
||||||
<img class="rounded-circle" src="{{ user.staff.dealer.logo.url }}" alt="" />
|
<img class="rounded-circle" src="{{ user.staff.dealer.logo.url }}" alt="" />
|
||||||
|
|||||||
@ -93,18 +93,21 @@
|
|||||||
</tr>
|
</tr>
|
||||||
{% endif %}
|
{% endif %}
|
||||||
<tr>
|
<tr>
|
||||||
|
|
||||||
<th>{% trans 'Location'|capfirst %}</th>
|
<th>{% trans 'Location'|capfirst %}</th>
|
||||||
<td>
|
<td>
|
||||||
{% if car.location %} {% if car.location.is_owner_showroom %} {% trans 'Our Showroom' %} {% else %} {{ car.location.showroom.get_local_name }} {% endif %}
|
{% if car.finances and not car.get_transfer %}
|
||||||
<a href="{% url 'transfer' car.location.pk %}" class="btn btn-phoenix-danger btn-sm">
|
{% if car.location %} {% if car.location.is_owner_showroom %} {% trans 'Our Showroom' %} {% else %} {{ car.location.showroom.get_local_name }} {% endif %}
|
||||||
{% trans "transfer"|capfirst %}
|
<a href="{% url 'transfer' car.location.pk %}" class="btn btn-phoenix-danger btn-sm">
|
||||||
</a>
|
{% trans "transfer"|capfirst %}
|
||||||
{% else %} {% trans "No location available." %}
|
</a>
|
||||||
<a href="{% url 'add_car_location' car.pk %}" class="btn btn-phoenix-success btn-sm ms-2">
|
{% else %} {% trans "No location available." %}
|
||||||
{% trans "Add" %}
|
<a href="{% url 'add_car_location' car.pk %}" class="btn btn-phoenix-success btn-sm ms-2">
|
||||||
</a>
|
{% trans "Add" %}
|
||||||
|
</a>
|
||||||
|
{% endif %}
|
||||||
|
{% endif %}
|
||||||
</td>
|
</td>
|
||||||
{% endif %}
|
|
||||||
</tr>
|
</tr>
|
||||||
</table>
|
</table>
|
||||||
</div>
|
</div>
|
||||||
@ -215,6 +218,7 @@
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
{% if car.status != 'transfer' %}
|
||||||
<div class="card rounded shadow d-flex align-content-center mt-3">
|
<div class="card rounded shadow d-flex align-content-center mt-3">
|
||||||
<p class="card-header rounded-top fw-bold">{% trans 'Reservations Details' %}</p>
|
<p class="card-header rounded-top fw-bold">{% trans 'Reservations Details' %}</p>
|
||||||
<div class="card-body">
|
<div class="card-body">
|
||||||
@ -266,6 +270,50 @@
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
{% endif %}
|
||||||
|
<!-- Transfer Table -->
|
||||||
|
{% if car.status == 'transfer' and car.get_transfer %}
|
||||||
|
<div class="card rounded shadow d-flex align-content-center mt-3">
|
||||||
|
<p class="card-header rounded-top fw-bold">{% trans 'Transfer Details' %}</p>
|
||||||
|
<div class="card-body">
|
||||||
|
<div class="table-responsive scrollbar mb-3">
|
||||||
|
<table class="table table-sm fs-9 mb-0 overflow-hidden">
|
||||||
|
<thead>
|
||||||
|
<tr>
|
||||||
|
<th>{% trans "Action" %}</th>
|
||||||
|
<th>{% trans "Status" %}</th>
|
||||||
|
<th>{% trans "From Showroom" %}</th>
|
||||||
|
<th>{% trans "To Showroom" %}</th>
|
||||||
|
<th>{% trans 'Date' %}</th>
|
||||||
|
<th></th>
|
||||||
|
</tr>
|
||||||
|
</thead>
|
||||||
|
<tbody>
|
||||||
|
|
||||||
|
<tr>
|
||||||
|
<td>Transfer</td>
|
||||||
|
<td>
|
||||||
|
{% if car.get_transfer.status == "draft" %}
|
||||||
|
waiting for approval
|
||||||
|
{% elif car.get_transfer.status == "approved" %}
|
||||||
|
waiting for dealer acceptance
|
||||||
|
{% endif %}
|
||||||
|
</td>
|
||||||
|
<td>{{ car.get_transfer.from_dealer }}</td>
|
||||||
|
<td>{{ car.get_transfer.to_dealer }}</td>
|
||||||
|
<td>{{ car.get_transfer.transfer_date }}</td>
|
||||||
|
<td>
|
||||||
|
{% if car.get_transfer.status == "draft" %}
|
||||||
|
<a href="{% url 'transfer_detail' car.get_transfer.pk %}">Approve</a>
|
||||||
|
{% endif %}
|
||||||
|
</td>
|
||||||
|
</tr>
|
||||||
|
</tbody>
|
||||||
|
</table>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
{% endif %}
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
47
templates/inventory/transfer_details.html
Normal file
47
templates/inventory/transfer_details.html
Normal file
@ -0,0 +1,47 @@
|
|||||||
|
{% extends 'base.html' %}
|
||||||
|
{% load crispy_forms_filters %}
|
||||||
|
{% load i18n %}
|
||||||
|
{% load custom_filters %}
|
||||||
|
|
||||||
|
{% block title %}
|
||||||
|
{% trans 'Car Transfer Details' %}
|
||||||
|
{% endblock %}
|
||||||
|
|
||||||
|
{% block content %}
|
||||||
|
<div class="modal fade" id="approveCardModal" tabindex="-1" aria-labelledby="approveModalLabel" 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="approveModalLabel">{% trans 'Car Transfer Approve' %}</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>
|
||||||
|
<div class="modal-footer">
|
||||||
|
<button type="button" class="btn btn-sm btn-phoenix-danger" data-bs-dismiss="modal">{% trans 'No' %}</button>
|
||||||
|
<form method="POST" action="{% url 'transfer_confirm' transfer.car.pk transfer.pk %}" class="d-inline">
|
||||||
|
{% csrf_token %}
|
||||||
|
<button type="submit" class="btn btn-phoenix-success btn-sm">{% trans 'Confirm' %}</button>
|
||||||
|
</form>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="row p-4">
|
||||||
|
<div class="row g-1">
|
||||||
|
<div class="col-lg-4 col-xl-12"></div>
|
||||||
|
</div>
|
||||||
|
<div class="row g-1">
|
||||||
|
<div class="btn-group">
|
||||||
|
<button type="button" class="btn btn-sm btn-phoenix-success" data-bs-toggle="modal" data-bs-target="#approveCardModal">
|
||||||
|
{% trans 'Approve' %}
|
||||||
|
</button>
|
||||||
|
<a href="{{ request.META.HTTP_REFERER }}" class="btn btn-sm btn-danger">{% trans 'Cancel' %}</a>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- JavaScript Section -->
|
||||||
|
{% endblock %}
|
||||||
339
templates/inventory/transfer_preview.html
Normal file
339
templates/inventory/transfer_preview.html
Normal file
@ -0,0 +1,339 @@
|
|||||||
|
{% load static i18n %}
|
||||||
|
<!DOCTYPE html>
|
||||||
|
<html lang="en">
|
||||||
|
<head>
|
||||||
|
<meta charset="UTF-8">
|
||||||
|
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||||
|
<title>transfer</title>
|
||||||
|
<!-- Bootstrap CSS -->
|
||||||
|
<link href="https://cdn.jsdelivr.net/npm/bootstrap@5.3.0/dist/css/bootstrap.min.css" rel="stylesheet">
|
||||||
|
<!-- Google Fonts - Roboto -->
|
||||||
|
<link href="https://fonts.googleapis.com/css2?family=Roboto:wght@300;400;500;700&display=swap" rel="stylesheet">
|
||||||
|
<!-- Custom CSS -->
|
||||||
|
<style>
|
||||||
|
body {
|
||||||
|
background-color: #f8f9fa;
|
||||||
|
font-family: 'Roboto', sans-serif;
|
||||||
|
margin: 0;
|
||||||
|
padding: 0;
|
||||||
|
}
|
||||||
|
.transfer-row {
|
||||||
|
background: #fff;
|
||||||
|
border-radius: 12px;
|
||||||
|
box-shadow: 0 6px 12px rgba(0, 0, 0, 0.1);
|
||||||
|
padding: 3rem;
|
||||||
|
margin: 3rem auto;
|
||||||
|
max-width: 1000px;
|
||||||
|
}
|
||||||
|
.transfer-header {
|
||||||
|
border-bottom: 2px solid #dee2e6;
|
||||||
|
padding-bottom: 1.5rem;
|
||||||
|
margin-bottom: 2.5rem;
|
||||||
|
text-align: center;
|
||||||
|
}
|
||||||
|
.transfer-header h1 {
|
||||||
|
font-size: 2.5rem;
|
||||||
|
font-weight: 700;
|
||||||
|
color: #333;
|
||||||
|
margin-bottom: 0.5rem;
|
||||||
|
}
|
||||||
|
.transfer-header p {
|
||||||
|
font-size: 1.1rem;
|
||||||
|
color: #666;
|
||||||
|
font-weight: 400;
|
||||||
|
}
|
||||||
|
.transfer-details {
|
||||||
|
margin-bottom: 2.5rem;
|
||||||
|
}
|
||||||
|
.transfer-details p {
|
||||||
|
margin: 0.75rem 0;
|
||||||
|
color: #555;
|
||||||
|
font-size: 1rem;
|
||||||
|
font-weight: 400;
|
||||||
|
}
|
||||||
|
.transfer-table {
|
||||||
|
margin-bottom: 2.5rem;
|
||||||
|
}
|
||||||
|
.transfer-table th {
|
||||||
|
background-color: #f8f9fa;
|
||||||
|
font-weight: 500;
|
||||||
|
color: #333;
|
||||||
|
}
|
||||||
|
.transfer-table td {
|
||||||
|
color: #555;
|
||||||
|
font-weight: 400;
|
||||||
|
}
|
||||||
|
.additional-charges {
|
||||||
|
margin-top: 2rem;
|
||||||
|
margin-bottom: 1.5rem;
|
||||||
|
}
|
||||||
|
.additional-charges p {
|
||||||
|
margin: 0.5rem 0;
|
||||||
|
color: #555;
|
||||||
|
font-size: 1rem;
|
||||||
|
font-weight: 400;
|
||||||
|
}
|
||||||
|
.transfer-total {
|
||||||
|
text-align: right;
|
||||||
|
font-size: 1.5rem;
|
||||||
|
font-weight: 500;
|
||||||
|
color: #333;
|
||||||
|
margin-top: 1rem;
|
||||||
|
}
|
||||||
|
.footer-note {
|
||||||
|
text-align: center;
|
||||||
|
color: #777;
|
||||||
|
margin-top: 3rem;
|
||||||
|
font-size: 1rem;
|
||||||
|
font-weight: 400;
|
||||||
|
}
|
||||||
|
.logo {
|
||||||
|
max-width: 150px;
|
||||||
|
margin-bottom: 1.5rem;
|
||||||
|
}
|
||||||
|
.highlight {
|
||||||
|
color: #007bff;
|
||||||
|
font-weight: 500;
|
||||||
|
}
|
||||||
|
.btn-primary {
|
||||||
|
background-color: #007bff;
|
||||||
|
border-color: #007bff;
|
||||||
|
font-weight: 500;
|
||||||
|
padding: 0.75rem 1.5rem;
|
||||||
|
font-size: 1rem;
|
||||||
|
}
|
||||||
|
.btn-primary:hover {
|
||||||
|
background-color: #0056b3;
|
||||||
|
border-color: #0056b3;
|
||||||
|
}
|
||||||
|
.button-row {
|
||||||
|
display: flex;
|
||||||
|
justify-content: center;
|
||||||
|
gap: 10px;
|
||||||
|
margin-top: 2rem;
|
||||||
|
}
|
||||||
|
</style>
|
||||||
|
{% if LANGUAGE_CODE == 'en' %}
|
||||||
|
<link href="{% static 'css/theme.min.css' %}" type="text/css" rel="stylesheet" id="style-default">
|
||||||
|
<link href="{% static 'css/user.min.css' %}" type="text/css" rel="stylesheet" id="user-style-default">
|
||||||
|
{% else %}
|
||||||
|
<link href="{% static 'css/theme-rtl.min.css' %}" type="text/css" rel="stylesheet" id="style-rtl">
|
||||||
|
<link href="{% static 'css/user-rtl.min.css' %}" type="text/css" rel="stylesheet" id="user-style-rtl">
|
||||||
|
{% endif %}
|
||||||
|
<link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/6.0.0-beta3/css/all.min.css">
|
||||||
|
</head>
|
||||||
|
<body>
|
||||||
|
{% if transfer.status != "approved" %}
|
||||||
|
<main class="main" id="top">
|
||||||
|
<div class="px-3">
|
||||||
|
<div class="row min-vh-100 flex-center p-5">
|
||||||
|
<div class="col-12 col-xl-10 col-xxl-8">
|
||||||
|
<div class="row justify-content-center align-items-center g-5">
|
||||||
|
<div class="col-12 col-lg-6 text-center order-lg-1"><img class="img-fluid w-lg-100 d-dark-none" src="{% static 'images/spot-illustrations/404-illustration.png' %}" alt="" width="400" /><img class="img-fluid w-md-50 w-lg-100 d-light-none" src="../../assets/img/spot-illustrations/dark_404-illustration.png" alt="" width="540" /></div>
|
||||||
|
<div class="col-12 col-lg-6 text-center text-lg-start"><img class="img-fluid mb-6 w-50 w-lg-75 d-dark-none" src="{% static 'images/spot-illustrations/404.png' %}" alt="" /><img class="img-fluid mb-6 w-50 w-lg-75 d-light-none" src="../../assets/img/spot-illustrations/dark_404.png" alt="" />
|
||||||
|
<h2 class="text-body-secondary fw-bolder mb-3">Page Missing!</h2>
|
||||||
|
<p class="text-body mb-5">But no worries! Our ostrich is looking everywhere <br class="d-none d-sm-block" />
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<script>
|
||||||
|
var navbarTopStyle = window.config.config.phoenixNavbarTopStyle;
|
||||||
|
var navbarTop = document.querySelector('.navbar-top');
|
||||||
|
if (navbarTopStyle === 'darker') {
|
||||||
|
navbarTop.setAttribute('data-navbar-appearance', 'darker');
|
||||||
|
}
|
||||||
|
|
||||||
|
var navbarVerticalStyle = window.config.config.phoenixNavbarVerticalStyle;
|
||||||
|
var navbarVertical = document.querySelector('.navbar-vertical');
|
||||||
|
if (navbarVertical && navbarVerticalStyle === 'darker') {
|
||||||
|
navbarVertical.setAttribute('data-navbar-appearance', 'darker');
|
||||||
|
}
|
||||||
|
</script>
|
||||||
|
<div class="support-chat-row">
|
||||||
|
<div class="row-fluid support-chat">
|
||||||
|
<div class="card bg-body-emphasis">
|
||||||
|
<div class="card-header d-flex flex-between-center px-4 py-3 border-bottom border-translucent">
|
||||||
|
<h5 class="mb-0 d-flex align-items-center gap-2">Demo widget<span class="fa-solid fa-circle text-success fs-11"></span></h5>
|
||||||
|
<div class="btn-reveal-trigger">
|
||||||
|
<button class="btn btn-link p-0 dropdown-toggle dropdown-caret-none transition-none d-flex" type="button" id="support-chat-dropdown" data-bs-toggle="dropdown" data-boundary="window" aria-haspopup="true" aria-expanded="false" data-bs-reference="parent"><span class="fas fa-ellipsis-h text-body"></span></button>
|
||||||
|
<div class="dropdown-menu dropdown-menu-end py-2" aria-labelledby="support-chat-dropdown"><a class="dropdown-item" href="#!">Request a callback</a><a class="dropdown-item" href="#!">Search in chat</a><a class="dropdown-item" href="#!">Show history</a><a class="dropdown-item" href="#!">Report to Admin</a><a class="dropdown-item btn-support-chat" href="#!">Close Support</a></div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="card-body chat p-0">
|
||||||
|
<div class="d-flex flex-column-reverse scrollbar h-100 p-3">
|
||||||
|
<div class="text-end mt-6"><a class="mb-2 d-inline-flex align-items-center text-decoration-none text-body-emphasis bg-body-hover rounded-pill border border-primary py-2 ps-4 pe-3" href="#!">
|
||||||
|
<p class="mb-0 fw-semibold fs-9">I need help with something</p><span class="fa-solid fa-paper-plane text-primary fs-9 ms-3"></span>
|
||||||
|
</a><a class="mb-2 d-inline-flex align-items-center text-decoration-none text-body-emphasis bg-body-hover rounded-pill border border-primary py-2 ps-4 pe-3" href="#!">
|
||||||
|
<p class="mb-0 fw-semibold fs-9">I can’t reorder a product I previously ordered</p><span class="fa-solid fa-paper-plane text-primary fs-9 ms-3"></span>
|
||||||
|
</a><a class="mb-2 d-inline-flex align-items-center text-decoration-none text-body-emphasis bg-body-hover rounded-pill border border-primary py-2 ps-4 pe-3" href="#!">
|
||||||
|
<p class="mb-0 fw-semibold fs-9">How do I place an order?</p><span class="fa-solid fa-paper-plane text-primary fs-9 ms-3"></span>
|
||||||
|
</a><a class="false d-inline-flex align-items-center text-decoration-none text-body-emphasis bg-body-hover rounded-pill border border-primary py-2 ps-4 pe-3" href="#!">
|
||||||
|
<p class="mb-0 fw-semibold fs-9">My payment method not working</p><span class="fa-solid fa-paper-plane text-primary fs-9 ms-3"></span>
|
||||||
|
</a>
|
||||||
|
</div>
|
||||||
|
<div class="text-center mt-auto">
|
||||||
|
<div class="avatar avatar-3xl status-online"><img class="rounded-circle border border-3 border-light-subtle" src="../../assets/img/team/30.webp" alt="" /></div>
|
||||||
|
<h5 class="mt-2 mb-3">Eric</h5>
|
||||||
|
<p class="text-center text-body-emphasis mb-0">Ask us anything – we’ll get back to you here or by email within 24 hours.</p>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="card-footer d-flex align-items-center gap-2 border-top border-translucent ps-3 pe-4 py-3">
|
||||||
|
<div class="d-flex align-items-center flex-1 gap-3 border border-translucent rounded-pill px-4">
|
||||||
|
<input class="form-control outline-none border-0 flex-1 fs-9 px-0" type="text" placeholder="Write message" />
|
||||||
|
<label class="btn btn-link d-flex p-0 text-body-quaternary fs-9 border-0" for="supportChatPhotos"><span class="fa-solid fa-image"></span></label>
|
||||||
|
<input class="d-none" type="file" accept="image/*" id="supportChatPhotos" />
|
||||||
|
<label class="btn btn-link d-flex p-0 text-body-quaternary fs-9 border-0" for="supportChatAttachment"> <span class="fa-solid fa-paperclip"></span></label>
|
||||||
|
<input class="d-none" type="file" id="supportChatAttachment" />
|
||||||
|
</div>
|
||||||
|
<button class="btn p-0 border-0 send-btn"><span class="fa-solid fa-paper-plane fs-9"></span></button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<button class="btn btn-support-chat p-0 border border-translucent"><span class="fs-8 btn-text text-primary text-nowrap">Chat demo</span><span class="ping-icon-wrapper mt-n4 ms-n6 mt-sm-0 ms-sm-2 position-absolute position-sm-relative"><span class="ping-icon-bg"></span><span class="fa-solid fa-circle ping-icon"></span></span><span class="fa-solid fa-headset text-primary fs-8 d-sm-none"></span><span class="fa-solid fa-chevron-down text-primary fs-7"></span></button>
|
||||||
|
</div>
|
||||||
|
</main>
|
||||||
|
{% else%}
|
||||||
|
<div class="button-row">
|
||||||
|
<button id="download-pdf" class="btn btn-primary">
|
||||||
|
<i class="fas fa-download"></i> {% trans 'Download transfer' %}
|
||||||
|
</button>
|
||||||
|
<button id="accept" class="btn btn-success" data-bs-toggle="modal" data-bs-target="#acceptModal">
|
||||||
|
<i class="fas fa-check-circle"></i> {% trans 'Accept transfer' %}
|
||||||
|
</button>
|
||||||
|
<button id="reject" class="btn btn-danger" data-bs-toggle="modal" data-bs-target="#rejectModal">
|
||||||
|
<i class="fas fa-times-circle"></i> {% trans 'Reject transfer' %}
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- Accept Modal -->
|
||||||
|
<div class="modal fade" id="acceptModal" tabindex="-1" aria-labelledby="acceptModalLabel" aria-hidden="true">
|
||||||
|
<div class="modal-dialog">
|
||||||
|
<div class="modal-content">
|
||||||
|
<div class="modal-header">
|
||||||
|
<h5 class="modal-title" id="acceptModalLabel">{% trans 'Accept transfer' %}</h5>
|
||||||
|
<button type="button" class="btn-close" data-bs-dismiss="modal" aria-label="Close"></button>
|
||||||
|
</div>
|
||||||
|
<div class="modal-body">
|
||||||
|
{% trans 'Are you sure you want to accept this transfer?' %}
|
||||||
|
</div>
|
||||||
|
<div class="modal-footer">
|
||||||
|
<button type="button" class="btn btn-secondary" data-bs-dismiss="modal">{% trans 'Cancel' %}</button>
|
||||||
|
<a class="btn btn-success" href="{% url 'transfer_accept_reject' transfer.car.pk transfer.pk %}?status=accepted">Confirm</a>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- Reject Modal -->
|
||||||
|
<div class="modal fade" id="rejectModal" tabindex="-1" aria-labelledby="rejectModalLabel" aria-hidden="true">
|
||||||
|
<div class="modal-dialog">
|
||||||
|
<div class="modal-content">
|
||||||
|
<div class="modal-header">
|
||||||
|
<h5 class="modal-title" id="rejectModalLabel">{% trans 'Reject transfer' %}</h5>
|
||||||
|
<button type="button" class="btn-close" data-bs-dismiss="modal" aria-label="Close"></button>
|
||||||
|
</div>
|
||||||
|
<div class="modal-body">
|
||||||
|
{% trans 'Are you sure you want to reject this transfer?' %}
|
||||||
|
</div>
|
||||||
|
<div class="modal-footer">
|
||||||
|
<button type="button" class="btn btn-secondary" data-bs-dismiss="modal">{% trans 'Cancel' %}</button>
|
||||||
|
<a class="btn btn-success" href="{% url 'transfer_accept_reject' transfer.car.pk transfer.pk %}?status=rejected">Confirm</a>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="transfer-row" id="transfer-content">
|
||||||
|
<!-- Header -->
|
||||||
|
<div class="transfer-header">
|
||||||
|
<svg width="101" height="24" viewBox="0 0 101 24" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||||
|
<!-- SVG Paths -->
|
||||||
|
</svg>
|
||||||
|
<h1 style="margin-top: 10px;"><b>{% trans "Transfer" %}</b></h1>
|
||||||
|
<p>{% trans "Thank you for choosing us. We appreciate your business" %}</p>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- Details -->
|
||||||
|
<div class="transfer-details">
|
||||||
|
<p><strong>{% trans "Date" %} :</strong> {{transfer.created_at}}</p>
|
||||||
|
<p><strong>{% trans "From" %} :</strong> {{transfer.from_dealer}}</p>
|
||||||
|
<p><strong>{% trans "To" %} :</strong> {{transfer.to_dealer}}</p>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- Items Table -->
|
||||||
|
<div class="transfer-table">
|
||||||
|
<table class="table table-bordered">
|
||||||
|
<thead>
|
||||||
|
<tr>
|
||||||
|
<th>{% trans "Item" %}</th>
|
||||||
|
<th class="text-center">{% trans "Quantity" %}</th>
|
||||||
|
<th class="text-center">{% trans "Unit Price" %}</th>
|
||||||
|
<th class="text-center">{% trans "Total" %}</th>
|
||||||
|
</tr>
|
||||||
|
</thead>
|
||||||
|
<tbody>
|
||||||
|
<tr>
|
||||||
|
<td>{{ transfer.car }}</td>
|
||||||
|
<td class="text-center">{{ transfer.quantity }}</td>
|
||||||
|
<td class="text-center">{{ transfer.car.finances.cost_price }}</td>
|
||||||
|
<td class="text-center">{{ transfer.total_price }}</td>
|
||||||
|
</tr>
|
||||||
|
</tbody>
|
||||||
|
</table>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
|
||||||
|
<!-- Total -->
|
||||||
|
<div class="transfer-total">
|
||||||
|
<p><strong>{%trans "Total Amount" %}:</strong> <span class="highlight">${{transfer.total_price}}</span></p>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- Footer Note -->
|
||||||
|
<div class="footer-note">
|
||||||
|
<p>{% trans "If you have any questions, feel free to contact us at" %} <a href="mailto:support@example.com">support@tenhal.com</a>.</p>
|
||||||
|
<p>{% trans "Thank you for your business" %}</p>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
{% endif %}
|
||||||
|
<!-- Bootstrap JS (Optional) -->
|
||||||
|
<script src="https://cdn.jsdelivr.net/npm/bootstrap@5.3.0/dist/js/bootstrap.bundle.min.js"></script>
|
||||||
|
<!-- jsPDF Library -->
|
||||||
|
<script src="https://cdnjs.cloudflare.com/ajax/libs/jspdf/2.5.1/jspdf.umd.min.js"></script>
|
||||||
|
<script src="https://cdnjs.cloudflare.com/ajax/libs/html2pdf.js/0.10.1/html2pdf.bundle.min.js"></script>
|
||||||
|
<script>
|
||||||
|
document.getElementById('download-pdf').addEventListener('click', function () {
|
||||||
|
const element = document.getElementById('transfer-content');
|
||||||
|
|
||||||
|
// Options for html2pdf.js
|
||||||
|
const options = {
|
||||||
|
margin: 0, // No margin
|
||||||
|
filename: 'transfer.pdf', // Name of the downloaded file
|
||||||
|
image: { type: 'jpeg', quality: 0.98 }, // Image quality
|
||||||
|
html2canvas: {
|
||||||
|
scale: 2, // Increase scale for better quality
|
||||||
|
scrollX: 0, // Ensure no horizontal scroll offset
|
||||||
|
scrollY: 0, // Ensure no vertical scroll offset
|
||||||
|
useCORS: true, // Enable CORS for external resources
|
||||||
|
},
|
||||||
|
jsPDF: { unit: 'mm', format: 'a4', orientation: 'portrait' } // PDF settings
|
||||||
|
};
|
||||||
|
|
||||||
|
// Generate and download the PDF
|
||||||
|
html2pdf().from(element).set(options).save();
|
||||||
|
});
|
||||||
|
|
||||||
|
document.getElementById('confirmAccept').addEventListener('click', function () {
|
||||||
|
// Handle the accept action here
|
||||||
|
$('#acceptModal').modal('hide');
|
||||||
|
});
|
||||||
|
|
||||||
|
document.getElementById('confirmReject').addEventListener('click', function () {
|
||||||
|
// Handle the reject action here
|
||||||
|
$('#rejectModal').modal('hide');
|
||||||
|
});
|
||||||
|
</script>
|
||||||
|
</body>
|
||||||
|
</html>
|
||||||
Loading…
x
Reference in New Issue
Block a user