300 lines
8.7 KiB
Python
300 lines
8.7 KiB
Python
#!/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:
|
|
|
|
conn = self.connector.get_connection()
|
|
cursor = None
|
|
try:
|
|
cursor = conn.cursor()
|
|
|
|
acc = account_number[-12:]
|
|
if acc.startswith('0'):
|
|
acc = acc[1:]
|
|
|
|
cursor.execute(
|
|
"SELECT COUNT(*) FROM dep_account WHERE link_accno = :accno",
|
|
{'accno': acc}
|
|
)
|
|
|
|
count = cursor.fetchone()[0]
|
|
return count > 0
|
|
|
|
except Exception as e:
|
|
logger.warning(f"Error validating account {account_number}: {e}")
|
|
return False
|
|
finally:
|
|
if cursor:
|
|
cursor.close()
|
|
conn.close()
|
|
|
|
# ---------------------------------------------------------
|
|
# 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
|
|
|
|
account_valid = self.validate_account_exists(acct)
|
|
is_credit = (txn.txn_type == "CREDIT")
|
|
|
|
if account_valid and is_credit:
|
|
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
|
|
# ---------------------------------------------------------
|
|
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
|
|
"""
|
|
)
|
|
|
|
return [row[0] for row in cursor.fetchall()]
|
|
|
|
except Exception as e:
|
|
logger.error(f"Error retrieving processed files: {e}", exc_info=True)
|
|
return []
|
|
|
|
finally:
|
|
if cursor:
|
|
cursor.close()
|
|
conn.close()
|
|
|
|
# ---------------------------------------------------------
|
|
# CALL STORED PROCEDURE
|
|
# ---------------------------------------------------------
|
|
def call_upi_api_txn_post(self) -> bool:
|
|
|
|
conn = self.connector.get_connection()
|
|
cursor = None
|
|
|
|
try:
|
|
cursor = conn.cursor()
|
|
logger.info("Calling upi_api_txn_post procedure...")
|
|
|
|
try:
|
|
cursor.callproc('PACS_DB.UPI_API_TXN_POST')
|
|
except Exception:
|
|
cursor.execute("BEGIN PACS_DB.UPI_API_TXN_POST; END;")
|
|
|
|
conn.commit()
|
|
|
|
logger.info("upi_api_txn_post executed successfully")
|
|
return True
|
|
|
|
except Exception as e:
|
|
logger.error(f"Error calling procedure: {e}", exc_info=True)
|
|
return False
|
|
|
|
finally:
|
|
if cursor:
|
|
cursor.close()
|
|
conn.close()
|
|
|
|
# ---------------------------------------------------------
|
|
# VERIFY TABLES
|
|
# ---------------------------------------------------------
|
|
def verify_tables_exist(self):
|
|
|
|
conn = self.connector.get_connection()
|
|
cursor = None
|
|
|
|
try:
|
|
cursor = conn.cursor()
|
|
|
|
try:
|
|
cursor.execute("SELECT 1 FROM inward_upi_api_log WHERE ROWNUM = 1")
|
|
logger.info("inward_upi_api_log exists")
|
|
except Exception as e:
|
|
raise SystemExit(
|
|
"FATAL: inward_upi_api_log table missing"
|
|
)
|
|
|
|
try:
|
|
cursor.execute("SELECT 1 FROM upi_processed_files WHERE ROWNUM = 1")
|
|
logger.info("upi_processed_files exists")
|
|
except Exception as e:
|
|
raise SystemExit(
|
|
"FATAL: upi_processed_files table missing"
|
|
)
|
|
|
|
finally:
|
|
if cursor:
|
|
cursor.close()
|
|
conn.close()
|