963 lines
33 KiB
Python
963 lines
33 KiB
Python
from django_ledger.io import roles
|
|
from django.core.exceptions import ObjectDoesNotExist
|
|
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
|
|
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,
|
|
BillModel,
|
|
VendorModel,
|
|
CustomerModel,
|
|
ItemTransactionModel,
|
|
AccountModel
|
|
)
|
|
from decimal import Decimal
|
|
from django.utils.translation import get_language
|
|
|
|
|
|
|
|
def get_jwt_token():
|
|
url = "https://carapi.app/api/auth/login"
|
|
headers = {
|
|
"accept": "text/plain",
|
|
"Content-Type": "application/json",
|
|
}
|
|
data = {
|
|
"api_token": settings.CAR_API_TOKEN,
|
|
"api_secret": settings.CAR_API_SECRET,
|
|
}
|
|
try:
|
|
response = requests.post(url, headers=headers, json=data)
|
|
response.raise_for_status()
|
|
return response.text
|
|
except requests.exceptions.RequestException as e:
|
|
print(f"Error obtaining JWT token: {e}")
|
|
return None
|
|
|
|
|
|
def localize_some_words():
|
|
success = _("success")
|
|
error = _("error")
|
|
forget = _("Forgot Password?")
|
|
|
|
return None
|
|
|
|
|
|
def get_calculations(quotation):
|
|
context = {}
|
|
qc_len = quotation.quotation_cars.count()
|
|
cars = [x.car for x in quotation.quotation_cars.all()]
|
|
finances = models.CarFinance.objects.filter(car__in=cars)
|
|
|
|
services = ItemModel.objects.filter(additional_finances__in=finances).all()
|
|
data = [
|
|
{
|
|
"name": x.name,
|
|
"price": x.default_amount,
|
|
"total_price": x.default_amount * qc_len,
|
|
"vated": float(x.default_amount) * 0.15 * float(qc_len),
|
|
"total_price_vat": float(x.default_amount)
|
|
+ (float(x.default_amount) * 0.15 * float(qc_len)),
|
|
}
|
|
for x in services
|
|
]
|
|
context["services"] = data
|
|
context["total_cost"] = 0
|
|
context["total_vat"] = 0
|
|
context["total_cost_vat"] = 0
|
|
for k in context["services"]:
|
|
context["total_cost"] += k["total_price"]
|
|
context["total_vat"] += k["vated"]
|
|
context["total_cost_vat"] = float(context["total_cost"]) + float(
|
|
context["total_vat"]
|
|
)
|
|
return context
|
|
|
|
|
|
def send_email(from_, to_, subject, message):
|
|
subject = subject
|
|
message = message
|
|
from_email = from_
|
|
recipient_list = [to_]
|
|
send_mail(subject, message, from_email, recipient_list)
|
|
|
|
|
|
def get_user_type(request):
|
|
if request.is_dealer:
|
|
return request.user.dealer
|
|
elif request.is_staff:
|
|
return request.user.staffmember.staff.dealer
|
|
return None
|
|
|
|
|
|
def get_dealer_from_instance(instance):
|
|
if instance.dealer.staff:
|
|
return instance.dealer
|
|
else:
|
|
return instance.dealer
|
|
|
|
|
|
def reserve_car(car, request):
|
|
try:
|
|
reserved_until = timezone.now() + timezone.timedelta(hours=24)
|
|
models.CarReservation.objects.create(
|
|
car=car, reserved_by=request.user, reserved_until=reserved_until
|
|
)
|
|
car.status = models.CarStatusChoices.RESERVED
|
|
car.save()
|
|
messages.success(request, _("Car reserved successfully."))
|
|
except Exception as e:
|
|
messages.error(request, f"Error reserving car: {e}")
|
|
|
|
return redirect("car_detail", pk=car.pk)
|
|
|
|
|
|
def calculate_vat_amount(amount):
|
|
vat = models.VatRate.objects.filter(is_active=True).first()
|
|
if vat:
|
|
return ((amount * Decimal(vat.rate)).quantize(Decimal("0.01")), vat.rate)
|
|
return amount
|
|
|
|
|
|
def get_car_finance_data(model):
|
|
vat = models.VatRate.objects.filter(is_active=True).first()
|
|
data = model.get_itemtxs_data()[0].all()
|
|
total = sum(
|
|
[
|
|
Decimal(item.item_model.additional_info["car_finance"]["selling_price"])
|
|
* Decimal(item.ce_quantity or item.quantity)
|
|
for item in data
|
|
]
|
|
)
|
|
|
|
additional_services = []
|
|
|
|
for i in data:
|
|
if i.item_model.additional_info["additional_services"]:
|
|
additional_services.extend(
|
|
[
|
|
{"name": x.get("name"), "price": x.get("price")}
|
|
for x in i.item_model.additional_info["additional_services"]
|
|
]
|
|
)
|
|
|
|
return {
|
|
"cars": [
|
|
{
|
|
"vin": x.item_model.additional_info["car_info"]["vin"],
|
|
"make": x.item_model.additional_info["car_info"]["make"],
|
|
"model": x.item_model.additional_info["car_info"]["model"],
|
|
"year": x.item_model.additional_info["car_info"]["year"],
|
|
"trim": x.item_model.additional_info["car_info"]["mileage"],
|
|
"cost_price": x.item_model.additional_info["car_finance"]["cost_price"],
|
|
"selling_price": x.item_model.additional_info["car_finance"][
|
|
"selling_price"
|
|
],
|
|
"discount": x.item_model.additional_info["car_finance"][
|
|
"discount_amount"
|
|
],
|
|
"quantity": x.ce_quantity or x.quantity,
|
|
"unit_price": Decimal(x.item_model.additional_info["car_finance"]["total"]),
|
|
"total": Decimal(x.item_model.additional_info["car_finance"]["total"]) * Decimal(x.quantity or x.ce_quantity),
|
|
"total_vat": x.item_model.additional_info["car_finance"]["total_vat"],
|
|
"additional_services": x.item_model.additional_info[
|
|
"additional_services"
|
|
],
|
|
}
|
|
for x in data
|
|
],
|
|
"quantity": sum((x.quantity or x.ce_quantity) for x in data),
|
|
"total_price": total,
|
|
"total_vat": (total * vat.rate) + total,
|
|
"total_discount": sum(
|
|
Decimal(x.item_model.additional_info["car_finance"]["discount_amount"])
|
|
for x in data
|
|
),
|
|
"grand_total": Decimal(total * vat.rate)
|
|
+ total
|
|
- Decimal(
|
|
sum(
|
|
Decimal(x.item_model.additional_info["car_finance"]["discount_amount"])
|
|
for x in data
|
|
)
|
|
),
|
|
"additionals": additional_services,
|
|
"vat": vat.rate,
|
|
}
|
|
|
|
|
|
def get_financial_values(model):
|
|
vat = models.VatRate.objects.filter(is_active=True).first()
|
|
|
|
if not model.get_itemtxs_data()[0].exists():
|
|
return {
|
|
"vat_amount": 0,
|
|
"total": 0,
|
|
"grand_total": 0,
|
|
"discount_amount": 0,
|
|
"vat": 0,
|
|
"car_and_item_info": [],
|
|
"additional_services": [],
|
|
}
|
|
data = model.get_itemtxs_data()[0].all()
|
|
|
|
for item in data:
|
|
if not item.item_model.additional_info.get("car_finance"):
|
|
return {
|
|
"vat_amount": 0,
|
|
"total": 0,
|
|
"grand_total": 0,
|
|
"discount_amount": 0,
|
|
"vat": 0,
|
|
"car_and_item_info": [],
|
|
"additional_services": [],
|
|
}
|
|
|
|
if isinstance(model, InvoiceModel):
|
|
if model.ce_model:
|
|
data = model.ce_model.get_itemtxs_data()[0].all()
|
|
else:
|
|
data = model.get_itemtxs_data()[0].all()
|
|
total = sum(
|
|
[
|
|
Decimal(item.item_model.additional_info["car_finance"]["selling_price"])
|
|
* Decimal(item.ce_quantity or item.quantity)
|
|
for item in data
|
|
]
|
|
)
|
|
|
|
discount_amount = sum(
|
|
Decimal(i.item_model.additional_info["car_finance"]["discount_amount"])
|
|
for i in data
|
|
)
|
|
|
|
additional_services = []
|
|
|
|
for i in data:
|
|
if i.item_model.additional_info["additional_services"]:
|
|
additional_services.extend(
|
|
[
|
|
{"name": x.name, "price": x.price}
|
|
for x in i.item_model.additional_info["additional_services"]
|
|
]
|
|
)
|
|
|
|
grand_total = Decimal(total) - Decimal(discount_amount)
|
|
vat_amount = round(Decimal(grand_total) * Decimal(vat.rate), 2)
|
|
|
|
car_and_item_info = [
|
|
{
|
|
"info": x.item_model.additional_info["car_info"],
|
|
"finances": x.item_model.additional_info["car_finance"],
|
|
"quantity": x.ce_quantity or x.quantity,
|
|
"total": Decimal(
|
|
x.item_model.additional_info["car_finance"]["selling_price"]
|
|
)
|
|
* Decimal(x.ce_quantity or x.quantity),
|
|
}
|
|
for x in data
|
|
]
|
|
|
|
return {
|
|
"total": total,
|
|
"discount_amount": discount_amount,
|
|
"car_and_item_info": car_and_item_info,
|
|
"additional_services": additional_services,
|
|
"grand_total": grand_total + vat_amount,
|
|
"vat_amount": vat_amount,
|
|
"vat": vat.rate,
|
|
}
|
|
|
|
|
|
def set_invoice_payment(dealer, entity, invoice, amount, payment_method):
|
|
calculator = CarFinanceCalculator(invoice)
|
|
finance_data = calculator.get_finance_data()
|
|
|
|
# journal = JournalEntryModel.objects.create(
|
|
# posted=False,
|
|
# description=f"Payment for Invoice {invoice.invoice_number}",
|
|
# ledger=invoice.ledger,
|
|
# locked=False,
|
|
# origin="Payment",
|
|
# )
|
|
|
|
# credit_account = entity.get_default_coa_accounts().get(name="Sales Revenue")
|
|
# debit_account = entity.get_default_coa_accounts().get(name="Cash", 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 Account
|
|
# amount=Decimal(finance_data["grand_total"]),
|
|
# tx_type="debit",
|
|
# description="Payment Received",
|
|
# )
|
|
|
|
# TransactionModel.objects.create(
|
|
# journal_entry=journal,
|
|
# account=credit_account, # Credit Accounts Receivable
|
|
# amount=Decimal(finance_data["total_price"] + finance_data["total_additionals"]),
|
|
# tx_type="credit",
|
|
# description="Payment Received",
|
|
# )
|
|
|
|
|
|
# TransactionModel.objects.create(
|
|
# journal_entry=journal,
|
|
# account=vat_payable_account, # Credit VAT Payable
|
|
# amount=finance_data.get("total_vat_amount"),
|
|
# tx_type="credit",
|
|
# description="VAT Payable on Invoice",
|
|
# )
|
|
handle_account_process(invoice,amount,finance_data)
|
|
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="Bill Payment Received",
|
|
)
|
|
|
|
TransactionModel.objects.create(
|
|
journal_entry=journal,
|
|
account=account_payable, # Credit Accounts Receivable
|
|
amount=amount, # Payment amount
|
|
tx_type="credit",
|
|
description="Bill 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()
|
|
|
|
class CarTransfer:
|
|
def __init__(self, car, transfer):
|
|
self.car = car
|
|
self.transfer = transfer
|
|
self.from_dealer = transfer.from_dealer
|
|
self.to_dealer = transfer.to_dealer
|
|
self.customer = None
|
|
self.invoice = None
|
|
self.ledger = None
|
|
self.item = None
|
|
self.product = None
|
|
self.vendor = None
|
|
self.bill = None
|
|
|
|
def transfer_car(self):
|
|
self._create_customer()
|
|
self._create_invoice()
|
|
self._create_product_in_receiver_ledger()
|
|
self._create_vendor_and_bill()
|
|
self._finalize_car_transfer()
|
|
return True
|
|
|
|
def _create_customer(self):
|
|
self.customer = self._find_or_create_customer()
|
|
if self.customer is None:
|
|
self.customer = self._create_new_customer()
|
|
|
|
def _find_or_create_customer(self):
|
|
return self.from_dealer.entity.get_customers().filter(email=self.to_dealer.user.email).first()
|
|
|
|
def _create_new_customer(self):
|
|
customer = self.from_dealer.entity.create_customer(
|
|
customer_model_kwargs={
|
|
"customer_name": self.to_dealer.name,
|
|
"email": self.to_dealer.user.email,
|
|
"address_1": self.to_dealer.address,
|
|
}
|
|
)
|
|
customer.additional_info = {}
|
|
customer.additional_info.update({"type": "organization"})
|
|
customer.save()
|
|
return customer
|
|
|
|
def _create_invoice(self):
|
|
self.invoice = self.from_dealer.entity.create_invoice(
|
|
customer_model=self.customer,
|
|
terms=InvoiceModel.TERMS_NET_30,
|
|
cash_account=self.from_dealer.entity.get_default_coa_accounts().get(name="Cash", active=True),
|
|
prepaid_account=self.from_dealer.entity.get_default_coa_accounts().get(name="Accounts Receivable", active=True),
|
|
coa_model=self.from_dealer.entity.get_default_coa(),
|
|
)
|
|
|
|
self.ledger = self.from_dealer.entity.create_ledger(name=str(self.invoice.pk))
|
|
self.invoice.ledgar = self.ledger
|
|
self.ledger.invoicemodel = self.invoice
|
|
self.ledger.save()
|
|
self.invoice.save()
|
|
|
|
self._add_car_item_to_invoice()
|
|
|
|
def _add_car_item_to_invoice(self):
|
|
self.item = self.from_dealer.entity.get_items_products().filter(name=self.car.vin).first()
|
|
if not self.item:
|
|
return
|
|
|
|
invoice_itemtxs = {
|
|
self.item.item_number: {
|
|
"unit_cost": self.transfer.total_price,
|
|
"quantity": self.transfer.quantity,
|
|
"total_amount": self.transfer.total_price,
|
|
}
|
|
}
|
|
|
|
invoice_itemtxs = self.invoice.migrate_itemtxs(
|
|
itemtxs=invoice_itemtxs,
|
|
commit=True,
|
|
operation=InvoiceModel.ITEMIZE_APPEND,
|
|
)
|
|
|
|
if self.invoice.can_review():
|
|
self.invoice.mark_as_review()
|
|
self.invoice.mark_as_approved(self.from_dealer.entity.slug, self.from_dealer.entity.admin)
|
|
self.invoice.save()
|
|
|
|
def _create_product_in_receiver_ledger(self):
|
|
uom = self.to_dealer.entity.get_uom_all().filter(name=self.item.uom.name).first()
|
|
self.product = self.to_dealer.entity.create_item_product(
|
|
name=self.item.name,
|
|
uom_model=uom,
|
|
item_type=self.item.item_type,
|
|
coa_model=self.to_dealer.entity.get_default_coa(),
|
|
)
|
|
|
|
self.product.additional_info = {}
|
|
self.product.additional_info.update({"car_info": self.car.to_dict()})
|
|
self.product.save()
|
|
|
|
def _create_vendor_and_bill(self):
|
|
self.vendor = self._find_or_create_vendor()
|
|
self.bill = self.to_dealer.entity.create_bill(
|
|
vendor_model=self.vendor,
|
|
terms=BillModel.TERMS_NET_30,
|
|
cash_account=self.to_dealer.entity.get_default_coa_accounts().get(name="Cash", active=True),
|
|
prepaid_account=self.to_dealer.entity.get_default_coa_accounts().get(name="Prepaid Expenses", active=True),
|
|
coa_model=self.to_dealer.entity.get_default_coa(),
|
|
)
|
|
|
|
self._add_car_item_to_bill()
|
|
|
|
def _find_or_create_vendor(self):
|
|
vendor = self.to_dealer.entity.get_vendors().filter(vendor_name=self.from_dealer.name).first()
|
|
if not vendor:
|
|
vendor = VendorModel.objects.create(
|
|
entity_model=self.to_dealer.entity,
|
|
vendor_name=self.from_dealer.name,
|
|
additional_info={"info": to_dict(self.from_dealer)},
|
|
)
|
|
return vendor
|
|
|
|
def _add_car_item_to_bill(self):
|
|
bill_itemtxs = {
|
|
self.product.item_number: {
|
|
"unit_cost": self.transfer.total_price,
|
|
"quantity": self.transfer.quantity,
|
|
"total_amount": self.transfer.total_price,
|
|
}
|
|
}
|
|
|
|
bill_itemtxs = self.bill.migrate_itemtxs(
|
|
itemtxs=bill_itemtxs, commit=True, operation=BillModel.ITEMIZE_APPEND
|
|
)
|
|
self.bill.additional_info = {}
|
|
self.bill.additional_info.update({"car_info": self.car.to_dict()})
|
|
self.bill.additional_info.update({"car_finance": self.car.finances.to_dict()})
|
|
|
|
self.bill.mark_as_review()
|
|
self.bill.mark_as_approved(self.to_dealer.entity.slug, self.to_dealer.entity.admin)
|
|
self.bill.save()
|
|
|
|
def _finalize_car_transfer(self):
|
|
self.car.dealer = self.to_dealer
|
|
self.car.vendor = self.vendor
|
|
self.car.receiving_date = datetime.datetime.now()
|
|
self.car.finances.additional_services.clear()
|
|
if hasattr(self.car, "custom_cards"):
|
|
self.car.custom_cards.delete()
|
|
|
|
self.car.finances.cost_price = self.transfer.total_price
|
|
self.car.finances.selling_price = 0
|
|
self.car.finances.discount_amount = 0
|
|
self.car.finances.save()
|
|
self.car.location.owner = self.to_dealer
|
|
self.car.location.showroom = self.to_dealer
|
|
self.car.location.description = ""
|
|
self.car.location.save()
|
|
self.car.status = models.CarStatusChoices.AVAILABLE
|
|
self.transfer.status = models.CarTransferStatusChoices.success
|
|
self.transfer.active = False
|
|
self.transfer.save()
|
|
self.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
|
|
|
|
# customer = (
|
|
# from_dealer.entity.get_customers().filter(email=to_dealer.user.email).first()
|
|
# )
|
|
# if not customer:
|
|
# customer = from_dealer.entity.create_customer(
|
|
# customer_model_kwargs={
|
|
# "customer_name": to_dealer.name,
|
|
# "email": to_dealer.user.email,
|
|
# "address_1": to_dealer.address,
|
|
# }
|
|
# )
|
|
# customer.additional_info.update({"type": "organization"})
|
|
# customer.save()
|
|
|
|
# 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": transfer.total_price,
|
|
# "quantity": transfer.quantity,
|
|
# "total_amount": transfer.total_price,
|
|
# }
|
|
# }
|
|
|
|
# invoice_itemtxs = invoice.migrate_itemtxs(
|
|
# itemtxs=invoice_itemtxs,
|
|
# commit=True,
|
|
# operation=InvoiceModel.ITEMIZE_APPEND,
|
|
# )
|
|
|
|
# invoice.save()
|
|
# 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()
|
|
|
|
# # create item product in the reciever ledger
|
|
# 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(),
|
|
# )
|
|
|
|
# product.additional_info.update({"car_info": car.to_dict()})
|
|
# product.save()
|
|
|
|
# # add the sender as vendor and create a bill for it
|
|
# vendor = None
|
|
# vendor = to_dealer.entity.get_vendors().filter(vendor_name=from_dealer.name).first()
|
|
# if not vendor:
|
|
# vendor = VendorModel.objects.create(
|
|
# entity_model=to_dealer.entity,
|
|
# vendor_name=from_dealer.name,
|
|
# additional_info={"info": to_dict(from_dealer)},
|
|
# )
|
|
|
|
# # transfer the car to to_dealer and create items record
|
|
|
|
# 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.additional_info = {}
|
|
# bill_itemtxs = {
|
|
# product.item_number: {
|
|
# "unit_cost": transfer.total_price,
|
|
# "quantity": transfer.quantity,
|
|
# "total_amount": transfer.total_price,
|
|
# }
|
|
# }
|
|
|
|
# bill_itemtxs = bill.migrate_itemtxs(
|
|
# itemtxs=bill_itemtxs, commit=True, operation=BillModel.ITEMIZE_APPEND
|
|
# )
|
|
|
|
# bill.additional_info.update({"car_info": car.to_dict()})
|
|
# bill.additional_info.update({"car_finance": car.finances.to_dict()})
|
|
|
|
# bill.mark_as_review()
|
|
# bill.mark_as_approved(to_dealer.entity.slug, to_dealer.entity.admin)
|
|
# bill.save()
|
|
|
|
# car.dealer = to_dealer
|
|
# car.vendor = vendor
|
|
# car.receiving_date = datetime.datetime.now()
|
|
# car.finances.additional_services.clear()
|
|
# if hasattr(car, "custom_cards"):
|
|
# car.custom_cards.delete()
|
|
|
|
# car.finances.cost_price = transfer.total_price
|
|
# 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.status = models.CarStatusChoices.AVAILABLE
|
|
# transfer.status = models.CarTransferStatusChoices.success
|
|
# transfer.active = False
|
|
# transfer.save()
|
|
# car.save()
|
|
|
|
# return True
|
|
|
|
|
|
def to_dict(obj):
|
|
obj_dict = vars(obj).copy()
|
|
if "_state" in vars(obj):
|
|
del obj_dict["_state"]
|
|
|
|
for key, value in obj_dict.items():
|
|
if isinstance(value, datetime.datetime):
|
|
obj_dict[key] = value.strftime("%Y-%m-%d %H:%M:%S")
|
|
elif hasattr(value, "pk") or hasattr(value, "id"):
|
|
try:
|
|
obj_dict[key] = value.name
|
|
except AttributeError:
|
|
obj_dict[key] = str(value)
|
|
else:
|
|
obj_dict[key] = str(value)
|
|
return obj_dict
|
|
|
|
class CarFinanceCalculator:
|
|
VAT_OBJ_NAME = 'vat_rate'
|
|
CAR_FINANCE_KEY = 'car_finance'
|
|
CAR_INFO_KEY = 'car_info'
|
|
ADDITIONAL_SERVICES_KEY = 'additional_services'
|
|
|
|
def __init__(self, model):
|
|
self.model = model
|
|
self.vat_rate = self._get_vat_rate()
|
|
self.item_transactions = self._get_item_transactions()
|
|
self.additional_services = self._get_additional_services()
|
|
|
|
def _get_vat_rate(self):
|
|
vat = models.VatRate.objects.filter(is_active=True).first()
|
|
if not vat:
|
|
raise ObjectDoesNotExist("No active VAT rate found")
|
|
return vat.rate
|
|
|
|
def _get_item_transactions(self):
|
|
return self.model.get_itemtxs_data()[0].all()
|
|
|
|
@staticmethod
|
|
def _get_quantity(item):
|
|
return item.ce_quantity or item.quantity
|
|
|
|
def _get_nested_value(self, item, *keys):
|
|
current = item.item_model.additional_info
|
|
for key in keys:
|
|
current = current.get(key, {})
|
|
return current
|
|
|
|
def _get_car_data(self, item):
|
|
quantity = self._get_quantity(item)
|
|
car_finance = self._get_nested_value(item, self.CAR_FINANCE_KEY)
|
|
car_info = self._get_nested_value(item, self.CAR_INFO_KEY)
|
|
unit_price = Decimal(car_finance.get('selling_price', 0))
|
|
|
|
return {
|
|
"item_number": item.item_model.item_number,
|
|
"vin": car_info.get('vin'),
|
|
"make": car_info.get('make'),
|
|
"model": car_info.get('model'),
|
|
"year": car_info.get('year'),
|
|
"trim": car_info.get('trim'),
|
|
"mileage": car_info.get('mileage'),
|
|
"cost_price": car_finance.get('cost_price'),
|
|
"selling_price": car_finance.get('selling_price'),
|
|
"discount": car_finance.get('discount_amount'),
|
|
"quantity": quantity,
|
|
"unit_price": unit_price,
|
|
"total": unit_price * Decimal(quantity),
|
|
"total_vat": car_finance.get('total_vat'),
|
|
"additional_services": self._get_nested_value(item, self.ADDITIONAL_SERVICES_KEY),
|
|
}
|
|
|
|
|
|
def _get_additional_services(self):
|
|
return [
|
|
{"name": service.get('name'), "price": service.get('price'), "taxable": service.get('taxable'),"price_": service.get('price_')}
|
|
for item in self.item_transactions
|
|
for service in self._get_nested_value(item, self.ADDITIONAL_SERVICES_KEY) or []
|
|
]
|
|
|
|
def calculate_totals(self):
|
|
total_price = sum(
|
|
Decimal(self._get_nested_value(item, self.CAR_FINANCE_KEY, 'selling_price')) *
|
|
int(self._get_quantity(item))
|
|
for item in self.item_transactions
|
|
)
|
|
total_additionals = sum(Decimal(x.get('price_')) for x in self._get_additional_services())
|
|
|
|
total_discount = sum(
|
|
Decimal(self._get_nested_value(item, self.CAR_FINANCE_KEY, 'discount_amount'))
|
|
for item in self.item_transactions
|
|
)
|
|
total_price_discounted = total_price - total_discount
|
|
total_vat_amount = total_price_discounted * self.vat_rate
|
|
|
|
return {
|
|
"total_price": round(total_price_discounted, 2), # total_price_discounted,
|
|
"total_vat_amount": round(total_vat_amount, 2), # total_vat_amount,
|
|
"total_discount": round(total_discount,2),
|
|
"total_additionals": round(total_additionals, 2), # total_additionals,
|
|
"grand_total": round(total_price_discounted + total_vat_amount + total_additionals, 2)
|
|
}
|
|
|
|
def get_finance_data(self):
|
|
totals = self.calculate_totals()
|
|
return {
|
|
"cars": [self._get_car_data(item) for item in self.item_transactions],
|
|
"quantity": sum(self._get_quantity(item) for item in self.item_transactions),
|
|
"total_price": totals['total_price'],
|
|
"total_vat": totals['total_vat_amount'] + totals['total_price'],
|
|
"total_vat_amount": totals['total_vat_amount'],
|
|
"total_discount": totals['total_discount'],
|
|
"total_additionals": totals['total_additionals'],
|
|
"grand_total": totals['grand_total'],
|
|
"additionals": self.additional_services,
|
|
"vat": self.vat_rate,
|
|
}
|
|
|
|
|
|
|
|
|
|
def get_item_transactions(txs):
|
|
transactions = []
|
|
for tx in txs:
|
|
data = {}
|
|
if tx.item_model.additional_info.get('car_info'):
|
|
data["info"] = tx.item_model.additional_info.get('car_info')
|
|
if tx.item_model.additional_info.get('car_finance'):
|
|
data["finance"] = tx.item_model.additional_info.get('car_finance')
|
|
if tx.has_estimate():
|
|
data["estimate"] = tx.ce_model
|
|
data["has_estimate"] = True
|
|
data["customer"] = tx.ce_model.customer
|
|
if tx.has_invoice():
|
|
data["invoice"] = tx.invoice_model
|
|
data["has_invoice"] = True
|
|
data["customer"] = tx.invoice_model.customer
|
|
if bool(data):
|
|
transactions.append(data)
|
|
print(data)
|
|
return transactions
|
|
|
|
|
|
def get_local_name(self):
|
|
"""
|
|
Returns the localized name based on the current language.
|
|
"""
|
|
if get_language() == 'ar':
|
|
return getattr(self, 'arabic_name', None)
|
|
return getattr(self, 'name', None)
|
|
|
|
|
|
def handle_account_process(invoice,amount,finance_data):
|
|
for i in invoice.get_itemtxs_data()[0]:
|
|
car = models.Car.objects.get(vin=invoice.get_itemtxs_data()[0].first().item_model.name)
|
|
entity = invoice.ledger.entity
|
|
coa = entity.get_default_coa()
|
|
|
|
make_account = entity.get_all_accounts().filter(name=car.id_car_make.name,role=roles.COGS).first()
|
|
if not make_account:
|
|
last_account = entity.get_all_accounts().filter(role=roles.COGS).order_by('-created').first()
|
|
if len(last_account.code) == 4:
|
|
code = f"{int(last_account.code)}{1:03d}"
|
|
elif len(last_account.code) > 4:
|
|
code = f"{int(last_account.code)+1}"
|
|
|
|
make_account = entity.create_account(
|
|
name=car.id_car_make.name,
|
|
code=code,
|
|
role=roles.COGS,
|
|
coa_model=coa,
|
|
balance_type="debit",
|
|
active=True
|
|
)
|
|
|
|
# get or create additional services account
|
|
additional_services_account = entity.get_default_coa_accounts().filter(name="Additional Services",role=roles.COGS).first()
|
|
if not additional_services_account:
|
|
last_account = entity.get_all_accounts().filter(role=roles.COGS).order_by('-created').first()
|
|
if len(last_account.code) == 4:
|
|
code = f"{int(last_account.code)}{1:03d}"
|
|
elif len(last_account.code) > 4:
|
|
code = f"{int(last_account.code)+1}"
|
|
|
|
additional_services_account = entity.create_account(
|
|
name="Additional Services",
|
|
code=code,
|
|
role=roles.COGS,
|
|
coa_model=coa,
|
|
balance_type="debit",
|
|
active=True
|
|
)
|
|
|
|
inventory_account = entity.get_default_coa_accounts().filter(role=roles.ASSET_CA_INVENTORY).first()
|
|
|
|
vat_payable_account = entity.get_default_coa_accounts().get(name="VAT Payable", active=True)
|
|
|
|
|
|
journal = JournalEntryModel.objects.create(
|
|
posted=False,
|
|
description=f"Payment for Invoice {invoice.invoice_number}",
|
|
ledger=invoice.ledger,
|
|
locked=False,
|
|
origin="Payment",
|
|
)
|
|
|
|
TransactionModel.objects.create(
|
|
journal_entry=journal,
|
|
account=make_account, # Debit car make Account
|
|
amount=Decimal(finance_data.get("grand_total")),
|
|
tx_type="debit",
|
|
description="Payment Received",
|
|
)
|
|
# TransactionModel.objects.create(
|
|
# journal_entry=journal,
|
|
# account=additional_services_account, # Debit Additional Services
|
|
# amount=Decimal(car.finances.total_additionals),
|
|
# tx_type="debit",
|
|
# description="Additional Services",
|
|
# )
|
|
|
|
TransactionModel.objects.create(
|
|
journal_entry=journal,
|
|
account=inventory_account, # Credit Inventory account
|
|
amount=Decimal(finance_data.get("grand_total")),
|
|
tx_type="credit",
|
|
description="Account Adjustment",
|
|
)
|
|
|
|
# TransactionModel.objects.create(
|
|
# journal_entry=journal,
|
|
# account=vat_payable_account, # Credit VAT Payable
|
|
# amount=finance_data.get("total_vat_amount"),
|
|
# tx_type="credit",
|
|
# description="VAT Payable on Invoice",
|
|
# )
|
|
|
|
def create_make_accounts(dealer):
|
|
entity = dealer.entity
|
|
coa = entity.get_default_coa()
|
|
|
|
# Create a unique account name for the dealer and car make combination
|
|
makes = models.DealersMake.objects.filter(dealer=dealer).all()
|
|
for make in makes:
|
|
account_name = f"{make.car_make.name} Inventory Account"
|
|
|
|
account = entity.get_all_accounts().filter(coa_model=coa,name=account_name).first()
|
|
if not account:
|
|
last_account = entity.get_all_accounts().filter(role=roles.ASSET_CA_INVENTORY).order_by('-created').first()
|
|
if len(last_account.code) == 4:
|
|
code = f"{int(last_account.code)}{1:03d}"
|
|
elif len(last_account.code) > 4:
|
|
code = f"{int(last_account.code)+1}"
|
|
|
|
account = entity.create_account(
|
|
name=account_name,
|
|
code=code,
|
|
role=roles.ASSET_CA_INVENTORY,
|
|
coa_model=coa,
|
|
balance_type="credit",
|
|
active=True
|
|
) |