import sys import arrow from decimal import Decimal from Schemas import BuildDecisionBookPayments, AccountRecords, ApiEnumDropdown # Counters for tracking conditions counter_current_currency_not_positive = 0 # Track when current_currency_value <= 0 counter_net_amount_not_positive = 0 # Track when net_amount <= 0 counter_account_records_updated = 0 # Track number of account records updated counter_payments_found = 0 # Track how many payments were found counter_found_payment_skips = 0 # Track how many times we skip due to found_payment counter_payment_exceptions = 0 # Track exceptions during payment processing counter_missing_build_parts_id = 0 # Track accounts with missing build_parts_id counter_null_payment_types = 0 # Track payments with null payment_types_id def pay_the_registration(account_record, receive_enum, debit_enum, is_old_record: bool = False, session=None): # If no session is provided, create a new one if session is None: with AccountRecords.new_session() as new_session: AccountRecords.set_session(new_session) BuildDecisionBookPayments.set_session(new_session) return _process_payment(account_record, receive_enum, debit_enum, is_old_record, new_session) else: # Use the provided session AccountRecords.set_session(session) BuildDecisionBookPayments.set_session(session) return _process_payment(account_record, receive_enum, debit_enum, is_old_record, session) def _process_payment(account_record, receive_enum, debit_enum, is_old_record, session): """Internal function to process payments with a given session""" current_currency_value = float(Decimal(account_record.currency_value)) - float(Decimal(account_record.remainder_balance)) if not current_currency_value > 0: global counter_current_currency_not_positive counter_current_currency_not_positive += 1 return current_currency_value # Check if account_record has build_parts_id if account_record.build_parts_id is None: global counter_missing_build_parts_id counter_missing_build_parts_id += 1 return current_currency_value process_date = arrow.get(account_record.bank_date) account_bank_date_year, account_bank_date_month = (process_date.date().year, process_date.date().month) # First, try to find payments with null payment_types_id payment_arguments_debit = [ BuildDecisionBookPayments.build_parts_id == account_record.build_parts_id, BuildDecisionBookPayments.account_records_id == None, ] # Add date filters if not processing old records if not is_old_record: payment_arguments_debit.extend([BuildDecisionBookPayments.process_date_y == int(account_bank_date_year), BuildDecisionBookPayments.process_date_m == int(account_bank_date_month)]) # First try with debit_enum.id payments = BuildDecisionBookPayments.query.filter(*payment_arguments_debit, BuildDecisionBookPayments.payment_types_id == debit_enum.id).order_by(BuildDecisionBookPayments.process_date.asc()).all() # If no payments found, try with null payment_types_id if len(payments) == 0: payments = BuildDecisionBookPayments.query.filter(*payment_arguments_debit, BuildDecisionBookPayments.payment_types_id == None).order_by(BuildDecisionBookPayments.process_date.asc()).all() if len(payments) > 0: global counter_null_payment_types counter_null_payment_types += len(payments) global counter_payments_found counter_payments_found += len(payments) # Debug: Print info about the first few payments found (if any) if len(payments) > 0 and account_record.id % 100 == 0: # Only print for every 100th record to avoid too much output print(f"DEBUG: Found {len(payments)} payments for account_record {account_record.id}") if len(payments) > 0: sample_payment = payments[0] print(f" Sample payment: ID={getattr(sample_payment, 'id', 'N/A')}, amount={getattr(sample_payment, 'payment_amount', 'N/A')}") if len(payments) == 0: # No payments found for this account record return current_currency_value for payment in payments: if not current_currency_value > 0: return current_currency_value payment_arguments_receive = [ BuildDecisionBookPayments.build_parts_id == account_record.build_parts_id, BuildDecisionBookPayments.payment_plan_time_periods == payment.payment_plan_time_periods, BuildDecisionBookPayments.payment_types_id == receive_enum.id, BuildDecisionBookPayments.build_decision_book_item_id == payment.build_decision_book_item_id, BuildDecisionBookPayments.decision_book_project_id == payment.decision_book_project_id, BuildDecisionBookPayments.process_date == payment.process_date, ] if not is_old_record: payment_arguments_receive.extend([BuildDecisionBookPayments.process_date_y == int(account_bank_date_year), BuildDecisionBookPayments.process_date_m == int(account_bank_date_month)]) payment_received = BuildDecisionBookPayments.query.filter(*payment_arguments_receive).all() sum_of_payment_received = sum([abs(payment.payment_amount) for payment in payment_received]) net_amount = float(abs(Decimal(payment.payment_amount))) - float(abs(Decimal(sum_of_payment_received))) if not net_amount > 0: global counter_net_amount_not_positive counter_net_amount_not_positive += 1 continue if float(abs(current_currency_value)) < float(abs(net_amount)): net_amount = float(current_currency_value) process_date = arrow.get(payment.process_date) try: found_payment = BuildDecisionBookPayments.query.filter_by( build_parts_id=payment.build_parts_id, payment_plan_time_periods=payment.payment_plan_time_periods, payment_types_id=receive_enum.id, build_decision_book_item_id=payment.build_decision_book_item_id, decision_book_project_id=payment.decision_book_project_id, process_date=str(process_date), ).first() if found_payment: global counter_found_payment_skips counter_found_payment_skips += 1 continue created_book_payment = BuildDecisionBookPayments.create( payment_plan_time_periods=payment.payment_plan_time_periods, payment_amount=float(abs(net_amount)), payment_types_id=receive_enum.id, payment_types_uu_id=str(receive_enum.uu_id), process_date=str(process_date), process_date_m=process_date.date().month, process_date_y=process_date.date().year, period_time=f"{process_date.year}-{str(process_date.month).zfill(2)}", build_parts_id=payment.build_parts_id, build_parts_uu_id=str(payment.build_parts_uu_id), account_records_id=account_record.id, account_records_uu_id=str(account_record.uu_id), build_decision_book_item_id=payment.build_decision_book_item_id, build_decision_book_item_uu_id=str(payment.build_decision_book_item_uu_id), decision_book_project_id=payment.decision_book_project_id, decision_book_project_uu_id=str(payment.decision_book_project_uu_id), ) created_book_payment.save() created_payment_amount = float(Decimal(created_book_payment.payment_amount)) remainder_balance = float(Decimal(account_record.remainder_balance)) + float(abs(created_payment_amount)) account_record.update(remainder_balance=remainder_balance) account_record.save() global counter_account_records_updated counter_account_records_updated += 1 if current_currency_value >= abs(net_amount): current_currency_value -= abs(net_amount) except Exception as e: print("Exception of decision payment:", e) global counter_payment_exceptions counter_payment_exceptions += 1 return current_currency_value def create_direct_payment(account_record, receive_enum, session): """ Create a direct payment record for an account record without relying on matching BuildDecisionBookPayments """ try: # Calculate the amount to process payment_amount = float(Decimal(account_record.currency_value)) - float(Decimal(account_record.remainder_balance)) if payment_amount <= 0: return False # Get process date information process_date = arrow.get(account_record.bank_date) process_date_y = process_date.date().year process_date_m = process_date.date().month period_time = f"{process_date_y}-{str(process_date_m).zfill(2)}" # Check if a payment already exists for this account record existing_payment = BuildDecisionBookPayments.query.filter_by(account_records_id=account_record.id, payment_types_id=receive_enum.id).first() if existing_payment: # Skip if payment already exists return False # Create a new payment record directly created_book_payment = BuildDecisionBookPayments.create( payment_plan_time_periods=1, # Default value payment_types_id=receive_enum.id, payment_types_uu_id=str(receive_enum.uu_id), payment_amount=payment_amount, process_date=str(process_date), process_date_y=process_date_y, process_date_m=process_date_m, period_time=period_time, account_records_id=account_record.id, account_records_uu_id=str(account_record.uu_id), build_parts_id=account_record.build_parts_id, build_parts_uu_id=str(account_record.build_parts_uu_id) if hasattr(account_record, 'build_parts_uu_id') and account_record.build_parts_uu_id else None, decision_book_project_id=getattr(account_record, 'decision_book_project_id', None), decision_book_project_uu_id=str(account_record.decision_book_project_uu_id) if hasattr(account_record, 'decision_book_project_uu_id') and account_record.decision_book_project_uu_id else None, ) created_book_payment.save() # Update the account record remainder_balance = float(Decimal(account_record.remainder_balance)) + float(abs(payment_amount)) account_record.update(remainder_balance=remainder_balance) account_record.save() global counter_account_records_updated counter_account_records_updated += 1 return True except Exception as e: print(f"Exception in create_direct_payment for account {account_record.id}: {e}") global counter_payment_exceptions counter_payment_exceptions += 1 return False def send_accounts_to_decision_payment(): with ApiEnumDropdown.new_session() as session: # Set the session for all models that will be used ApiEnumDropdown.set_session(session) AccountRecords.set_session(session) BuildDecisionBookPayments.set_session(session) try: # Get required enum values receive_enum = ApiEnumDropdown.query.filter_by(enum_class="DebitTypes", key="DT-R").first() debit_enum = ApiEnumDropdown.query.filter_by(enum_class="DebitTypes", key="DT-D").first() if not receive_enum or not debit_enum: print("Error: Could not find required enum values") return # Check if there are any BuildDecisionBookPayments records at all total_payments = BuildDecisionBookPayments.query.count() print(f"\n--- DEBUG: Database Statistics ---") print(f"Total BuildDecisionBookPayments records in database: {total_payments}") # Check how many have payment_types_id = debit_enum.id debit_payments = BuildDecisionBookPayments.query.filter_by(payment_types_id=debit_enum.id).count() print(f"BuildDecisionBookPayments with payment_types_id={debit_enum.id} (DT-D): {debit_payments}") # Check how many have account_records_id = None null_account_payments = BuildDecisionBookPayments.query.filter(BuildDecisionBookPayments.account_records_id == None).count() print(f"BuildDecisionBookPayments with account_records_id=None: {null_account_payments}") # Check a sample payment record sample_payment = BuildDecisionBookPayments.query.first() if sample_payment: print("\n--- Sample BuildDecisionBookPayment ---") print(f"ID: {getattr(sample_payment, 'id', 'N/A')}") print(f"payment_types_id: {getattr(sample_payment, 'payment_types_id', 'N/A')}") print(f"build_parts_id: {getattr(sample_payment, 'build_parts_id', 'N/A')}") print(f"account_records_id: {getattr(sample_payment, 'account_records_id', 'N/A')}") print(f"process_date_y: {getattr(sample_payment, 'process_date_y', 'N/A')}") print(f"process_date_m: {getattr(sample_payment, 'process_date_m', 'N/A')}") else: print("No BuildDecisionBookPayment records found in the database!") # Check a sample account record sample_account = AccountRecords.query.filter(AccountRecords.remainder_balance < AccountRecords.currency_value, AccountRecords.receive_debit == receive_enum.id).first() if sample_account: print("\n--- Sample AccountRecord ---") print(f"ID: {getattr(sample_account, 'id', 'N/A')}") print(f"build_parts_id: {getattr(sample_account, 'build_parts_id', 'N/A')}") print(f"bank_date: {getattr(sample_account, 'bank_date', 'N/A')}") # Try to find payments for this specific account record if sample_account.bank_date: process_date = arrow.get(sample_account.bank_date) account_bank_date_year, account_bank_date_month = (process_date.date().year, process_date.date().month) print("\n--- Checking for payments for sample account ---") print(f"Looking for payments with build_parts_id={sample_account.build_parts_id}, payment_types_id={debit_enum.id}, account_records_id=None") # Try without date filters first basic_payments = BuildDecisionBookPayments.query.filter( BuildDecisionBookPayments.build_parts_id == sample_account.build_parts_id, BuildDecisionBookPayments.payment_types_id == debit_enum.id, BuildDecisionBookPayments.account_records_id == None ).count() print(f"Found {basic_payments} payments without date filters") # Now try with date filters dated_payments = BuildDecisionBookPayments.query.filter( BuildDecisionBookPayments.build_parts_id == sample_account.build_parts_id, BuildDecisionBookPayments.payment_types_id == debit_enum.id, BuildDecisionBookPayments.account_records_id == None, BuildDecisionBookPayments.process_date_y == int(account_bank_date_year), BuildDecisionBookPayments.process_date_m == int(account_bank_date_month) ).count() print(f"Found {dated_payments} payments with date filters (year={account_bank_date_year}, month={account_bank_date_month})") else: print("No matching AccountRecord found for debugging!") # Query for account records that need payment processing # Note: We removed the approved_record condition as it was too restrictive account_records_list = AccountRecords.query.filter(AccountRecords.remainder_balance < AccountRecords.currency_value, AccountRecords.receive_debit == receive_enum.id ).order_by(AccountRecords.bank_date.desc()).all() print(f"\nProcessing {len(account_records_list)} account records") # Track how many records were processed with each method traditional_method_count = 0 direct_method_count = 0 for account_record in account_records_list: # Try the traditional method first current_currency_value = pay_the_registration(account_record, receive_enum, debit_enum, False, session) if current_currency_value > 0: # If first pass found payments, try with old records too pay_the_registration(account_record, receive_enum, debit_enum, True, session) traditional_method_count += 1 else: # If traditional method didn't work, try direct payment for all records # This will handle records with missing build_parts_id if create_direct_payment(account_record, receive_enum, session): direct_method_count += 1 if direct_method_count % 10 == 0: # Only print every 10th record to avoid too much output print(f"Direct payment created for account_record {account_record.id}") # Refresh the account record to get updated values session.refresh(account_record) # Update status if the remainder balance equals the currency value if abs(float(Decimal(account_record.remainder_balance))) == abs(float(Decimal(account_record.currency_value))): account_record.update(status_id=97) account_record.save() print(f"\nProcessed with traditional method: {traditional_method_count} records") print(f"Processed with direct payment method: {direct_method_count} records") print("Payment processing completed successfully") except Exception as e: print(f"Error in send_accounts_to_decision_payment: {e}") import traceback traceback.print_exc() # Rollback the session in case of error session.rollback() return if __name__ == "__main__": print("Payment Service is running...") try: send_accounts_to_decision_payment() # Print counter statistics print("\n--- Processing Statistics ---") print(f"Records where current_currency_value <= 0: {counter_current_currency_not_positive}") print(f"Records where net_amount <= 0: {counter_net_amount_not_positive}") print(f"Account records updated: {counter_account_records_updated}") print(f"Total payments found: {counter_payments_found}") print(f"Skips due to found_payment: {counter_found_payment_skips}") print(f"Payment exceptions: {counter_payment_exceptions}") except Exception as e: print(f"Error: {e}") print("Payment Service is finished...")