haikal/inventory/utils.py
Marwan Alwali c92371a9b5 update
2025-02-02 17:22:35 +03:00

677 lines
22 KiB
Python

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,
)
from decimal import Decimal
def get_jwt_token():
url = "https://carapi.app/api/auth/login"
headers = {
"accept": "text/plain",
"Content-Type": "application/json",
}
data = {
"api_token": "f5204a00-6f31-4de2-96d8-ed998e0d230c",
"api_secret": "8c11320781a5b8f4f327b6937e6f8241",
}
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):
dealer = ""
if hasattr(request.user, "dealer"):
dealer = request.user.dealer
elif hasattr(request.user, "staff"):
dealer = request.user.staff.dealer
return dealer
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.name, "price": x.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):
vat_amount = 0
total_amount = 0
calculator = CarFinanceCalculator(invoice)
finance_data = calculator.get_finance_data()
# if invoice.terms == "on_receipt":
# for x in invoice.get_itemtxs_data()[0].all():
# total_amount += Decimal(x.unit_cost) * Decimal(x.quantity)
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=finance_data["grand_total"], # Payment amount
tx_type="debit",
description="Payment Received",
)
TransactionModel.objects.create(
journal_entry=journal,
account=credit_account, # Credit Accounts Receivable
amount=finance_data["total_price"], # Payment amount
tx_type="credit",
description="Payment Received",
)
TransactionModel.objects.create(
journal_entry=journal,
account=vat_payable_account, # Credit VAT Payable
amount=finance_data["total_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
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
# pay the pill
# set_bill_payment(to_dealer,to_dealer.entity,bill,transfer.total_price,"credit")
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.name, "price": service.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')) *
Decimal(self._get_quantity(item))
for item in self.item_transactions
)
total_vat_amount = total_price * self.vat_rate
total_discount = sum(
Decimal(self._get_nested_value(item, self.CAR_FINANCE_KEY, 'discount_amount'))
for item in self.item_transactions
)
return {
"total_price": total_price,
"total_vat_amount": total_vat_amount,
"total_discount": total_discount,
"grand_total": (total_price + total_vat_amount) - total_discount ,
}
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'],
"grand_total": totals['grand_total'],
"additionals": self.additional_services,
"vat": self.vat_rate,
}