493 lines
27 KiB
Python
493 lines
27 KiB
Python
import arrow
|
|
import calendar
|
|
import time
|
|
|
|
from decimal import Decimal
|
|
from datetime import datetime, timedelta
|
|
|
|
from Schemas import BuildDecisionBookPayments, AccountRecords, ApiEnumDropdown, Build, BuildDecisionBook, AccountDelayInterest
|
|
from time import perf_counter
|
|
from sqlalchemy import select, func, distinct, cast, Date, String, literal, desc, and_, or_, case
|
|
from Controllers.Postgres.engine import get_session_factory
|
|
from interest_calculate import hesapla_gecikme_faizi
|
|
# from ServicesApi.Schemas.account.account import AccountRecords, AccountDelayInterest
|
|
# from ServicesApi.Schemas.building.decision_book import BuildDecisionBookPayments
|
|
|
|
|
|
def find_last_day_of_month(date_value):
|
|
today = date_value.date()
|
|
_, last_day = calendar.monthrange(today.year, today.month)
|
|
return datetime(today.year, today.month, last_day, 23, 59, 59)
|
|
|
|
|
|
def find_first_day_of_month(date_value):
|
|
today = date_value.date()
|
|
return datetime(today.year, today.month, 1)
|
|
|
|
|
|
def add_month_to_date(date_value):
|
|
month_to_process = date_value.month
|
|
year_to_process = date_value.year
|
|
if date_value.month == 12:
|
|
month_to_process = 1
|
|
year_to_process += 1
|
|
else:
|
|
month_to_process += 1
|
|
_, last_day = calendar.monthrange(year_to_process, month_to_process)
|
|
return datetime(year_to_process, month_to_process, 1), datetime(year_to_process, month_to_process, last_day)
|
|
|
|
|
|
class BuildDuesTypes:
|
|
|
|
def __init__(self):
|
|
self.debit: ApiEnumDropdownShallowCopy = None
|
|
self.add_debit: ApiEnumDropdownShallowCopy = None
|
|
self.renovation: ApiEnumDropdownShallowCopy = None
|
|
self.lawyer_expence: ApiEnumDropdownShallowCopy = None
|
|
self.service_fee: ApiEnumDropdownShallowCopy = None
|
|
self.information: ApiEnumDropdownShallowCopy = None
|
|
|
|
|
|
class ApiEnumDropdownShallowCopy:
|
|
|
|
id: int
|
|
uuid: str
|
|
enum_class: str
|
|
key: str
|
|
value: str
|
|
|
|
def __init__(self, id: int, uuid: str, enum_class: str, key: str, value: str):
|
|
self.id = id
|
|
self.uuid = uuid
|
|
self.enum_class = enum_class
|
|
self.key = key
|
|
self.value = value
|
|
|
|
|
|
def retrieve_remainder_balance_set_if_needed(account_record_id: int, session):
|
|
"""Update the remainder_balance of an account after spending money.
|
|
|
|
Args:
|
|
account_record: The account record to update
|
|
amount_spent: The amount spent in this transaction
|
|
session: Database session
|
|
|
|
Returns:
|
|
bool: True if all money is spent, False otherwise
|
|
"""
|
|
AccountRecords.set_session(session)
|
|
|
|
account_record_remainder_balance = session.query(func.sum(func.abs(BuildDecisionBookPayments.payment_amount))).filter(
|
|
BuildDecisionBookPayments.account_records_id == account_record_id,
|
|
BuildDecisionBookPayments.account_is_debit == False, BuildDecisionBookPayments.active == True,
|
|
).scalar()
|
|
|
|
if not account_record_remainder_balance:
|
|
account_record_remainder_balance = 0
|
|
|
|
account_record = AccountRecords.query.filter_by(id=account_record_id).first()
|
|
account_record.remainder_balance = -1 * abs(account_record_remainder_balance) if account_record_remainder_balance != 0 else 0
|
|
account_record.save()
|
|
return account_record_remainder_balance
|
|
|
|
|
|
def retrieve_current_debt_to_pay_from_database(ref_id: int, session):
|
|
debt_to_pay = session.query(func.sum(BuildDecisionBookPayments.payment_amount)).filter(BuildDecisionBookPayments.ref_id == ref_id, BuildDecisionBookPayments.active == True).scalar()
|
|
return abs(debt_to_pay)
|
|
|
|
|
|
def check_current_debt_to_pay_from_database_is_closed(ref_id: int, session):
|
|
session.commit()
|
|
BuildDecisionBookPayments.set_session(session)
|
|
payment_row_book = BuildDecisionBookPayments.query.filter(BuildDecisionBookPayments.ref_id == str(ref_id),
|
|
BuildDecisionBookPayments.account_is_debit == True, BuildDecisionBookPayments.active == True).first()
|
|
debt_paid = session.query(func.sum(func.abs(BuildDecisionBookPayments.payment_amount))).filter(
|
|
BuildDecisionBookPayments.ref_id == str(ref_id), BuildDecisionBookPayments.account_is_debit == False, BuildDecisionBookPayments.active == True
|
|
).scalar()
|
|
payment_row_book.debt_paid = abs(debt_paid) if debt_paid else 0 # Debt Reminder is how much money is needed to close record
|
|
payment_row_book.debt_to_pay = abs(payment_row_book.payment_amount) - abs(payment_row_book.debt_paid) # Debt To Pay is how much money is needed to close record
|
|
payment_row_book.is_closed = payment_row_book.debt_to_pay == 0
|
|
payment_row_book.save()
|
|
|
|
|
|
def close_payment_book(payment_row_book, account_record, value, session):
|
|
"""Create a credit entry in BuildDecisionBookPayments to close a debt.
|
|
|
|
Args:
|
|
payment_row_book: The debit entry to be paid
|
|
account_record: The account record containing the funds
|
|
value: The amount to pay
|
|
session: Database session
|
|
|
|
Returns:
|
|
The newly created payment record
|
|
"""
|
|
BuildDecisionBookPayments.set_session(session)
|
|
account_record_remainder_balance = retrieve_remainder_balance_set_if_needed(account_record_id=account_record.id, session=session)
|
|
print(f'NOT Updated remainder balance: {account_record_remainder_balance} | Account record id: {account_record.id}')
|
|
|
|
# Create a new credit entry (payment)
|
|
new_row = BuildDecisionBookPayments.create(
|
|
ref_id=str(payment_row_book.uu_id),
|
|
payment_plan_time_periods=payment_row_book.payment_plan_time_periods,
|
|
period_time=payment_row_book.period_time,
|
|
currency=payment_row_book.currency,
|
|
account_records_id=account_record.id,
|
|
account_records_uu_id=str(account_record.uu_id),
|
|
build_parts_id=payment_row_book.build_parts_id,
|
|
build_parts_uu_id=str(payment_row_book.build_parts_uu_id),
|
|
payment_amount=abs(value), # Negative for credit entries
|
|
payment_types_id=payment_row_book.payment_types_id,
|
|
payment_types_uu_id=str(payment_row_book.payment_types_uu_id),
|
|
process_date_m=payment_row_book.process_date.month,
|
|
process_date_y=payment_row_book.process_date.year,
|
|
process_date=payment_row_book.process_date,
|
|
build_decision_book_item_id=payment_row_book.build_decision_book_item_id if payment_row_book.build_decision_book_item_id else None,
|
|
build_decision_book_item_uu_id=str(payment_row_book.build_decision_book_item_uu_id) if payment_row_book.build_decision_book_item_uu_id else None,
|
|
decision_book_project_id=payment_row_book.decision_book_project_id if payment_row_book.decision_book_project_id else None,
|
|
decision_book_project_uu_id=str(payment_row_book.decision_book_project_uu_id) if payment_row_book.decision_book_project_uu_id else None,
|
|
build_decision_book_id=payment_row_book.build_decision_book_id if payment_row_book.build_decision_book_id else None,
|
|
build_decision_book_uu_id=str(payment_row_book.build_decision_book_uu_id) if payment_row_book.build_decision_book_uu_id else None,
|
|
is_confirmed=True,
|
|
active=True,
|
|
account_is_debit=False,
|
|
)
|
|
|
|
# Save the new payment record
|
|
saved_row = new_row.save()
|
|
session.commit()
|
|
session.refresh(saved_row)
|
|
account_record_remainder_balance = retrieve_remainder_balance_set_if_needed(account_record_id=account_record.id, session=session)
|
|
print(f'Updated remainder balance: {account_record_remainder_balance} | Account record id: {account_record.id}')
|
|
check_current_debt_to_pay_from_database_is_closed(ref_id=new_row.ref_id, session=session)
|
|
return account_record_remainder_balance
|
|
|
|
|
|
def get_enums_from_database():
|
|
build_dues_types = BuildDuesTypes()
|
|
with ApiEnumDropdown.new_session() as session:
|
|
|
|
ApiEnumDropdown.set_session(session)
|
|
|
|
debit_enum_shallow = ApiEnumDropdown.query.filter_by(enum_class="BuildDuesTypes", key="BDT-D").first() # Debit
|
|
add_debit_enum_shallow = ApiEnumDropdown.query.filter_by(enum_class="BuildDuesTypes", key="BDT-A").first() # Add Debit
|
|
renovation_enum_shallow = ApiEnumDropdown.query.filter_by(enum_class="BuildDuesTypes", key="BDT-R").first() # Renovation
|
|
late_payment_enum_shallow = ApiEnumDropdown.query.filter_by(enum_class="BuildDuesTypes", key="BDT-L").first() # Lawyer expence
|
|
service_fee_enum_shallow = ApiEnumDropdown.query.filter_by(enum_class="BuildDuesTypes", key="BDT-S").first() # Service fee
|
|
information_enum_shallow = ApiEnumDropdown.query.filter_by(enum_class="BuildDuesTypes", key="BDT-I").first() # Information
|
|
# close_last_period_receipt_enum_shallow = ApiEnumDropdown.query.filter_by(enum_class="BuildDuesTypes", key="BDT-CL").first() # Close Last Period Receipt
|
|
# open_new_period_receipt_enum_shallow = ApiEnumDropdown.query.filter_by(enum_class="BuildDuesTypes", key="BDT-OP").first() # Open New Period Receipt
|
|
|
|
build_dues_types.debit = ApiEnumDropdownShallowCopy(
|
|
debit_enum_shallow.id, str(debit_enum_shallow.uu_id), debit_enum_shallow.enum_class, debit_enum_shallow.key, debit_enum_shallow.value
|
|
)
|
|
build_dues_types.add_debit = ApiEnumDropdownShallowCopy(
|
|
add_debit_enum_shallow.id, str(add_debit_enum_shallow.uu_id), add_debit_enum_shallow.enum_class, add_debit_enum_shallow.key, add_debit_enum_shallow.value
|
|
)
|
|
build_dues_types.renovation = ApiEnumDropdownShallowCopy(
|
|
renovation_enum_shallow.id, str(renovation_enum_shallow.uu_id), renovation_enum_shallow.enum_class, renovation_enum_shallow.key, renovation_enum_shallow.value
|
|
)
|
|
build_dues_types.lawyer_expence = ApiEnumDropdownShallowCopy(
|
|
late_payment_enum_shallow.id, str(late_payment_enum_shallow.uu_id), late_payment_enum_shallow.enum_class, late_payment_enum_shallow.key, late_payment_enum_shallow.value
|
|
)
|
|
build_dues_types.service_fee = ApiEnumDropdownShallowCopy(
|
|
service_fee_enum_shallow.id, str(service_fee_enum_shallow.uu_id), service_fee_enum_shallow.enum_class, service_fee_enum_shallow.key, service_fee_enum_shallow.value
|
|
)
|
|
build_dues_types.information = ApiEnumDropdownShallowCopy(
|
|
information_enum_shallow.id, str(information_enum_shallow.uu_id), information_enum_shallow.enum_class, information_enum_shallow.key, information_enum_shallow.value
|
|
)
|
|
# build_dues_types.close_last_period_receipt = ApiEnumDropdownShallowCopy(
|
|
# close_last_period_receipt_enum_shallow.id, str(close_last_period_receipt_enum_shallow.uu_id), close_last_period_receipt_enum_shallow.enum_class, close_last_period_receipt_enum_shallow.key, close_last_period_receipt_enum_shallow.value
|
|
# )
|
|
# build_dues_types.open_new_period_receipt = ApiEnumDropdownShallowCopy(
|
|
# open_new_period_receipt_enum_shallow.id, str(open_new_period_receipt_enum_shallow.uu_id), open_new_period_receipt_enum_shallow.enum_class, open_new_period_receipt_enum_shallow.key, open_new_period_receipt_enum_shallow.value
|
|
# )
|
|
return [
|
|
build_dues_types.debit,
|
|
build_dues_types.lawyer_expence,
|
|
build_dues_types.add_debit,
|
|
build_dues_types.renovation,
|
|
build_dues_types.service_fee,
|
|
build_dues_types.information,
|
|
# build_dues_types.close_last_period_receipt,
|
|
# build_dues_types.open_new_period_receipt
|
|
]
|
|
|
|
|
|
def do_payments(session, build_id: int, work_date: datetime):
|
|
"""Process payments for the current month's unpaid debts.
|
|
This function retrieves account records with available funds and processes
|
|
payments for current month's unpaid debts in order of payment type priority.
|
|
"""
|
|
|
|
# Set session for all models
|
|
AccountRecords.set_session(session)
|
|
BuildDecisionBookPayments.set_session(session)
|
|
Build.set_session(session)
|
|
BuildDecisionBook.set_session(session)
|
|
|
|
today = datetime.now()
|
|
|
|
# Get payment types in priority order
|
|
payment_type_list = get_enums_from_database()
|
|
fund_finished = lambda money_spend, money_in_account: money_spend == money_in_account
|
|
payments_made, total_amount_paid, paid_count = 0, 0, 0
|
|
target_build = Build.query.filter(Build.id == build_id).first()
|
|
if not target_build:
|
|
raise ValueError(f"Build with id {build_id} not found")
|
|
|
|
decision_book = BuildDecisionBook.query.filter(
|
|
BuildDecisionBook.build_id == build_id, cast(BuildDecisionBook.expiry_starts, Date) <= work_date.date(),
|
|
cast(BuildDecisionBook.expiry_ends, Date) >= work_date.date(), BuildDecisionBook.decision_type == "RBM",
|
|
BuildDecisionBook.active == True
|
|
).first()
|
|
if not decision_book:
|
|
raise ValueError(f"Decision book not found for build with id {build_id}")
|
|
|
|
period_date_start = decision_book.expiry_starts
|
|
period_date_end = decision_book.expiry_ends
|
|
# period_id = decision_book.id
|
|
|
|
first_date_of_process_date = find_first_day_of_month(datetime(work_date.year, work_date.month, 1))
|
|
last_date_of_process_date = find_last_day_of_month(datetime(work_date.year, work_date.month, 1))
|
|
if work_date.month == today.month and work_date.year == today.year:
|
|
last_date_of_process_date = today
|
|
|
|
# Current month date filter
|
|
date_query_tuple = (
|
|
cast(BuildDecisionBookPayments.process_date, Date) >= first_date_of_process_date.date(),
|
|
cast(BuildDecisionBookPayments.process_date, Date) <= last_date_of_process_date.date()
|
|
)
|
|
date_query_account_tuple = (
|
|
cast(AccountRecords.bank_date, Date) >= first_date_of_process_date.date(),
|
|
cast(AccountRecords.bank_date, Date) <= last_date_of_process_date.date()
|
|
)
|
|
|
|
for payment_type in payment_type_list:
|
|
query_of_payments = BuildDecisionBookPayments.query.filter(*date_query_tuple)
|
|
query_of_payments = query_of_payments.filter(
|
|
BuildDecisionBookPayments.payment_types_id == payment_type.id, BuildDecisionBookPayments.account_is_debit == True,
|
|
BuildDecisionBookPayments.is_closed == False, BuildDecisionBookPayments.active == True,
|
|
).order_by(BuildDecisionBookPayments.process_date.asc(), BuildDecisionBookPayments.debt_to_pay.desc()).all()
|
|
# priority_uuids = [payment_type.uuid, *[pt.uuid for pt in payment_type_list if pt.uuid != payment_type.uuid]]
|
|
# case_order = case(*[(AccountRecords.payment_result_type_uu_id == uuid, index) for index, uuid in enumerate(priority_uuids)], else_=len(priority_uuids))
|
|
|
|
for payment_row in query_of_payments:
|
|
money_to_pay_rows = AccountRecords.query.filter(
|
|
AccountRecords.build_parts_id == payment_row.build_parts_id, AccountRecords.payment_result_type == payment_type.id, AccountRecords.active == True,
|
|
AccountRecords.currency_value > 0, AccountRecords.currency_value > func.abs(AccountRecords.remainder_balance), *date_query_account_tuple,
|
|
).order_by(AccountRecords.bank_date.asc(), AccountRecords.remainder_balance.desc()).all()
|
|
for money_to_pay_row in money_to_pay_rows:
|
|
# Get remainder balance from database regardless trust over AccountRecords row from first query
|
|
remainder_balance_from_database = retrieve_remainder_balance_set_if_needed(money_to_pay_row.id, session)
|
|
available_funds = abs(money_to_pay_row.currency_value) - abs(remainder_balance_from_database)
|
|
# BuildDecisionBookPayments must be refreshed to check is there still money to be paid for this row
|
|
debt_to_pay = retrieve_current_debt_to_pay_from_database(ref_id=payment_row.ref_id, session=session)
|
|
if debt_to_pay == 0:
|
|
break # For session caused errors double check the database FOR multi process actions
|
|
if abs(available_funds) > abs(debt_to_pay):
|
|
payments_made += 1
|
|
total_amount_paid += debt_to_pay
|
|
paid_count += 1
|
|
close_payment_book(payment_row, money_to_pay_row, abs(debt_to_pay), session)
|
|
break # More fund to spend so go to next BuildDecisionBookPayments
|
|
else:
|
|
payments_made += 1
|
|
total_amount_paid += available_funds
|
|
paid_count += 1
|
|
close_payment_book(payment_row, money_to_pay_row, abs(available_funds), session)
|
|
continue # Fund has finished so go to next AccountRecords
|
|
|
|
print('payments_made', payments_made)
|
|
print('total_amount_paid', total_amount_paid)
|
|
print('paid_count', paid_count)
|
|
session.close()
|
|
session_factory.remove() # Clean up the session from the registry
|
|
|
|
|
|
def calculate_interest_from_date_paid_to_date_debt_to_pay(session, payment_value, payment, record):
|
|
"""Calculate interest from date paid to date debt to pay."""
|
|
AccountDelayInterest.set_session(session)
|
|
today = datetime.now()
|
|
calculated_interest = hesapla_gecikme_faizi(borc=payment_value, vade_tarihi=payment.process_date, islem_tarihi=today, faiz_turu="gecikme")
|
|
if not calculated_interest.toplam > 0:
|
|
return None
|
|
print('interest', dict(
|
|
debt_to_pay=payment_value, payment_process_date=str(payment.process_date.date()), record_bank_date=str(record.bank_date.date()),
|
|
available_money=abs(record.currency_value) - abs(record.remainder_balance), account_records_id=record.id, build_decision_book_payment_id=payment.id,
|
|
new_build_decision_book_payment_id=payment.build_decision_book_id, interest_rate=calculated_interest.gunluk_oran, delay_day=calculated_interest.gecikme_gunu,
|
|
daily_rate=calculated_interest.gunluk_oran, interest=calculated_interest.faiz, bsmv=calculated_interest.bsmv, kkdf=calculated_interest.kkdf,
|
|
total=calculated_interest.toplam,
|
|
))
|
|
# new_row_account_delay_interest = AccountDelayInterest.create(
|
|
# account_records_id=record.id, account_records_uu_id=str(record.uu_id), build_decision_book_payment_id=payment.id, build_decision_book_payment_uu_id=str(payment.uu_id),
|
|
# new_build_decision_book_payment_id=payment.build_decision_book_id, new_build_decision_book_payment_uu_id=str(payment.build_decision_book_uu_id),
|
|
# interest_turn="gecikme", interest_rate=calculated_interest.gunluk_oran, delay_day=calculated_interest.gecikme_gunu, daily_rate=calculated_interest.gunluk_oran,
|
|
# interest=calculated_interest.faiz, bsmv=calculated_interest.bsmv, kkdf=calculated_interest.kkdf, total=calculated_interest.toplam,
|
|
# )
|
|
# new_row_account_delay_interest.save()
|
|
# session.commit()
|
|
# session.refresh(new_row_account_delay_interest)
|
|
# return new_row_account_delay_interest
|
|
|
|
|
|
def do_payments_of_overdue_payments(session, build_id: int):
|
|
"""Process payments for the previous month's unpaid debts.
|
|
This function retrieves account records with available funds and processes
|
|
payments for previous month's unpaid debts in order of payment type priority.
|
|
"""
|
|
session_factory = get_session_factory()
|
|
session = session_factory()
|
|
|
|
# Set session for all models
|
|
AccountRecords.set_session(session)
|
|
BuildDecisionBookPayments.set_session(session)
|
|
Build.set_session(session)
|
|
BuildDecisionBook.set_session(session)
|
|
|
|
# Get payment types in priority order
|
|
payment_type_list = get_enums_from_database()
|
|
fund_finished = lambda money_spend, money_in_account: money_spend == money_in_account
|
|
payments_made, total_amount_paid, paid_count = 0, 0, 0
|
|
|
|
# first_date_of_process_date = find_first_day_of_month(datetime(period_date_start.year, period_date_start.month, 1))
|
|
# last_date_of_process_date = find_first_day_of_month(datetime(now.year, now.month - 1, 1))
|
|
# print('first_date_of_process_date', first_date_of_process_date)
|
|
# print('last_date_of_process_date', last_date_of_process_date)
|
|
|
|
# # Current month date filter
|
|
# date_query_tuple = (
|
|
# cast(BuildDecisionBookPayments.process_date, Date) >= first_date_of_process_date.date(), cast(BuildDecisionBookPayments.process_date, Date) <= last_date_of_process_date.date()
|
|
# )
|
|
|
|
priority_uuids = [pt.uuid for pt in payment_type_list]
|
|
case_order = case(*[(AccountRecords.payment_result_type_uu_id == str(uuid), index) for index, uuid in enumerate(priority_uuids)], else_=len(priority_uuids))
|
|
case_order_payment = case(*[(BuildDecisionBookPayments.payment_types_uu_id == str(uuid), index) for index, uuid in enumerate(priority_uuids)], else_=len(priority_uuids))
|
|
# for payment_type in payment_type_list:
|
|
query_of_payments = BuildDecisionBookPayments.query.filter(
|
|
BuildDecisionBookPayments.account_is_debit == True, BuildDecisionBookPayments.active == True,
|
|
cast(BuildDecisionBookPayments.process_date, Date) < find_first_day_of_month(today)
|
|
).order_by(
|
|
# Order by process_date in ascending order (oldest first)
|
|
cast(BuildDecisionBookPayments.process_date, Date).asc(), BuildDecisionBookPayments.payment_amount.desc(),
|
|
case_order_payment.asc(),
|
|
).all()
|
|
for payment_row in query_of_payments:
|
|
# Get the payment_row's process_date to find closest bank_date in AccountRecords
|
|
# First get all eligible records
|
|
money_to_pay_rows = AccountRecords.query.filter(
|
|
AccountRecords.build_parts_id == payment_row.build_parts_id,
|
|
# AccountRecords.payment_result_type == payment_type.id,
|
|
AccountRecords.active == True,
|
|
AccountRecords.currency_value > 0,
|
|
func.abs(AccountRecords.currency_value) > func.abs(AccountRecords.remainder_balance),
|
|
# Filter for bank_dates that are valid for this payment
|
|
# cast(AccountRecords.bank_date, Date) <= arrow.get(payment_row.process_date).to("Europe/Istanbul").date()
|
|
).order_by(
|
|
# Order by bank_date in ascending order (oldest first)
|
|
cast(AccountRecords.bank_date, Date).asc(),
|
|
case_order.asc(),
|
|
).all()
|
|
|
|
# Sort the results in Python by the absolute difference between dates
|
|
if money_to_pay_rows:
|
|
payment_date = payment_row.process_date.date() if hasattr(payment_row.process_date, 'date') else payment_row.process_date
|
|
money_to_pay_rows.sort(key=lambda x: abs((x.bank_date.date() if hasattr(x.bank_date, 'date') else x.bank_date) - payment_date))
|
|
for money_to_pay_row in money_to_pay_rows:
|
|
# Get remainder balance from database regardless trust over AccountRecords row from first query
|
|
remainder_balance_from_database = retrieve_remainder_balance_set_if_needed(money_to_pay_row.id, session)
|
|
available_funds = abs(money_to_pay_row.currency_value) - abs(remainder_balance_from_database)
|
|
# BuildDecisionBookPayments must be refreshed to check is there still money to be paid for this row
|
|
debt_to_pay = retrieve_current_debt_to_pay_from_database(ref_id=payment_row.ref_id, session=session)
|
|
if debt_to_pay == 0:
|
|
break # For session caused errors double check the database FOR multi process actions
|
|
if abs(available_funds) > abs(debt_to_pay):
|
|
print('More Money than debt to pay ------------------------------------------------------------------------------------------------------------------------')
|
|
payments_made += 1
|
|
total_amount_paid += debt_to_pay
|
|
paid_count += 1
|
|
close_payment_book(payment_row, money_to_pay_row, abs(debt_to_pay), session)
|
|
calculate_interest_from_date_paid_to_date_debt_to_pay(session=session, payment_value=abs(debt_to_pay), payment=payment_row, record=money_to_pay_row)
|
|
break # More fund to spend so go to next BuildDecisionBookPayments
|
|
else:
|
|
print('All Money is spent ---------------------------------------------------------------------------------------------------------------------------------')
|
|
payments_made += 1
|
|
total_amount_paid += available_funds
|
|
paid_count += 1
|
|
close_payment_book(payment_row, money_to_pay_row, abs(available_funds), session)
|
|
calculate_interest_from_date_paid_to_date_debt_to_pay(session=session, payment_value=abs(available_funds), payment=payment_row, record=money_to_pay_row)
|
|
continue # Fund has finished so go to next AccountRecords
|
|
print('payments_made', payments_made)
|
|
print('total_amount_paid', total_amount_paid)
|
|
print('paid_count', paid_count)
|
|
|
|
|
|
def do_regular_monthly_payers_payment_function(session, build_id: int, period_count: int = 1, skip_count: int = 0):
|
|
today = datetime.now()
|
|
Build.set_session(session)
|
|
BuildDecisionBook.set_session(session)
|
|
|
|
target_build = Build.query.filter(Build.id == build_id).first()
|
|
if not target_build:
|
|
raise ValueError(f"Build with id {build_id} not found")
|
|
decision_books = BuildDecisionBook.query.filter(
|
|
BuildDecisionBook.build_id == build_id, cast(BuildDecisionBook.expiry_starts, Date) <= today.date(), BuildDecisionBook.active == True,
|
|
).order_by(BuildDecisionBook.expiry_starts.desc()).limit(period_count + skip_count).offset(skip_count).all()
|
|
if not decision_books:
|
|
raise ValueError(f"Decision book not found for build with id {build_id}")
|
|
if skip_count > 0:
|
|
decision_books = decision_books[skip_count:]
|
|
first_decision_book = decision_books[-1]
|
|
work_date = find_first_day_of_month(first_decision_book.expiry_starts)
|
|
while work_date <= today:
|
|
do_payments(session=session, build_id=build_id, work_date=work_date)
|
|
work_date, _ = add_month_to_date(work_date)
|
|
return True
|
|
|
|
|
|
if __name__ == "__main__":
|
|
today, build_id, start_time = datetime.now(), 1, perf_counter()
|
|
|
|
session_factory = get_session_factory()
|
|
session = session_factory()
|
|
|
|
print("\n===== PROCESSING PAYMENTS =====\n")
|
|
print("Starting payment processing at:", str(today))
|
|
|
|
# Process payments for current month first
|
|
print("\n2. Processing current month payments...")
|
|
do_payments(session=session, build_id=build_id, work_date=today)
|
|
|
|
# Do regular monthly payers payments
|
|
print("\n1. Processing regular monthly payers payments...")
|
|
do_regular_monthly_payers_payment_function(session=session, build_id=build_id, period_count=4, skip_count=0)
|
|
|
|
# Commit and flush session to prevent session caused errors
|
|
session.commit()
|
|
session.flush()
|
|
|
|
# Process payments for previous months
|
|
print("\n2. Processing previous months payments...")
|
|
total_amount_of_available_funds = session.query(func.sum(AccountRecords.currency_value)).filter(
|
|
AccountRecords.currency_value > 0, func.abs(AccountRecords.currency_value) > func.abs(AccountRecords.remainder_balance),
|
|
AccountRecords.build_parts_id.isnot(None), AccountRecords.active == True
|
|
).scalar()
|
|
print(f"Total amount of available funds: {total_amount_of_available_funds:,.2f}")
|
|
total_amount_of_debt_to_pay = session.query(func.sum(BuildDecisionBookPayments.debt_to_pay)).filter(
|
|
BuildDecisionBookPayments.debt_to_pay > 0,
|
|
BuildDecisionBookPayments.account_is_debit == True,
|
|
BuildDecisionBookPayments.active == True
|
|
).scalar()
|
|
print(f"Total amount of debt to pay: {total_amount_of_debt_to_pay:,.2f}")
|
|
|
|
if total_amount_of_available_funds - total_amount_of_debt_to_pay > 0:
|
|
do_payments_of_overdue_payments(session=session, build_id=build_id)
|
|
print("\n===== PAYMENT PROCESSING COMPLETE =====\n")
|
|
|
|
# Analyze the payment situation after processing payments
|
|
print("\n===== ANALYZING PAYMENT SITUATION AFTER PROCESSING =====\n")
|
|
# analyze_payment_function()
|
|
|
|
end_time = perf_counter()
|
|
print(f"\n{end_time - start_time:.3f} : seconds")
|