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,text, Integer, Float, func, distinct, cast, Date, String, literal, desc, and_, or_, case from sqlalchemy.sql.expression import literal 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 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 read_all_account_records(session, date_from, list_payment_type = None): if list_payment_type: return session.query(AccountRecords).filter( func.abs(AccountRecords.currency_value) - func.abs(AccountRecords.remainder_balance) > 0, cast(AccountRecords.bank_date, Date) >= date_from, cast(AccountRecords.bank_date, Date) <= datetime.now().date(), AccountRecords.currency_value > 0, AccountRecords.build_parts_id > 1, AccountRecords.payment_result_type == list_payment_type.id, ).order_by(AccountRecords.build_parts_id.asc(), AccountRecords.bank_date.asc()).all() return session.query(AccountRecords).filter( func.abs(AccountRecords.currency_value) - func.abs(AccountRecords.remainder_balance) > 0, cast(AccountRecords.bank_date, Date) >= date_from, cast(AccountRecords.bank_date, Date) <= datetime.now().date(), AccountRecords.currency_value > 0, AccountRecords.build_parts_id > 1, ).order_by(AccountRecords.build_parts_id.asc(), AccountRecords.bank_date.asc()).all() def calculate_debts_for_this_month(session, date_from, list_payment_type, bank_date, build_decision_book_id, build_parts_id): selected_bank_date_month = bank_date.month selected_bank_date_year = bank_date.year return session.query(func.sum(BuildDecisionBookPayments.debt_to_pay)).filter( BuildDecisionBookPayments.build_decision_book_id == build_decision_book_id, func.extract('month', BuildDecisionBookPayments.process_date) == int(selected_bank_date_month), func.extract('year', BuildDecisionBookPayments.process_date) == int(selected_bank_date_year), BuildDecisionBookPayments.payment_types_id == list_payment_type.id, BuildDecisionBookPayments.account_is_debit == True, BuildDecisionBookPayments.is_closed == False, BuildDecisionBookPayments.build_parts_id == build_parts_id, ).scalar() def calculate_debts_for_previous_month(session, date_from, list_payment_type, bank_date, build_decision_book_id, build_parts_id): return session.query(func.sum(BuildDecisionBookPayments.debt_to_pay)).filter( BuildDecisionBookPayments.build_decision_book_id == build_decision_book_id, cast(BuildDecisionBookPayments.process_date, Date) <= bank_date.date(), BuildDecisionBookPayments.payment_types_id == list_payment_type.id, BuildDecisionBookPayments.account_is_debit == True, BuildDecisionBookPayments.is_closed == False, BuildDecisionBookPayments.build_parts_id == build_parts_id, ).scalar() def calculate_debts_for_previous_month_without_payment_type(session, date_from, bank_date, build_decision_book_id, build_parts_id): return session.query(func.sum(BuildDecisionBookPayments.debt_to_pay)).filter( BuildDecisionBookPayments.build_decision_book_id == build_decision_book_id, # cast(BuildDecisionBookPayments.process_date, Date) <= bank_date.date(), BuildDecisionBookPayments.account_is_debit == True, BuildDecisionBookPayments.is_closed == False, BuildDecisionBookPayments.build_parts_id == build_parts_id, ).scalar() def get_rows_of_debt_for_this_month(session, date_from, list_payment_type, bank_date, build_decision_book_id, build_parts_id): selected_bank_date_month = bank_date.month selected_bank_date_year = bank_date.year return session.query(BuildDecisionBookPayments).filter( BuildDecisionBookPayments.build_decision_book_id == build_decision_book_id, func.extract('month', BuildDecisionBookPayments.process_date) == int(selected_bank_date_month), func.extract('year', BuildDecisionBookPayments.process_date) == int(selected_bank_date_year), BuildDecisionBookPayments.payment_types_id == list_payment_type.id, BuildDecisionBookPayments.account_is_debit == True, BuildDecisionBookPayments.is_closed == False, BuildDecisionBookPayments.build_parts_id == build_parts_id, ).order_by(BuildDecisionBookPayments.debt_to_pay.desc()).all() def get_rows_of_debt_for_previous_month(session, date_from, list_payment_type, bank_date, build_decision_book_id, build_parts_id): return session.query(BuildDecisionBookPayments).filter( BuildDecisionBookPayments.build_decision_book_id == build_decision_book_id, cast(BuildDecisionBookPayments.process_date, Date) <= bank_date.date(), BuildDecisionBookPayments.payment_types_id == list_payment_type.id, BuildDecisionBookPayments.account_is_debit == True, BuildDecisionBookPayments.is_closed == False, BuildDecisionBookPayments.build_parts_id == build_parts_id, ).order_by(BuildDecisionBookPayments.debt_to_pay.desc(), BuildDecisionBookPayments.process_date.desc()).all() def get_rows_of_debt_for_previous_month_without_payment_type(session, date_from, bank_date, build_decision_book_id, build_parts_id): return session.query(BuildDecisionBookPayments).filter( BuildDecisionBookPayments.build_decision_book_id == build_decision_book_id, # cast(BuildDecisionBookPayments.process_date, Date) <= bank_date.date(), BuildDecisionBookPayments.account_is_debit == True, BuildDecisionBookPayments.is_closed == False, BuildDecisionBookPayments.build_parts_id == build_parts_id, ).order_by(BuildDecisionBookPayments.debt_to_pay.desc(), BuildDecisionBookPayments.process_date.desc()).all() 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) debt_to_pay_with_commission_applied = payment_row_book.debt_to_pay - value if debt_to_pay_with_commission_applied < 0: return None new_row_interest = AccountDelayInterest.create( account_record_bank_date=account_record.bank_date, book_payment_process_date=payment_row_book.process_date, debt=abs(debt_to_pay_with_commission_applied), account_records_id=account_record.id, account_records_uu_id=str(account_record.uu_id), build_decision_book_payment_id=payment_row_book.id, build_decision_book_payment_uu_id=str(payment_row_book.uu_id), replication_id=99, # Triggers for trigger_account_delay_interest calculation [active trigger] bank_resp_company_id=1, # Evyos interest rates will be applied when id is given bank_resp_company_uu_id="CMP", ) new_row_interest.save() session.commit() session.refresh(new_row_interest) print(f"Applying commission : {payment_row_book.build_parts_id}") print(f"Account record id : {account_record.id} - {account_record.bank_date.date()}") print(f"Commission applied : {debt_to_pay_with_commission_applied} | {payment_row_book.debt_to_pay}") print(f"Build Decision Book id : {payment_row_book.build_decision_book_id} - {payment_row_book.process_date.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_available_account_records_fund(session, account_record_id): return session.query(func.abs(AccountRecords.currency_value) - func.abs(AccountRecords.remainder_balance)).filter( AccountRecords.id == account_record_id, ).scalar() def set_account_and_payments_with_this_month_debts(session, date_from, list_payment_types): # Function 1: Read only matching month and year of account records and close debts for each payment type [Commission Applicable if insufficient funds] print('Function 1: Read only matching month and year of account records and close debts for each payment type [Commission Applicable if insufficient funds]') print('---' * 200) AccountRecords.set_session(session) BuildDecisionBookPayments.set_session(session) BuildDecisionBook.set_session(session) for list_payment_type in list_payment_types: result_all_account_records = read_all_account_records(session, date_from, list_payment_type) print('list_payment_type', list_payment_type.id, '---' * 50) for result_all_account_record in result_all_account_records: AccountRecords.set_session(session) BuildDecisionBookPayments.set_session(session) selected_bank_date = result_all_account_record.bank_date selected_bank_date_month = selected_bank_date.month selected_bank_date_year = selected_bank_date.year avaliable_fund = get_available_account_records_fund(session, result_all_account_record.id) # print(f'iterating account records id : ', result_all_account_record.id) # print(f'iterating account records bank_date : ', result_all_account_record.bank_date.date()) # print(f'iterating account records currency_value : ', result_all_account_record.currency_value) # print(f'iterating account records remainder_balance : ', result_all_account_record.remainder_balance) # print(f'iterating account records process comment : ', result_all_account_record.process_comment) # print(f'iterating account records build_parts_id : ', result_all_account_record.build_parts_id) # print(f'iterating account records decision_book_id : ', result_all_account_record.build_decision_book_id) # print(f'iterating account records avaliable_fund : {avaliable_fund:,.2f}') get_payments_dict = dict( session=session, date_from=date_from, list_payment_type=list_payment_type, bank_date=selected_bank_date, build_decision_book_id=result_all_account_record.build_decision_book_id, build_parts_id=result_all_account_record.build_parts_id ) debts_for_this_month = calculate_debts_for_this_month(**get_payments_dict) or 0 print_variable_of_debt = f"{debts_for_this_month:,.2f}" if debts_for_this_month else "No debts found" # print(f'{selected_bank_date_year}-{selected_bank_date_month:02d}: {print_variable_of_debt}') if debts_for_this_month > 0 and avaliable_fund > 0: this_month_debts = get_rows_of_debt_for_this_month(**get_payments_dict) for this_month_debt in this_month_debts: if this_month_debt.debt_to_pay <= avaliable_fund: # print(f"debt process date : {this_month_debt.process_date.date()}") # print(f"debt amount : {this_month_debt.debt_to_pay:,.2f}") # print(f"money paid : {this_month_debt.debt_to_pay:,.2f}") close_payment_book( payment_row_book=this_month_debt, account_record=result_all_account_record, value=this_month_debt.debt_to_pay, session=session, is_commission_applicable=False ) avaliable_fund = get_available_account_records_fund(session, result_all_account_record.id) # print(f"Remaind Balance : {avaliable_fund:,.2f}") if avaliable_fund <= 0: break # else: # close_payment_book( # payment_row_book=this_month_debt, account_record=result_all_account_record, value=avaliable_fund, session=session, is_commission_applicable=True # ) # print(f"Insufficient fund for debt :( : {this_month_debt.debt_to_pay:,.2f}") # print(f"Fund available :( : {avaliable_fund:,.2f}") else: pass # print(f"No debts found for or fund found for {selected_bank_date_year}-{selected_bank_date_month:02d} debt: {debts_for_this_month:,.2f} fund: {avaliable_fund:,.2f}") # Function 1: Read only matching month and year of account records and close debts for each payment type [Commission Applicable if insufficient funds] def set_account_and_payments_with_previous_month_debts(session, date_from, list_payment_types): # Function 2: Read only bank date < decision book payment process date and close debts for each payment type [Commission Applicable if insufficient funds] print('Function 2: Read only bank date < decision book payment process date and close debts for each payment type [Commission Applicable if insufficient funds]') print('---' * 200) for list_payment_type in list_payment_types: result_all_account_records = read_all_account_records(session, date_from, list_payment_type) for result_all_account_record in result_all_account_records: AccountRecords.set_session(session) BuildDecisionBookPayments.set_session(session) selected_bank_date = result_all_account_record.bank_date selected_bank_date_month = selected_bank_date.month selected_bank_date_year = selected_bank_date.year avaliable_fund = get_available_account_records_fund(session, result_all_account_record.id) print(f'iterating account records id : ', result_all_account_record.id) print(f'iterating account records bank_date : ', result_all_account_record.bank_date.date()) print(f'iterating account records currency_value : ', result_all_account_record.currency_value) print(f'iterating account records remainder_balance : ', result_all_account_record.remainder_balance) print(f'iterating account records process comment : ', result_all_account_record.process_comment) print(f'iterating account records build_parts_id : ', result_all_account_record.build_parts_id) print(f'iterating account records decision_book_id : ', result_all_account_record.build_decision_book_id) print(f'iterating account records payment type : ', list_payment_type.value, 'id: ', list_payment_type.id) print(f'iterating account records avaliable_fund : {avaliable_fund:,.2f}') get_payments_dict = dict( session=session, date_from=date_from, list_payment_type=list_payment_type, bank_date=selected_bank_date, build_decision_book_id=result_all_account_record.build_decision_book_id, build_parts_id=result_all_account_record.build_parts_id ) debts_for_this_month = calculate_debts_for_previous_month(**get_payments_dict) or 0 print_variable_of_debt = f"{debts_for_this_month:,.2f}" if debts_for_this_month else "No debts found" print(f'{selected_bank_date_year}-{selected_bank_date_month:02d}: {print_variable_of_debt}') if debts_for_this_month > 0 and avaliable_fund > 0: this_month_debts = get_rows_of_debt_for_previous_month(**get_payments_dict) for this_month_debt in this_month_debts: if this_month_debt.debt_to_pay <= avaliable_fund: print(f"debt process date : {this_month_debt.process_date.date()}") print(f"debt amount : {this_month_debt.debt_to_pay:,.2f}") print(f"money paid : {this_month_debt.debt_to_pay:,.2f}") print(f"Payment id : {this_month_debt.id}") close_payment_book( payment_row_book=this_month_debt, account_record=result_all_account_record, value=this_month_debt.debt_to_pay, session=session, is_commission_applicable=True ) avaliable_fund = get_available_account_records_fund(session, result_all_account_record.id) print(f"Remaind Balance : {avaliable_fund:,.2f}") if avaliable_fund <= 0: break # else: # # close_payment_book( # # payment_row_book=this_month_debt, # # account_record=result_all_account_record, # # value=avaliable_fund, # # session=session, # # is_commission_applicable=True # # ) # print(f"Insufficient fund for debt : {this_month_debt.debt_to_pay:,.2f}") # print(f"Debt process date : {this_month_debt.process_date.date()}") # print(f"Debt id : {this_month_debt.id}") # print(f"Fund available : {avaliable_fund:,.2f}") # print(f"Payment id : {this_month_debt.id}") else: print(f"No debts found for or fund found for {selected_bank_date_year}-{selected_bank_date_month:02d} debt: {debts_for_this_month:,.2f} fund: {avaliable_fund:,.2f}") def set_account_and_payments_with_previous_month_debts_without_payment_type(session, date_from): # Function 3: Read only bank date < decision book payment process date and close debts for without payment type [Commission Applicable if insufficient funds] print('Function 3: Read only bank date < decision book payment process date and close debts for without payment type [Commission Applicable if insufficient funds]') print('---' * 200) result_all_account_records = read_all_account_records(session, date_from) for result_all_account_record in result_all_account_records: AccountRecords.set_session(session) BuildDecisionBookPayments.set_session(session) selected_bank_date = result_all_account_record.bank_date selected_bank_date_month = selected_bank_date.month selected_bank_date_year = selected_bank_date.year avaliable_fund = get_available_account_records_fund(session, result_all_account_record.id) print(f'iterating account records id : ', result_all_account_record.id) print(f'iterating account records bank_date : ', result_all_account_record.bank_date.date()) print(f'iterating account records currency_value : ', result_all_account_record.currency_value) print(f'iterating account records remainder_balance : ', result_all_account_record.remainder_balance) print(f'iterating account records process comment : ', result_all_account_record.process_comment) print(f'iterating account records build_parts_id : ', result_all_account_record.build_parts_id) print(f'iterating account records decision_book_id : ', result_all_account_record.build_decision_book_id) print(f'iterating account records payment type : ', result_all_account_record.payment_result_type) print(f'iterating account records avaliable_fund : {avaliable_fund:,.2f}') get_payments_dict = dict( session=session, date_from=date_from, bank_date=selected_bank_date, build_decision_book_id=result_all_account_record.build_decision_book_id, build_parts_id=result_all_account_record.build_parts_id ) debts_for_this_month = calculate_debts_for_previous_month_without_payment_type(**get_payments_dict) or 0 print_variable_of_debt = f"{debts_for_this_month:,.2f}" if debts_for_this_month else "No debts found" print(f'{selected_bank_date_year}-{selected_bank_date_month:02d}: {print_variable_of_debt}') if debts_for_this_month > 0 and avaliable_fund > 0: this_month_debts = get_rows_of_debt_for_previous_month_without_payment_type(**get_payments_dict) for this_month_debt in this_month_debts: if this_month_debt.debt_to_pay <= avaliable_fund: print(f"debt process date : {this_month_debt.process_date.date()}") print(f"debt amount : {this_month_debt.debt_to_pay:,.2f}") print(f"money paid : {this_month_debt.debt_to_pay:,.2f}") print(f"Payment id : {this_month_debt.id}") print(f"Payment type : {this_month_debt.payment_types_id}") close_payment_book( payment_row_book=this_month_debt, account_record=result_all_account_record, value=this_month_debt.debt_to_pay, session=session, is_commission_applicable=True ) avaliable_fund = get_available_account_records_fund(session, result_all_account_record.id) print(f"Remaind Balance : {avaliable_fund:,.2f}") if avaliable_fund <= 0: break # else: # close_payment_book( # payment_row_book=this_month_debt, # account_record=result_all_account_record, # value=avaliable_fund, # session=session, # is_commission_applicable=True # ) # print(f"Insufficient fund for debt : {this_month_debt.debt_to_pay:,.2f}") # print(f"Debt process date : {this_month_debt.process_date.date()}") # print(f"Debt id : {this_month_debt.id}") # print(f"Fund available : {avaliable_fund:,.2f}") # print(f"Payment id : {this_month_debt.id}") else: print(f"No debts found for or fund found for {selected_bank_date_year}-{selected_bank_date_month:02d} debt: {debts_for_this_month:,.2f} fund: {avaliable_fund:,.2f}") if __name__ == "__main__": # import sys # Default values # id_given = 1 count_row = 0 session_factory = get_session_factory() session = session_factory() list_payment_types = get_enums_from_database() date_from = '2023-06-30' AccountRecords.set_session(session) BuildDecisionBookPayments.set_session(session) BuildDecisionBook.set_session(session) set_account_and_payments_with_this_month_debts(session, date_from, list_payment_types) session.commit() session.flush() set_account_and_payments_with_previous_month_debts(session, date_from, list_payment_types) session.commit() session.flush() set_account_and_payments_with_previous_month_debts_without_payment_type(session, date_from) session.commit() session.flush() session.close()