1445 lines
52 KiB
Python
1445 lines
52 KiB
Python
import json
|
||
import secrets
|
||
import datetime
|
||
import requests
|
||
from decimal import Decimal
|
||
from inventory import models
|
||
from django.urls import reverse
|
||
from django.conf import settings
|
||
from django.utils import timezone
|
||
from django_ledger.io import roles
|
||
from django.contrib import messages
|
||
from django.shortcuts import redirect
|
||
from django_q.tasks import async_task
|
||
from django.core.mail import send_mail
|
||
from plans.models import AbstractOrder
|
||
from django_ledger.models import (
|
||
InvoiceModel,
|
||
BillModel,
|
||
VendorModel,
|
||
)
|
||
from django_ledger.models.items import ItemModel
|
||
from django.utils.translation import get_language
|
||
from django.core.exceptions import ObjectDoesNotExist
|
||
from django.utils.translation import gettext_lazy as _
|
||
from django_ledger.models.transactions import TransactionModel
|
||
from django_ledger.models.journal_entry import JournalEntryModel
|
||
|
||
import logging
|
||
logger=logging.getLogger(__name__)
|
||
|
||
def make_random_password(
|
||
length=10, allowed_chars="abcdefghjkmnpqrstuvwxyzABCDEFGHJKLMNPQRSTUVWXYZ23456789"
|
||
):
|
||
return "".join(secrets.choice(allowed_chars) for i in range(length))
|
||
|
||
|
||
def get_jwt_token():
|
||
"""
|
||
Fetches a JWT token from an external authentication API.
|
||
|
||
This function sends a POST request to the provided authentication API endpoint with
|
||
the required headers and payload. It retrieves a JSON Web Token (JWT) if the request
|
||
is successful or returns None in case of a failure.
|
||
|
||
:raises: This function does not propagate exceptions but catches and logs
|
||
``requests.exceptions.RequestException`` if the request fails.
|
||
|
||
:return: A JWT token as a string if the request is successful, or None if
|
||
an error occurs during the process.
|
||
:rtype: str or None
|
||
"""
|
||
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,
|
||
}
|
||
logger.debug(f"Attempting to fetch JWT token from: {url}")
|
||
try:
|
||
response = requests.post(url, headers=headers, json=data)
|
||
response.raise_for_status()
|
||
#logging for success
|
||
logger.info("Successfully fetched JWT token.")
|
||
return response.text
|
||
except requests.exceptions.RequestException as e:
|
||
#logging for error
|
||
logger.error(
|
||
f"HTTP error fetching JWT token from {url}: ",
|
||
exc_info=True
|
||
)
|
||
print(f"Error obtaining JWT token: {e}")
|
||
return None
|
||
|
||
|
||
def localize_some_words():
|
||
"""
|
||
Localize predefined words and phrases for display purposes.
|
||
|
||
This function is used to localize a set of predefined words or phrases
|
||
that may be displayed in a user interface or other contexts. These
|
||
localized words or phrases include terms like "success," "error," and
|
||
"Forgot Password?".
|
||
|
||
:return: None
|
||
"""
|
||
success = _("success")
|
||
error = _("error")
|
||
forget = _("Forgot Password?")
|
||
|
||
return None
|
||
|
||
|
||
def get_calculations(quotation):
|
||
"""
|
||
Calculates and summarizes financial services data related to the cars in a given
|
||
quotation. It aggregates costs, VAT, and total amounts based on the services
|
||
available for the cars linked to the quotation.
|
||
|
||
:param quotation: The quotation object containing the cars and their related
|
||
financial details.
|
||
:type quotation: Quotation
|
||
:return: A dictionary with computed financial details including the services
|
||
data, total service costs, total VAT computed, and the total including VAT.
|
||
:rtype: dict
|
||
"""
|
||
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):
|
||
"""
|
||
Send an email with the specified subject and message.
|
||
|
||
This function sends an email from the given sender to the specified
|
||
recipient with a subject and a message body. It utilizes the provided
|
||
parameters to construct and send the email.
|
||
|
||
:param from_: str
|
||
The sender's email address.
|
||
:param to_: str
|
||
The recipient's email address.
|
||
:param subject: str
|
||
The subject of the email.
|
||
:param message: str
|
||
The body/content of the email.
|
||
:return: None
|
||
"""
|
||
subject = subject
|
||
message = message
|
||
from_email = from_
|
||
recipient_list = [to_]
|
||
async_task(send_mail,subject, message, from_email, recipient_list)
|
||
|
||
|
||
def get_user_type(request):
|
||
"""
|
||
Determine the type of user based on the given request object.
|
||
|
||
This function identifies the type of user from the provided request. It
|
||
checks if the user is a dealer, staff member, or neither based on the
|
||
attributes of the request object, returning the appropriate associated
|
||
user type or `None` if the determination cannot be made.
|
||
|
||
:param request: The HTTP request object containing user and role
|
||
information.
|
||
:type request: Any
|
||
:return: A dealer object if the user is a dealer, a dealer object
|
||
associated with the staff member if the user is a staff member, or
|
||
None if the user type cannot be identified.
|
||
:rtype: Optional[Any]
|
||
"""
|
||
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):
|
||
"""
|
||
Retrieve the dealer object from the given instance.
|
||
|
||
This function checks whether the given instance's dealer has an attribute
|
||
`staff`. If the attribute exists, it directly returns the dealer object
|
||
associated with the instance.
|
||
|
||
:param instance: The instance from which the dealer object is retrieved.
|
||
:type instance: Any
|
||
:return: The dealer object associated with the instance.
|
||
:rtype: Any
|
||
"""
|
||
if instance.dealer.staff:
|
||
return instance.dealer
|
||
else:
|
||
return instance.dealer
|
||
|
||
|
||
def reserve_car(car, request):
|
||
"""
|
||
Reserve a car for a user for 24 hours and update its status to reserved.
|
||
|
||
The function creates a reservation record for the specified car, sets the
|
||
reservation expiration time, updates the car's status, and notifies the user
|
||
about the operation outcome through success or error messages. It then redirects
|
||
to the car detail page.
|
||
|
||
:param car: The car object to be reserved.
|
||
:param request: The HTTP request object containing the user making the reservation.
|
||
:return: Redirection to the car's detail page.
|
||
"""
|
||
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()
|
||
# --- Logging for Success ---
|
||
logger.info(
|
||
f"Car {car.id} ('{car.make} {car.model}') reserved successfully "
|
||
f"by user {request.user.id} ('{request.user.username}'). "
|
||
f"Reserved until: {reserved_until}."
|
||
)
|
||
|
||
messages.success(request, _("Car reserved successfully."))
|
||
except Exception as e:
|
||
# --- Logging for Error ---
|
||
logger.error(
|
||
f"Error reserving car {car.id} ('{car.make} {car.model}') "
|
||
f"for user {request.user.id} ('{request.user.username}'). "
|
||
f"Error: {e}",
|
||
exc_info=True
|
||
)
|
||
messages.error(request, f"Error reserving car: {e}")
|
||
|
||
return redirect("car_detail", dealer_slug=request.dealer.slug, slug=car.slug)
|
||
|
||
|
||
def calculate_vat_amount(amount):
|
||
"""
|
||
Calculates the VAT (Value Added Tax) amount for a given monetary value. The method retrieves
|
||
the first active VAT rate from the database and applies it to the provided amount. If no
|
||
active VAT rate is found, the function will return the original amount.
|
||
|
||
:param amount: The monetary value to which the VAT should be applied.
|
||
:type amount: Decimal
|
||
:return: A tuple containing the computed VAT amount and the VAT rate as a decimal, or
|
||
the original amount if no VAT rate is active.
|
||
:rtype: Tuple[Decimal, Decimal] or Decimal
|
||
"""
|
||
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):
|
||
"""
|
||
Fetches car finance data from the provided model instance and calculates
|
||
various related details including total prices, VAT, discounts, and
|
||
additional services information.
|
||
|
||
:param model: The model instance that contains car and finance-related
|
||
transaction data.
|
||
:type model: Model
|
||
:return: A dictionary containing detailed information about the car finance
|
||
transactions, including individual car details, total quantity, calculated
|
||
totals for prices, VAT, discounts, additional services, and the VAT rate.
|
||
:rtype: dict
|
||
"""
|
||
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):
|
||
"""
|
||
Calculates financial values for a given model, including VAT, discounts,
|
||
grand total, and additional services.
|
||
|
||
This function processes financial data related to items in the input model
|
||
and computes aggregated values like total amounts, VAT amounts, discounts,
|
||
and more. It also provides detailed information on cars and items as well
|
||
as any applicable additional services.
|
||
|
||
:param model: The model instance containing financial and item data.
|
||
It should have the necessary methods and attributes to fetch
|
||
transactional records and relevant details.
|
||
:type model: Any
|
||
:return: A dictionary containing calculated financial details including:
|
||
- "vat_amount": The computed VAT amount.
|
||
- "total": The total value of all items before VAT and discounts.
|
||
- "grand_total": The total after applying discounts and adding VAT.
|
||
- "discount_amount": The aggregated discount amount.
|
||
- "vat": The applicable VAT rate.
|
||
- "car_and_item_info": A list of detailed car and item information.
|
||
- "additional_services": A list of additional services and their prices.
|
||
:rtype: dict
|
||
"""
|
||
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):
|
||
"""
|
||
Processes and applies a payment for a specified invoice. This function calculates
|
||
finance details, handles associated account transactions, and updates the invoice
|
||
status accordingly.
|
||
|
||
:param dealer: Dealer object responsible for processing the payment
|
||
:type dealer: Dealer
|
||
:param entity: Entity object associated with the invoice and payment
|
||
:type entity: Entity
|
||
:param invoice: The invoice object for which the payment is being made
|
||
:type invoice: Invoice
|
||
:param amount: The amount being paid towards the invoice
|
||
:type amount: Decimal
|
||
:param payment_method: The payment method used for the transaction
|
||
:type payment_method: str
|
||
:return: None
|
||
"""
|
||
calculator = CarFinanceCalculator(invoice)
|
||
finance_data = calculator.get_finance_data()
|
||
|
||
handle_account_process(invoice, amount, finance_data)
|
||
invoice.make_payment(amount)
|
||
invoice.save()
|
||
|
||
|
||
def set_bill_payment(dealer, entity, bill, amount, payment_method):
|
||
"""
|
||
Sets the payment for a given bill by creating journal entries for the
|
||
transaction and updating the respective accounts and the bill's status.
|
||
|
||
The function handles the transaction by creating a new journal entry
|
||
linked with the specified bill, then records debit and credit
|
||
transactions using the entity’s cash and accounts payable accounts.
|
||
It finally updates the bill's payment status and persists changes.
|
||
|
||
:param dealer: The dealer making or receiving the payment.
|
||
:type dealer: Any
|
||
:param entity: The business entity involved in the payment transaction.
|
||
:type entity: Any
|
||
:param bill: The bill object representing the invoice to be paid.
|
||
:type bill: Any
|
||
:param amount: The amount to be paid for the bill.
|
||
:type amount: Decimal
|
||
:param payment_method: The method used to make the payment (e.g., cash, credit).
|
||
:type payment_method: Any
|
||
:return: None
|
||
"""
|
||
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):
|
||
"""
|
||
Transfers a list of cars from one dealer to another, ensuring that all cars
|
||
originate from the same dealer and logging the transfer.
|
||
|
||
:param request: The HTTP request object which contains information about
|
||
the current user and context.
|
||
:type request: HttpRequest
|
||
:param cars: A list of car objects to transfer.
|
||
:type cars: list[Car]
|
||
:param to_dealer: The dealer object representing the destination dealer.
|
||
:type to_dealer: Dealer
|
||
:param remarks: Optional remarks to include in the transfer log.
|
||
Defaults to None.
|
||
:type remarks: str, optional
|
||
:return: None
|
||
:rtype: NoneType
|
||
|
||
:raises ValueError: If no cars are selected for transfer.
|
||
:raises ValueError: If the cars are not all from the same dealer.
|
||
:raises ValueError: If the source and destination dealers are the same.
|
||
"""
|
||
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:
|
||
"""
|
||
Handles the process of transferring a car between dealers, automating associated tasks
|
||
such as creating customers, invoices, products, vendors, and bills, as well as reflecting
|
||
changes in relevant entities and systems.
|
||
|
||
The purpose of this class is to facilitate a smooth transfer of car ownership from one
|
||
dealer to another. It ensures that all necessary financial, logistic, and informational
|
||
processes are executed and updated within the system. The car's status and related
|
||
entities are adjusted accordingly post-transfer.
|
||
|
||
:ivar car: Car object being transferred.
|
||
:ivar transfer: Represents the transfer details, including source and destination dealers.
|
||
:ivar from_dealer: The dealer transferring the car out.
|
||
:ivar to_dealer: The dealer receiving the car.
|
||
:ivar customer: Customer entity corresponding to the receiving dealer.
|
||
:ivar invoice: Invoice entity created for the car transfer.
|
||
:ivar ledger: Ledger associated with the created invoice.
|
||
:ivar item: The inventory item corresponding to the car being transferred.
|
||
:ivar product: The product created in the receiving dealer's ledger.
|
||
:ivar vendor: Vendor entity related to the transferring dealer.
|
||
:ivar bill: Bill entity created for the receiving dealer.
|
||
"""
|
||
|
||
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):
|
||
"""
|
||
Convert a Python object's attributes and associated values to a dictionary,
|
||
with special handling for specific data types such as datetime and object references.
|
||
|
||
:param obj: The Python object to be converted into a dictionary. Object attributes
|
||
are dynamically introspected to create the dictionary. For objects containing
|
||
references to other objects, specific attributes (e.g., "pk" or "id") may be used
|
||
for conversion and representation.
|
||
:return: A dictionary containing the key-value pairs for the object's attributes.
|
||
Datetime values are formatted as strings in "YYYY-MM-DD HH:MM:SS" format. All other
|
||
attributes, including nested object references, are converted to strings.
|
||
"""
|
||
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:
|
||
"""
|
||
Class responsible for calculating car financing details.
|
||
|
||
This class provides methods and attributes required for calculating various
|
||
aspects related to car financing, such as VAT calculation, pricing, discounts,
|
||
and additional services. It processes data about cars, computes totals (e.g.,
|
||
price, VAT, discounts), and aggregates the financial data for reporting or
|
||
further processing.
|
||
|
||
:ivar model: The data model passed to the calculator for retrieving transaction data.
|
||
:type model: Any
|
||
:ivar vat_rate: The current active VAT rate retrieved from the database.
|
||
:type vat_rate: Decimal
|
||
:ivar item_transactions: A collection of item transactions retrieved from the model.
|
||
:type item_transactions: list
|
||
:ivar additional_services: A list of additional services with details (e.g., name, price, taxable status).
|
||
:type additional_services: list
|
||
"""
|
||
|
||
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))
|
||
print(item.item_model.car.finances)
|
||
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"),
|
||
"logo": getattr(item.item_model.car.id_car_make, "logo", ""),
|
||
"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_before_discount": round(
|
||
total_price, 2
|
||
), # total_price_before_discount,
|
||
"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_price_before_discount": totals["total_price_before_discount"],
|
||
"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):
|
||
"""
|
||
Extracts and compiles relevant transaction details from a list of transactions,
|
||
including information about cars, financing, estimates, and invoices. The extracted
|
||
details for each transaction are stored as dictionaries in a list.
|
||
|
||
:param txs: A list of transaction objects from which information is extracted.
|
||
:type txs: list
|
||
:return: A list of dictionaries, each containing extracted transaction details.
|
||
:rtype: list
|
||
"""
|
||
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):
|
||
"""
|
||
Retrieve the localized name of the instance based on the current language.
|
||
|
||
This method returns the name attribute of an instance based on the currently
|
||
active language. If the language is Arabic ('ar'), it attempts to return the
|
||
value of the attribute `arabic_name`. If the language is not Arabic or the
|
||
`arabic_name` attribute is not defined, it defaults to returning the value of
|
||
the `name` attribute.
|
||
|
||
:param self:
|
||
Reference to the instance of the class containing `name` and
|
||
optionally `arabic_name` attributes.
|
||
:return:
|
||
The localized name based on the current language. Returns the value of
|
||
`arabic_name` for Arabic ('ar') language, or the value of `name` for any
|
||
other language or if `arabic_name` is not defined.
|
||
"""
|
||
if get_language() == "ar":
|
||
return getattr(self, "arabic_name", None)
|
||
return getattr(self, "name", None)
|
||
|
||
|
||
def handle_account_process(invoice, amount, finance_data):
|
||
"""
|
||
Processes accounting transactions based on an invoice, financial data,
|
||
and related entity accounts configuration. This function handles the
|
||
creation of accounts if they do not already exist, and processes journal
|
||
entries and transactions.
|
||
|
||
:param invoice: The invoice object to process transactions for.
|
||
:type invoice: InvoiceModel
|
||
:param amount: Total monetary value for the transaction.
|
||
:type amount: Decimal
|
||
:param finance_data: Dictionary containing financial details such as
|
||
'grand_total', 'total_vat_amount', and other related data.
|
||
:type finance_data: dict
|
||
:return: None
|
||
"""
|
||
for i in invoice.get_itemtxs_data()[0]:
|
||
# car = models.Car.objects.get(vin=invoice.get_itemtxs_data()[0].first().item_model.name)
|
||
car = i.item_model.car
|
||
entity = invoice.ledger.entity
|
||
coa = entity.get_default_coa()
|
||
|
||
cash_account = (
|
||
entity.get_all_accounts()
|
||
.filter(role_default=True, role=roles.ASSET_CA_CASH)
|
||
.first()
|
||
)
|
||
inventory_account = car.get_inventory_account()
|
||
revenue_account = car.get_revenue_account()
|
||
cogs_account = car.get_cogs_account()
|
||
|
||
# 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=f"Sale of {car.id_car_make.name}{car.vin}: Invoice {invoice.invoice_number}",
|
||
)
|
||
|
||
TransactionModel.objects.create(
|
||
journal_entry=journal,
|
||
account=cash_account,
|
||
amount=Decimal(finance_data.get("grand_total")),
|
||
tx_type="debit",
|
||
description="",
|
||
)
|
||
|
||
TransactionModel.objects.create(
|
||
journal_entry=journal,
|
||
account=revenue_account,
|
||
amount=Decimal(finance_data.get("grand_total")),
|
||
tx_type="credit",
|
||
description="",
|
||
)
|
||
|
||
journal_cogs = JournalEntryModel.objects.create(
|
||
posted=False,
|
||
description=f"COGS of {car.id_car_make.name}{car.vin}: Invoice {invoice.invoice_number}",
|
||
ledger=invoice.ledger,
|
||
locked=False,
|
||
origin="Payment",
|
||
)
|
||
TransactionModel.objects.create(
|
||
journal_entry=journal_cogs,
|
||
account=cogs_account,
|
||
amount=Decimal(car.finances.cost_price),
|
||
tx_type="debit",
|
||
description="",
|
||
)
|
||
|
||
TransactionModel.objects.create(
|
||
journal_entry=journal_cogs,
|
||
account=inventory_account,
|
||
amount=Decimal(car.finances.cost_price),
|
||
tx_type="credit",
|
||
description="",
|
||
)
|
||
try:
|
||
car.item_model.for_inventory = False
|
||
logger.debug(f"Set item_model.for_inventory to False for car {car.vin}.")
|
||
except Exception as e:
|
||
logger.error(
|
||
f"Error updating item_model.for_inventory for car {car.vin} (Invoice {invoice.invoice_number}): {e}",
|
||
exc_info=True
|
||
)
|
||
|
||
print(e)
|
||
car.finances.is_sold = True
|
||
car.finances.save()
|
||
car.item_model.save()
|
||
|
||
# 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):
|
||
"""
|
||
Creates accounts for dealer's car makes and associates them with a default
|
||
Chart of Accounts (CoA) if they don't already exist. The function iterates
|
||
through all car makes associated with the dealer and creates unique inventory
|
||
accounts for each car make. Accounts are created with a specified naming
|
||
convention and are assigned a new account code if required.
|
||
|
||
:param dealer: The dealer for whom to create make accounts.
|
||
:type dealer: Dealer
|
||
:return: None
|
||
:rtype: None
|
||
"""
|
||
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,
|
||
)
|
||
|
||
def handle_payment(request, order):
|
||
url = "https://api.moyasar.com/v1/payments"
|
||
callback_url = request.build_absolute_uri(reverse("payment_callback", args=[request.dealer.slug]))
|
||
|
||
if request.user.is_authenticated:
|
||
# email = request.user.email
|
||
# first_name = request.user.first_name
|
||
# last_name = request.user.last_name
|
||
# phone = request.user.phone
|
||
# else:
|
||
email = request.POST["email"]
|
||
first_name = request.POST["first_name"]
|
||
last_name = request.POST["last_name"]
|
||
phone = request.POST["phone"]
|
||
|
||
card_name = request.POST["card_name"]
|
||
card_number = str(request.POST["card_number"]).replace(" ", "").strip()
|
||
month = int(request.POST["card_expiry"].split("/")[0].strip())
|
||
year = int(request.POST["card_expiry"].split("/")[1].strip())
|
||
cvv = request.POST["card_cvv"]
|
||
|
||
user_data = {
|
||
"email": email,
|
||
"first_name": first_name,
|
||
"last_name": last_name,
|
||
"phone": phone,
|
||
}
|
||
total = int(round(order.total())) * 100
|
||
payload = json.dumps(
|
||
{
|
||
"amount": total,
|
||
"currency": "SAR",
|
||
"description": f"payment issued for {'email'}",
|
||
"callback_url": callback_url,
|
||
"source": {
|
||
"type": "creditcard",
|
||
"name": card_name,
|
||
"number": card_number,
|
||
"month": month,
|
||
"year": year,
|
||
"cvc": cvv,
|
||
"statement_descriptor": "Century Store",
|
||
"3ds": True,
|
||
"manual": False,
|
||
"save_card": False,
|
||
},
|
||
"metadata": user_data,
|
||
}
|
||
)
|
||
|
||
headers = {"Content-Type": "application/json", "Accept": "application/json"}
|
||
auth = (settings.MOYASAR_SECRET_KEY, "")
|
||
response = requests.request("POST", url, auth=auth, headers=headers, data=payload)
|
||
#
|
||
order.status = AbstractOrder.STATUS.NEW
|
||
order.save()
|
||
#
|
||
data = response.json()
|
||
amount = Decimal("{0:.2f}".format(Decimal(total) / Decimal(100)))
|
||
models.PaymentHistory.objects.create(
|
||
user=request.user,
|
||
user_data=user_data,
|
||
amount=amount,
|
||
currency=data["currency"],
|
||
status=data["status"],
|
||
transaction_id=data["id"],
|
||
payment_date=data["created_at"],
|
||
gateway_response=data,
|
||
)
|
||
transaction_url = data["source"]["transaction_url"]
|
||
|
||
return transaction_url
|
||
|
||
|
||
# def get_user_quota(user):
|
||
# return user.dealer.quota
|