#!/usr/bin/env python3 """ Data access layer for UPI inward file processing. Handles CRUD operations and transaction management. """ from typing import List, Optional from logging_config import get_logger from .oracle_connector import get_connector from .models import UPIInwardRecord, ProcessedFile logger = get_logger(__name__) class Repository: """Data access layer for UPI inward processing.""" def __init__(self): self.connector = get_connector() # --------------------------------------------------------- # Account validation (same as NEFT) # --------------------------------------------------------- def validate_account_exists(self, account_number: str) -> bool: if not account_number: return False last12 = str(account_number)[-12:] conn = self.connector.get_connection() try: cursor = conn.cursor() cursor.execute( "SELECT COUNT(*) FROM dep_account WHERE link_accno = :accno", {'accno': last12} ) count = cursor.fetchone()[0] return count > 0 except Exception as e: logger.warning(f"Error validating account {account_number}: {e}") return False finally: cursor.close() conn.close() # --------------------------------------------------------- # UPDATED: UPI BULK INSERT WITH VALIDATION # --------------------------------------------------------- def bulk_insert_transactions(self, transactions: List[UPIInwardRecord]) -> tuple: if not transactions: logger.warning("No transactions to insert") return 0, 0 valid_transactions = [] skipped_count = 0 for txn in transactions: acct = txn.credit_account # Condition 1: account exists account_valid = self.validate_account_exists(acct) # Condition 2: only CREDIT transactions is_credit = (txn.txn_type == "CREDIT") if account_valid and is_credit: # Force DB value to CR txn.txn_type = "CR" valid_transactions.append(txn) else: skipped_count += 1 if not valid_transactions: logger.info(f"All {skipped_count} transactions skipped") return 0, skipped_count conn = self.connector.get_connection() cursor = None try: cursor = conn.cursor() batch_data = [txn.to_dict() for txn in valid_transactions] logger.info(batch_data) insert_sql = """ INSERT INTO pacs_db.inward_upi_api_log ( BANKCODE, RRN, TXN_DATE, TXN_AMT, TXN_TYPE, BENEFICIARY_ACCOUNT, STATUS, NARRATION ) VALUES ( :BANKCODE, :RRN, :TXN_DATE, :TXN_AMT, :TXN_TYPE, :BENEFICIARY_ACCOUNT, :STATUS, :NARRATION ) """ cursor.executemany(insert_sql, batch_data) logger.info(f"executemany rowcount: {cursor.rowcount}") conn.commit() logger.info("UPI transactions committed successfully") inserted_count = len(valid_transactions) logger.info(f"Inserted {inserted_count} UPI transactions") return inserted_count, skipped_count except Exception as e: if conn: conn.rollback() logger.error(f"Error inserting UPI transactions: {e}", exc_info=True) raise finally: if cursor: cursor.close() conn.close() # --------------------------------------------------------- # FILE TRACKING (UNCHANGED) # --------------------------------------------------------- def is_file_processed(self, filename: str, bankcode: str) -> bool: conn = self.connector.get_connection() cursor = None try: cursor = conn.cursor() cursor.execute( """ SELECT COUNT(*) FROM upi_processed_files WHERE filename = :filename AND bankcode = :bankcode """, {'filename': filename, 'bankcode': bankcode} ) count = cursor.fetchone()[0] return count > 0 except Exception as e: logger.error(f"Error checking processed file: {e}", exc_info=True) return False finally: if cursor: cursor.close() conn.close() def mark_file_processed(self, processed_file: ProcessedFile) -> bool: conn = self.connector.get_connection() cursor = None try: cursor = conn.cursor() file_data = processed_file.to_dict() insert_sql = """ INSERT INTO upi_processed_files ( filename, bankcode, file_path, transaction_count, status, error_message, processed_at ) VALUES ( :filename, :bankcode, :file_path, :transaction_count, :status, :error_message, :processed_at ) """ cursor.execute(insert_sql, file_data) conn.commit() logger.info(f"Marked file as processed: {processed_file.filename}") return True except Exception as e: if conn: conn.rollback() logger.error(f"Error marking file as processed: {e}", exc_info=True) return False finally: if cursor: cursor.close() conn.close() def get_processed_files(self, bankcode: Optional[str] = None) -> List[str]: conn = self.connector.get_connection() cursor = None try: cursor = conn.cursor() if bankcode: cursor.execute( """ SELECT filename FROM upi_processed_files WHERE bankcode = :bankcode ORDER BY processed_at DESC """, {'bankcode': bankcode} ) else: cursor.execute( """ SELECT filename FROM upi_processed_files ORDER BY processed_at DESC """ ) filenames = [row[0] for row in cursor.fetchall()] return filenames except Exception as e: logger.error(f"Error retrieving processed files: {e}", exc_info=True) return [] finally: if cursor: cursor.close() conn.close() # def call_neft_api_txn_post(self) -> bool: # conn = self.connector.get_connection() # cursor = None # try: # cursor = conn.cursor() # logger.info("Calling neft_api_txn_post procedure to process all inserted transactions...") # try: # cursor.callproc('neft_api_txn_post') # except Exception: # cursor.execute("BEGIN neft_api_txn_post; END;") # conn.commit() # logger.info("neft_api_txn_post procedure executed successfully") # return True # except Exception as e: # logger.error(f"Error calling neft_api_txn_post procedure: {e}", exc_info=True) # return False # finally: # if cursor: # cursor.close() # conn.close() def verify_tables_exist(self): conn = self.connector.get_connection() cursor = None try: cursor = conn.cursor() try: cursor.execute("SELECT COUNT(*) FROM inward_upi_api_log WHERE ROWNUM = 1") logger.info("✓ inward_upi_api_log table exists") except Exception as e: logger.error(f"✗ inward_upi_api_log table not found: {e}") raise SystemExit( "FATAL: inward_upi_api_log table must be created manually before running this application" ) try: cursor.execute("SELECT COUNT(*) FROM upi_processed_files WHERE ROWNUM = 1") logger.info("✓ upi_processed_files table exists") except Exception as e: logger.error(f"✗ upi_processed_files table not found: {e}") raise SystemExit( "FATAL: upi_processed_files table must be created manually before running this application" ) logger.info("Database tables verified successfully") except SystemExit: raise except Exception as e: logger.error(f"Error verifying tables: {e}", exc_info=True) raise SystemExit(f"FATAL: Error verifying database tables: {e}") finally: if cursor: cursor.close() conn.close()