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)