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 ) 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): dealer = "" if hasattr(request.user, "dealer"): dealer = request.user.dealer elif hasattr(request.user, "staffmember"): dealer = request.user.staffmember.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.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: print(i) 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", ) 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)