#!/usr/bin/env python3 """ Data access layer for NEFT 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 NEFTInwardRecord, ProcessedFile logger = get_logger(__name__) class Repository: """Data access layer for NEFT inward processing.""" def __init__(self): """Initialize repository with connector.""" self.connector = get_connector() # --------------------------------------------------------- # ADDED: Account validation using last 12 digits of RECVR_ACCT_NO # --------------------------------------------------------- def validate_account_exists(self, account_number: str) -> bool: """ Validate if account number exists in dep_account table. Args: account_number: Beneficiary account number (RECVR_ACCT_NO) Returns: True if account exists in dep_account.link_accno, False otherwise """ 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: bulk_insert_transactions WITH VALIDATION # --------------------------------------------------------- def bulk_insert_transactions(self, transactions: List[NEFTInwardRecord]) -> tuple: """ Bulk insert NEFT transactions into inward_neft_api_log. Records with invalid beneficiary account numbers are skipped. Args: transactions: List of NEFTOutwardRecord objects Returns: (inserted_count, skipped_count) """ if not transactions: logger.warning("No transactions to insert") return 0, 0 valid_transactions = [] skipped_count = 0 for txn in transactions: acct = txn.sender_acct_no if self.validate_account_exists(acct): valid_transactions.append(txn) else: skipped_count += 1 if not valid_transactions: logger.debug(f"All {skipped_count} transactions skipped (invalid Remitter accounts)") 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 outward_neft_api_log ( TXNIND, JRNL_ID, BANKCODE, REF_NO, TRAN_DATE, TXN_AMT, SENDER_IFSC, RECIEVER_IFSC, SENDER_ACCT_NO, SENDER_NAME, RECVR_ACCT_NO, RECIEVER_NAME, REJECT_CODE, REJECT_REASON, BENEFICIARY_ADDRESS, MSG_TYPE ) VALUES ( :TXNIND, :JRNL_ID, :BANKCODE, :REF_NO, :TRAN_DATE, :TXN_AMT, :SENDER_IFSC, :RECIEVER_IFSC, :SENDER_ACCT_NO, :SENDER_NAME, :RECVR_ACCT_NO, :RECIEVER_NAME, :REJECT_CODE, :REJECT_REASON, :BENEFICIARY_ADDRESS, :MSG_TYPE ) """ cursor.executemany(insert_sql, batch_data) conn.commit() inserted_count = len(valid_transactions) logger.info(f"Inserted {inserted_count} NEFT transactions into outward_neft_api_log") return inserted_count, skipped_count except Exception as e: if conn: conn.rollback() logger.error(f"Error inserting NEFT transactions: {e}", exc_info=True) raise finally: if cursor: cursor.close() conn.close() # --------------------------------------------------------- # NOTHING ELSE BELOW THIS LINE WAS TOUCHED # --------------------------------------------------------- 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 neft_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 neft_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 neft_processed_files WHERE bankcode = :bankcode ORDER BY processed_at DESC """, {'bankcode': bankcode} ) else: cursor.execute( """ SELECT filename FROM neft_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_neft_api_log WHERE ROWNUM = 1") logger.info("✓ inward_neft_api_log table exists") except Exception as e: logger.error(f"✗ inward_neft_api_log table not found: {e}") raise SystemExit( "FATAL: inward_neft_api_log table must be created manually before running this application" ) try: cursor.execute("SELECT COUNT(*) FROM neft_processed_files WHERE ROWNUM = 1") logger.info("✓ neft_processed_files table exists") except Exception as e: logger.error(f"✗ neft_processed_files table not found: {e}") raise SystemExit( "FATAL: neft_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()