This commit is contained in:
2026-02-02 13:06:07 +05:30
commit 1b173f992a
41 changed files with 9380 additions and 0 deletions

147
processors/data_mapper.py Normal file
View File

@@ -0,0 +1,147 @@
#!/usr/bin/env python3
"""
Data mapper for field transformations.
Maps ACH parser output to database format.
"""
from datetime import datetime
from decimal import Decimal
from typing import Dict, Any
from logging_config import get_logger
from db.models import TransactionRecord
logger = get_logger(__name__)
class DataMapper:
"""Maps parsed ACH transactions to database records."""
@staticmethod
def convert_date(date_str: str) -> datetime.date:
"""
Convert ACH date string to Python date.
ACH format: DD/MM/YY (e.g., '19/01/26')
Args:
date_str: Date string in DD/MM/YY format
Returns:
datetime.date object
"""
try:
if not date_str or len(date_str) < 8:
raise ValueError(f"Invalid date format: {date_str}")
# Parse DD/MM/YY
parsed_date = datetime.strptime(date_str, '%d/%m/%y')
return parsed_date.date()
except Exception as e:
logger.error(f"Error converting date '{date_str}': {e}")
# Return today's date as fallback
return datetime.now().date()
@staticmethod
def calculate_txnind(amount_str: str) -> str:
"""
Calculate transaction indicator from amount.
Args:
amount_str: Amount as string
Returns:
'CR' for credit (>= 0), 'DR' for debit (< 0)
"""
try:
amount = Decimal(amount_str.strip())
return 'DR' if amount < 0 else 'CR'
except Exception as e:
logger.error(f"Error calculating TXNIND for amount '{amount_str}': {e}")
return 'CR' # Default to credit
@staticmethod
def convert_amount(amount_str: str) -> Decimal:
"""
Convert amount string to Decimal.
Args:
amount_str: Amount as string
Returns:
Decimal representation of amount
"""
try:
if not amount_str:
return Decimal('0')
amount = Decimal(amount_str.strip())
return abs(amount) # Store absolute value, use TXNIND for sign
except Exception as e:
logger.error(f"Error converting amount '{amount_str}': {e}")
return Decimal('0')
@classmethod
def map_transaction(
cls,
parsed_transaction: Dict[str, Any],
bankcode: str
) -> TransactionRecord:
"""
Map parsed transaction to database record.
Args:
parsed_transaction: Transaction from ACHParser
bankcode: Bank code for this transaction
Returns:
TransactionRecord ready for database insertion
"""
try:
amount_str = parsed_transaction.get('amount', '0')
amount = cls.convert_amount(amount_str)
txnind = cls.calculate_txnind(amount_str)
tran_date = cls.convert_date(parsed_transaction.get('date', ''))
record = TransactionRecord(
narration=parsed_transaction.get('remarks', '')[:500], # Limit to 500 chars
status=parsed_transaction.get('sys', ''),
bankcode=bankcode,
jrnl_id=parsed_transaction.get('jrnl_no', ''),
tran_date=tran_date,
cbs_acct=parsed_transaction.get('cust_acct', ''),
tran_amt=amount,
txnind=txnind,
)
return record
except Exception as e:
logger.error(f"Error mapping transaction: {e}", exc_info=True)
raise
@classmethod
def map_transactions(
cls,
parsed_transactions: list,
bankcode: str
) -> list:
"""
Map multiple transactions.
Args:
parsed_transactions: List of transactions from ACHParser
bankcode: Bank code for these transactions
Returns:
List of TransactionRecord objects
"""
records = []
for txn in parsed_transactions:
try:
record = cls.map_transaction(txn, bankcode)
records.append(record)
except Exception as e:
logger.warning(f"Skipping transaction due to error: {e}")
continue
logger.info(f"Mapped {len(records)} transactions for bank {bankcode}")
return records