production-evyos-systems-an.../ServicesBank/Finder/Payment/runner_new.py

417 lines
24 KiB
Python

import arrow
import calendar
from time import perf_counter
from decimal import Decimal
from datetime import datetime, timedelta
from Schemas import BuildDecisionBookPayments, AccountRecords, AccountDelayInterest, ApiEnumDropdown, Build, BuildDecisionBook
from sqlalchemy import select, func, distinct, cast, Date, String, literal, desc, and_, or_, case
from Controllers.Postgres.engine import get_session_factory
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 close_payment_book(payment_row_book, account_record, value, session, is_commission_applicable: bool = False):
"""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,
account_is_debit=False,
)
# Save the new payment record
saved_row = new_row.save()
session.commit()
session.refresh(saved_row)
if is_commission_applicable:
AccountDelayInterest.set_session(session)
new_row_interest = AccountDelayInterest.create(
account_record_bank_date=account_record.bank_date,
book_payment_process_date=payment_row_book.process_date,
debt=abs(value),
account_records_id=account_record.id,
account_records_uu_id=str(account_record.uu_id),
build_decision_book_payment_id=saved_row.id,
build_decision_book_payment_uu_id=str(saved_row.uu_id),
)
new_row_interest.save()
session.commit()
session.refresh(new_row_interest)
print(f'Commission applicable: amount : {value} bank date: {account_record.bank_date} process date: {payment_row_book.process_date}')
# 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 None
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
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
)
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]
def get_read_payment_type(payment_result_type_uu_id):
payment_type_list = get_enums_from_database()
for payment_type in payment_type_list:
if payment_type.uuid == payment_result_type_uu_id:
return payment_type.key
return None
def pay_book_payment_by_account_records_id(account_records_id: int, session, payment_type_list: list):
timezone, today = 'Europe/Istanbul', datetime.now()
AccountRecords.set_session(session)
account_record_selected = AccountRecords.query.filter_by(id=account_records_id).first()
if not account_record_selected:
print(f"Account record not found with id: {account_records_id}")
return
# raise ValueError(f"Account record not found with id: {account_records_id}")
if not account_record_selected.currency_value + account_record_selected.remainder_balance > 0:
print(f"Account record {account_record_selected.id} has no money to pay {account_record_selected.currency_value + account_record_selected.remainder_balance}")
return
# raise ValueError(f"Account record {account_record_selected.id} has no money to pay {account_record_selected.currency_value + account_record_selected.remainder_balance}")
BuildDecisionBook.set_session(session)
selected_decision_book = BuildDecisionBook.query.filter_by(id=account_record_selected.build_decision_book_id).first()
if not selected_decision_book:
print(f"Build decision book not found with id: {account_record_selected.build_decision_book_id}")
return
# raise ValueError(f"Build decision book not found with id: {account_record_selected.build_decision_book_id}")
BuildDecisionBookPayments.set_session(session)
payment_result_type_uu_id = account_record_selected.payment_result_type_uu_id or payment_type_list[0]
priority_uuids = [payment_result_type_uu_id, *[pt.uuid for pt in payment_type_list if pt.uuid != payment_result_type_uu_id]]
order_payment_by_type = case(*[(BuildDecisionBookPayments.payment_types_uu_id == uuid, index) for index, uuid in enumerate(priority_uuids)], else_=len(priority_uuids))
payment_value_func = lambda account_record_selected: account_record_selected.currency_value + account_record_selected.remainder_balance
period_start_date = arrow.get(selected_decision_book.expiry_starts).to(timezone).datetime
period_end_date = arrow.get(selected_decision_book.expiry_ends).to(timezone).datetime
bank_date_from = arrow.get(account_record_selected.bank_date).to(timezone).datetime
month_start_date = find_first_day_of_month(bank_date_from)
payment_type_list = [payment_result_type_uu_id, *[pt.uuid for pt in payment_type_list if pt.uuid != payment_result_type_uu_id]]
# Do payments of period_start_date to bank_date_from [This months payment]
print('Do payments of period_start_date to bank_date_from [This months payment]')
for payment_type in payment_type_list:
# No matter what paymnet_uu_id check for debt and money amount to clarify debt close !
selected_payments = BuildDecisionBookPayments.query.filter(
BuildDecisionBookPayments.account_is_debit.is_(True),
BuildDecisionBookPayments.active.is_(True),
BuildDecisionBookPayments.build_parts_id == account_record_selected.build_parts_id,
BuildDecisionBookPayments.payment_types_uu_id == payment_type,
BuildDecisionBookPayments.is_closed.is_(False),
cast(BuildDecisionBookPayments.process_date, Date) >= month_start_date,
cast(BuildDecisionBookPayments.process_date, Date) <= bank_date_from,
).order_by(BuildDecisionBookPayments.process_date.desc()).all()
print(
'dates period_start_date:', month_start_date.strftime('%Y-%m-%d %H:%M:%S'),
'period_end_date:', bank_date_from.strftime('%Y-%m-%d %H:%M:%S'),
"payment_type", get_read_payment_type(payment_type),
)
print('fund', dict(
id=account_record_selected.id, build_parts_id=account_record_selected.build_parts_id, money_available=payment_value_func(account_record_selected),
bank_date=arrow.get(bank_date_from).format('YYYY-MM-DD HH:mm:ss'), currency_value=account_record_selected.currency_value,
remainder_balance=account_record_selected.remainder_balance, length_matches=len(selected_payments),
))
money_available = payment_value_func(account_record_selected)
if not money_available > 0:
print(f"Account record {account_record_selected.id} has no money to pay {money_available}")
break
# raise ValueError(f"Account record {account_record_selected.id} has no money to pay {money_available}")
for selected_payment in selected_payments:
money_available = payment_value_func(account_record_selected)
if selected_payment.is_closed:
continue
print('debt', dict(
id=selected_payment.id, payment_type=selected_payment.payment_types_id,
debt_date=arrow.get(selected_payment.process_date).format('YYYY-MM-DD HH:mm:ss'),
payment_amount=selected_payment.payment_amount, debt_paid=selected_payment.debt_paid,
debt_to_pay=selected_payment.debt_to_pay, is_closed=selected_payment.is_closed,
))
if money_available > (-1 * selected_payment.debt_to_pay):
close_payment_book(
payment_row_book=selected_payment,
account_record=account_record_selected,
value=(-1 * selected_payment.debt_to_pay),
session=session,
is_commission_applicable=False
)
account_record_selected = AccountRecords.query.filter_by(id=account_records_id).first()
money_available = payment_value_func(account_record_selected)
print('updated fund', dict(
id=account_record_selected.id, build_parts_id=account_record_selected.build_parts_id, money_available=money_available,
bank_date=arrow.get(bank_date_from).format('YYYY-MM-DD HH:mm:ss'), currency_value=account_record_selected.currency_value,
remainder_balance=account_record_selected.remainder_balance, length_matches=len(selected_payments),
))
print(f"There is still money to pay for this account record {account_record_selected.id} fund to continue : {money_available}")
continue
else:
close_payment_book(
payment_row_book=selected_payment,
account_record=account_record_selected,
value=money_available,
session=session,
is_commission_applicable=False
)
account_record_selected = AccountRecords.query.filter_by(id=account_records_id).first()
money_available = payment_value_func(account_record_selected)
print('updated fund', dict(
id=account_record_selected.id, build_parts_id=account_record_selected.build_parts_id, money_available=money_available,
bank_date=arrow.get(bank_date_from).format('YYYY-MM-DD HH:mm:ss'), currency_value=account_record_selected.currency_value,
remainder_balance=account_record_selected.remainder_balance, length_matches=len(selected_payments),
))
print(f"All payments for account record {account_record_selected.id} have been paid.")
break
# Do payments of period_start_date to bank_date_from [Build Decision Book Payments of period]
print('Do payments of period_start_date to bank_date_from [Build Decision Book Payments of period]')
# for payment_type in payment_type_list:
money_available = payment_value_func(account_record_selected)
if not money_available > 0:
print(f"Account record {account_record_selected.id} has no money to pay {money_available}")
return
print(f"No debt is found for account record {account_record_selected.id} for this month. Fund is still available: {money_available}")
selected_payments = BuildDecisionBookPayments.query.filter(
BuildDecisionBookPayments.account_is_debit.is_(True),
BuildDecisionBookPayments.active.is_(True),
BuildDecisionBookPayments.is_closed.is_(False),
BuildDecisionBookPayments.build_parts_id == account_record_selected.build_parts_id,
# BuildDecisionBookPayments.payment_types_uu_id == payment_type,
cast(BuildDecisionBookPayments.process_date, Date) >= period_start_date,
cast(BuildDecisionBookPayments.process_date, Date) <= bank_date_from,
).order_by(BuildDecisionBookPayments.process_date.desc(), order_payment_by_type).all()
print(
'dates period_start_date:', period_start_date.strftime('%Y-%m-%d %H:%M:%S'),
'period_end_date:', bank_date_from.strftime('%Y-%m-%d %H:%M:%S'),
"payment_type", get_read_payment_type(payment_type),
)
for selected_payment in selected_payments:
money_available = payment_value_func(account_record_selected)
if selected_payment.is_closed:
continue
print('debt', dict(
id=selected_payment.id, payment_type=selected_payment.payment_types_id,
debt_date=arrow.get(selected_payment.process_date).format('YYYY-MM-DD HH:mm:ss'),
payment_amount=selected_payment.payment_amount, debt_paid=selected_payment.debt_paid,
debt_to_pay=selected_payment.debt_to_pay, is_closed=selected_payment.is_closed,
))
if money_available > (-1 * selected_payment.debt_to_pay):
close_payment_book(
payment_row_book=selected_payment, account_record=account_record_selected, value=(-1 * selected_payment.debt_to_pay), session=session, is_commission_applicable=True
)
account_record_selected = AccountRecords.query.filter_by(id=account_records_id).first()
money_available = payment_value_func(account_record_selected)
print('updated fund', dict(
id=account_record_selected.id, build_parts_id=account_record_selected.build_parts_id, money_available=money_available,
bank_date=arrow.get(bank_date_from).format('YYYY-MM-DD HH:mm:ss'), currency_value=account_record_selected.currency_value,
remainder_balance=account_record_selected.remainder_balance, length_matches=len(selected_payments),
))
print(f"There is still money to pay for this account record {account_record_selected.id} fund to continue : {money_available}")
continue
else:
close_payment_book(
payment_row_book=selected_payment, account_record=account_record_selected, value=money_available, session=session, is_commission_applicable=True
)
account_record_selected = AccountRecords.query.filter_by(id=account_records_id).first()
money_available = payment_value_func(account_record_selected)
print('updated fund', dict(
id=account_record_selected.id, build_parts_id=account_record_selected.build_parts_id, money_available=money_available,
bank_date=arrow.get(bank_date_from).format('YYYY-MM-DD HH:mm:ss'), currency_value=account_record_selected.currency_value,
remainder_balance=account_record_selected.remainder_balance, length_matches=len(selected_payments),
))
print(f"All payments for account record {account_record_selected.id} have been paid.")
break
# Do all payments of period_start_date to current_date [All payments]
print('Do all payments of period_start_date to current_date [All payments]')
# for payment_type in payment_type_list:
money_available = payment_value_func(account_record_selected)
if not money_available > 0:
print(f"Account record {account_record_selected.id} has no money to pay {money_available}")
return
print(f"No debt is found for account record {account_record_selected.id} for this month. Fund is still available: {money_available}")
selected_payments = BuildDecisionBookPayments.query.filter(
BuildDecisionBookPayments.account_is_debit.is_(True),
BuildDecisionBookPayments.active.is_(True),
BuildDecisionBookPayments.is_closed.is_(False),
BuildDecisionBookPayments.build_parts_id == account_record_selected.build_parts_id,
# BuildDecisionBookPayments.payment_types_uu_id == payment_type,
cast(BuildDecisionBookPayments.process_date, Date) >= period_start_date,
cast(BuildDecisionBookPayments.process_date, Date) <= today.date(),
).order_by(BuildDecisionBookPayments.process_date.desc(), order_payment_by_type).all()
print(
'dates period_start_date:', period_start_date.strftime('%Y-%m-%d %H:%M:%S'),
'period_end_date:', today.date().strftime('%Y-%m-%d %H:%M:%S'),
"payment_type", get_read_payment_type(payment_type),
)
for selected_payment in selected_payments:
money_available = payment_value_func(account_record_selected)
if selected_payment.is_closed:
continue
print('debt', dict(
id=selected_payment.id, payment_type=selected_payment.payment_types_id,
debt_date=arrow.get(selected_payment.process_date).format('YYYY-MM-DD HH:mm:ss'),
payment_amount=selected_payment.payment_amount, debt_paid=selected_payment.debt_paid,
debt_to_pay=selected_payment.debt_to_pay, is_closed=selected_payment.is_closed,
))
if money_available > (-1 * selected_payment.debt_to_pay):
close_payment_book(
payment_row_book=selected_payment, account_record=account_record_selected, value=(-1 * selected_payment.debt_to_pay), session=session, is_commission_applicable=True
)
account_record_selected = AccountRecords.query.filter_by(id=account_records_id).first()
money_available = payment_value_func(account_record_selected)
print('updated fund', dict(
id=account_record_selected.id, build_parts_id=account_record_selected.build_parts_id, money_available=money_available,
bank_date=arrow.get(bank_date_from).format('YYYY-MM-DD HH:mm:ss'), currency_value=account_record_selected.currency_value,
remainder_balance=account_record_selected.remainder_balance, length_matches=len(selected_payments),
))
print(f"There is still money to pay for this account record {account_record_selected.id} fund to continue : {money_available}")
continue
else:
close_payment_book(
payment_row_book=selected_payment, account_record=account_record_selected, value=money_available, session=session, is_commission_applicable=True
)
account_record_selected = AccountRecords.query.filter_by(id=account_records_id).first()
money_available = payment_value_func(account_record_selected)
print('updated fund', dict(
id=account_record_selected.id, build_parts_id=account_record_selected.build_parts_id, money_available=money_available,
bank_date=arrow.get(bank_date_from).format('YYYY-MM-DD HH:mm:ss'), currency_value=account_record_selected.currency_value,
remainder_balance=account_record_selected.remainder_balance, length_matches=len(selected_payments),
))
print(f"All payments for account record {account_record_selected.id} have been paid.")
break
if __name__ == "__main__":
import sys
# Default values
id_given = 1
session_factory = get_session_factory()
session = session_factory()
payment_type_list = get_enums_from_database()
# Parse command line arguments
if len(sys.argv) > 1:
try:
id_given = int(sys.argv[1])
except ValueError:
print(f"Error: Invalid limit value '{sys.argv[1]}'. Using default limit={limit}")
result = pay_book_payment_by_account_records_id(account_records_id=id_given, session=session, payment_type_list=payment_type_list)