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 class ExactMatch: def __init__(self, account_record_id, build_decision_book_payment_id, living, diff, day_df, process_comment, bank_date, process_date, fund, debt, payment_correct): self.account_record_id = account_record_id self.build_decision_book_payment_id = build_decision_book_payment_id self.living = living self.diff = diff self.day_df = day_df self.process_comment = process_comment self.bank_date = str(bank_date) self.process_date = str(process_date) self.fund = fund self.debt = debt self.payment_correct = payment_correct def to_dict(self): return { 'account_record_id': self.account_record_id, 'build_decision_book_payment_id': self.build_decision_book_payment_id, # 'process_comment': self.process_comment, 'living': self.living, 'diff': self.diff, 'day_df': self.day_df, 'bank_date': self.bank_date, 'process_date': self.process_date, 'fund': self.fund, 'debt': self.debt, 'payment_correct': self.payment_correct } 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), 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'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 # Debt pay debt that is paid in same month early bird def pay_debt_that_is_paid_in_same_month_early_bird(session, payment_type_list, date_from): # Create aliases for tables ar = AccountRecords.__table__.alias('ar') bdp = BuildDecisionBookPayments.__table__.alias('bdp') # Calculate day difference expression day_diff = (func.cast(func.to_char(ar.c.bank_date, 'YYYYMMDD'), Integer) - func.cast(func.to_char(bdp.c.process_date, 'YYYYMMDD'), Integer)).label('day_df') # Calculate absolute difference between fund and payment amount_diff = func.abs((ar.c.currency_value + ar.c.remainder_balance) - func.abs(bdp.c.debt_to_pay)).label('diff') # Calculate fund amount fund_amount = (ar.c.currency_value + ar.c.remainder_balance).label('fund') # Payment correctness check payment_correct = (ar.c.payment_result_type == bdp.c.payment_types_id).label('payment_correct') # func.to_char(ar.c.bank_date, 'YYYY-MM') == func.to_char(bdp.c.process_date, 'YYYY-MM'), # Build the query using SQLAlchemy query = session.query( ar.c.id.label('account_record_id'), bdp.c.id.label('build_decision_book_payment_id'), ar.c.living_space_id.label('living'), amount_diff, day_diff, ar.c.process_comment, func.cast(ar.c.bank_date, Date).label('bank_date'), func.cast(bdp.c.process_date, Date).label('process_date'), fund_amount, bdp.c.debt_to_pay.label('debt'), payment_correct ).join( bdp, and_( ar.c.build_parts_id == bdp.c.build_parts_id, func.to_char(ar.c.bank_date, 'YYYY-MM') == func.to_char(bdp.c.process_date, 'YYYY-MM'), ar.c.payment_result_type == bdp.c.payment_types_id, (ar.c.currency_value + ar.c.remainder_balance) == func.abs(bdp.c.debt_to_pay), ) ).filter( ar.c.currency_value > 0, bdp.c.build_decision_book_id == ar.c.build_decision_book_id, bdp.c.is_closed.is_(False), bdp.c.account_is_debit.is_(True), func.cast(ar.c.bank_date, Date) <= datetime.now(), func.cast(bdp.c.process_date, Date) <= datetime.now(), func.cast(ar.c.bank_date, Date) > date_from, func.cast(bdp.c.process_date, Date) > date_from ).order_by(ar.c.living_space_id.asc(), day_diff.asc(), amount_diff.asc(), ar.c.bank_date.desc(), bdp.c.process_date.desc()) return query.all() # Debt pay debt that pay date in between thirty one day def pay_debt_that_pay_date_in_between_thirty_one_day(session, payment_type_list, date_from): # Create aliases for tables ar = AccountRecords.__table__.alias('ar') bdp = BuildDecisionBookPayments.__table__.alias('bdp') # Calculate day difference expression day_diff = (func.cast(func.to_char(ar.c.bank_date, 'YYYYMMDD'), Integer) - func.cast(func.to_char(bdp.c.process_date, 'YYYYMMDD'), Integer)).label('day_df') # Calculate absolute difference between fund and payment amount_diff = func.abs((ar.c.currency_value + ar.c.remainder_balance) - func.abs(bdp.c.debt_to_pay)).label('diff') # Calculate fund amount fund_amount = (ar.c.currency_value + ar.c.remainder_balance).label('fund') # Payment correctness check payment_correct = (ar.c.payment_result_type == bdp.c.payment_types_id).label('payment_correct') # func.to_char(ar.c.bank_date, 'YYYY-MM') == func.to_char(bdp.c.process_date, 'YYYY-MM'), # Build the query using SQLAlchemy query = session.query( ar.c.id.label('account_record_id'), bdp.c.id.label('build_decision_book_payment_id'), ar.c.living_space_id.label('living'), amount_diff, day_diff, ar.c.process_comment, func.cast(ar.c.bank_date, Date).label('bank_date'), func.cast(bdp.c.process_date, Date).label('process_date'), fund_amount, bdp.c.debt_to_pay.label('debt'), payment_correct ).join( bdp, and_( ar.c.build_parts_id == bdp.c.build_parts_id, ar.c.payment_result_type == bdp.c.payment_types_id, (ar.c.currency_value + ar.c.remainder_balance) == func.abs(bdp.c.debt_to_pay), func.abs(func.cast(ar.c.bank_date, Date) - func.cast(bdp.c.process_date, Date)) < 32, ) ).filter( bdp.c.is_closed.is_(False), ar.c.currency_value > 0, bdp.c.account_is_debit.is_(True), bdp.c.build_decision_book_id == ar.c.build_decision_book_id, func.cast(ar.c.bank_date, Date) < datetime.now(), func.cast(bdp.c.process_date, Date) < datetime.now(), func.cast(ar.c.bank_date, Date) > date_from, func.cast(bdp.c.process_date, Date) > date_from ).order_by(ar.c.living_space_id.asc(), day_diff.asc(), amount_diff.asc(), ar.c.bank_date.desc(), bdp.c.process_date.desc()) return query.all() # Debt pay debt that has sent to do many rows def pay_debt_that_has_sent_to_do_many_rows(session, payment_type_list, date_from): # Create aliases for tables ar = AccountRecords.__table__.alias('ar') bdp = BuildDecisionBookPayments.__table__.alias('bdp') # Calculate day difference expression day_diff = (func.cast(func.to_char(ar.c.bank_date, 'YYYYMMDD'), Integer) - func.cast(func.to_char(bdp.c.process_date, 'YYYYMMDD'), Integer)).label('day_df') # Calculate absolute difference between fund and payment amount_diff = func.abs((ar.c.currency_value + ar.c.remainder_balance) - func.abs(bdp.c.debt_to_pay)).label('diff') # Calculate fund amount fund_amount = (ar.c.currency_value + ar.c.remainder_balance).label('fund') # Payment correctness check payment_correct = (ar.c.payment_result_type == bdp.c.payment_types_id).label('payment_correct') # func.to_char(ar.c.bank_date, 'YYYY-MM') == func.to_char(bdp.c.process_date, 'YYYY-MM'), # Build the query using SQLAlchemy query = session.query( ar.c.id.label('account_record_id'), bdp.c.id.label('build_decision_book_payment_id'), ar.c.living_space_id.label('living'), amount_diff, day_diff, ar.c.process_comment, func.cast(ar.c.bank_date, Date).label('bank_date'), func.cast(bdp.c.process_date, Date).label('process_date'), fund_amount, bdp.c.debt_to_pay.label('debt'), payment_correct ).join( bdp, and_( ar.c.build_parts_id == bdp.c.build_parts_id, ar.c.build_decision_book_id == bdp.c.build_decision_book_id, func.abs(func.cast(ar.c.bank_date, Date) - func.cast(bdp.c.process_date, Date)) < 32, ar.c.payment_result_type == bdp.c.payment_types_id, (ar.c.currency_value + ar.c.remainder_balance) >= func.abs(bdp.c.debt_to_pay), bdp.c.is_closed.is_(False), ) ).filter( ar.c.currency_value > 0, ar.c.build_decision_book_id == bdp.c.build_decision_book_id, bdp.c.account_is_debit.is_(True), func.cast(ar.c.bank_date, Date) < datetime.now(), func.cast(bdp.c.process_date, Date) < datetime.now(), func.cast(ar.c.bank_date, Date) > date_from, func.cast(bdp.c.process_date, Date) > date_from ).order_by( ar.c.living_space_id.asc(), day_diff.asc(), amount_diff.asc(), ar.c.bank_date.desc(), bdp.c.process_date.desc() ) return query.all() if __name__ == "__main__": # import sys # Default values # id_given = 1 count_row = 0 session_factory = get_session_factory() session = session_factory() payment_type_list = get_enums_from_database() date_from = '2022-01-01' early_bird_results = pay_debt_that_is_paid_in_same_month_early_bird(session=session, payment_type_list=payment_type_list, date_from=date_from) for early_bird_result in early_bird_results: AccountRecords.set_session(session) BuildDecisionBookPayments.set_session(session) print(ExactMatch(*early_bird_result).to_dict()) account_record = AccountRecords.query.filter_by(id=early_bird_result.account_record_id).first() book_payment = BuildDecisionBookPayments.query.filter_by(id=early_bird_result.build_decision_book_payment_id).first() fund = account_record.currency_value + account_record.remainder_balance if fund < abs(book_payment.debt_to_pay): print("Fund is less than debt to pay", "-" * 500) continue if book_payment.is_closed: print("Payment is closed", "-" * 500) continue close_payment_book(account_record=account_record, payment_row_book=book_payment, session=session, value=book_payment.debt_to_pay, is_commission_applicable=False) count_row += 1 late_bird_results = pay_debt_that_pay_date_in_between_thirty_one_day(session=session, payment_type_list=payment_type_list, date_from=date_from) for late_bird_result in late_bird_results: AccountRecords.set_session(session) BuildDecisionBookPayments.set_session(session) print(ExactMatch(*late_bird_result).to_dict()) account_record = AccountRecords.query.filter_by(id=late_bird_result.account_record_id).first() book_payment = BuildDecisionBookPayments.query.filter_by(id=late_bird_result.build_decision_book_payment_id).first() fund = account_record.currency_value + account_record.remainder_balance if fund < abs(book_payment.debt_to_pay): print("Fund is less than debt to pay", "-" * 500) continue if book_payment.is_closed: print("Payment is closed", "-" * 500) continue close_payment_book(account_record=account_record, payment_row_book=book_payment, session=session, value=book_payment.debt_to_pay, is_commission_applicable=False) count_row += 1 thirty_three_days = pay_debt_that_has_sent_to_do_many_rows(session=session, payment_type_list=payment_type_list, date_from=date_from) for thirty_three_day in thirty_three_days: AccountRecords.set_session(session) BuildDecisionBookPayments.set_session(session) print(ExactMatch(*thirty_three_day).to_dict()) account_record = AccountRecords.query.filter_by(id=thirty_three_day.account_record_id).first() book_payment = BuildDecisionBookPayments.query.filter_by(id=thirty_three_day.build_decision_book_payment_id).first() fund = account_record.currency_value + account_record.remainder_balance if fund < abs(book_payment.debt_to_pay): print("Fund is less than debt to pay", "-" * 500) continue if book_payment.is_closed: print("Payment is closed", "-" * 500) continue close_payment_book(account_record=account_record, payment_row_book=book_payment, session=session, value=book_payment.debt_to_pay, is_commission_applicable=False) count_row += 1 session.close() print(f"Processed {count_row} rows") # 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}")