first commit
This commit is contained in:
Binary file not shown.
Binary file not shown.
@@ -0,0 +1,38 @@
|
||||
import requests
|
||||
from logging_config import get_logger
|
||||
|
||||
logger = get_logger(__name__)
|
||||
|
||||
class FetchQueueClient:
|
||||
|
||||
def __init__(self, config):
|
||||
self.url = config.FETCH_QUEUE_URL
|
||||
|
||||
def fetch(self, payload):
|
||||
logger.info("API request payload: %s", payload)
|
||||
|
||||
try:
|
||||
response = requests.post(
|
||||
self.url,
|
||||
json=payload,
|
||||
verify=False,
|
||||
timeout=20
|
||||
)
|
||||
|
||||
logger.info("API status: %s", response.status_code)
|
||||
|
||||
if not response.ok:
|
||||
logger.error("API error response: %s", response.text)
|
||||
|
||||
response.raise_for_status()
|
||||
|
||||
result = response.json()
|
||||
|
||||
logger.debug("API full response: %s", result)
|
||||
|
||||
return result
|
||||
|
||||
except Exception:
|
||||
logger.exception("API call failed")
|
||||
raise
|
||||
``
|
||||
@@ -0,0 +1,32 @@
|
||||
import os
|
||||
|
||||
class Config:
|
||||
|
||||
BANK_CODE_MAPPING = {
|
||||
"9": "0015", "12": "0014", "13": "0016", "14": "0009",
|
||||
"15": "0005", "16": "0001", "7": "0013", "6": "0002",
|
||||
"5": "0007", "17": "0020", "1": "0012", "10": "0017",
|
||||
"11": "0018", "2": "0003", "4": "0004",
|
||||
"8": "0006", "3": "0021"
|
||||
}
|
||||
|
||||
FETCH_QUEUE_URL = "https://43.225.3.224:443/IPKS_Queue_Generation/fetchAllQueue"
|
||||
|
||||
def __init__(self):
|
||||
self.db_user = os.getenv("DB_USER", "")
|
||||
self.db_password = os.getenv("DB_PASSWORD", "")
|
||||
self.db_dsn = os.getenv("DB_DSN", "")
|
||||
|
||||
self.poll_interval_minutes = int(os.getenv("POLL_INTERVAL_MINUTES", "60"))
|
||||
self.bank_codes = self._parse_bank_codes()
|
||||
|
||||
def _parse_bank_codes(self):
|
||||
codes_str = os.getenv(
|
||||
"BANK_CODES",
|
||||
"0001,0002,0003,0004,0005,0006,0007,0009,0012,0013,0014,0015,0016,0017,0018,0020,0021"
|
||||
)
|
||||
return [c.strip() for c in codes_str.split(",") if c.strip()]
|
||||
|
||||
def validate(self):
|
||||
if not self.db_user or not self.db_password or not self.db_dsn:
|
||||
raise RuntimeError("Database configuration missing")
|
||||
Binary file not shown.
@@ -0,0 +1,87 @@
|
||||
from logging_config import get_logger
|
||||
|
||||
logger = get_logger(__name__)
|
||||
|
||||
class OracleDB:
|
||||
|
||||
def __init__(self, connector):
|
||||
self.connector = connector
|
||||
|
||||
def fetch_all(self, sql, params=None):
|
||||
conn = None
|
||||
cur = None
|
||||
try:
|
||||
conn = self.connector.get_connection()
|
||||
cur = conn.cursor()
|
||||
|
||||
cur.execute(sql, params or {})
|
||||
|
||||
cols = [c[0] for c in cur.description]
|
||||
rows = [dict(zip(cols, r)) for r in cur.fetchall()]
|
||||
|
||||
logger.info("Fetched %s rows from database", len(rows))
|
||||
|
||||
return rows
|
||||
|
||||
except Exception:
|
||||
logger.exception("Error in fetch_all")
|
||||
raise
|
||||
|
||||
finally:
|
||||
if cur:
|
||||
cur.close()
|
||||
if conn:
|
||||
conn.close()
|
||||
|
||||
def execute(self, sql, params):
|
||||
conn = None
|
||||
cur = None
|
||||
try:
|
||||
conn = self.connector.get_connection()
|
||||
cur = conn.cursor()
|
||||
|
||||
cur.execute(sql, params)
|
||||
conn.commit()
|
||||
|
||||
logger.info("Insert/Update executed successfully")
|
||||
|
||||
except Exception:
|
||||
if conn:
|
||||
conn.rollback()
|
||||
logger.exception("Error executing SQL")
|
||||
raise
|
||||
|
||||
finally:
|
||||
if cur:
|
||||
cur.close()
|
||||
if conn:
|
||||
conn.close()
|
||||
|
||||
def call_procedure(self, name):
|
||||
conn = None
|
||||
cur = None
|
||||
try:
|
||||
conn = self.connector.get_connection()
|
||||
cur = conn.cursor()
|
||||
|
||||
logger.info("Calling procedure %s", name)
|
||||
|
||||
try:
|
||||
cur.callproc(name)
|
||||
except Exception:
|
||||
cur.execute(f"BEGIN {name}; END;")
|
||||
|
||||
conn.commit()
|
||||
|
||||
logger.info("Procedure executed successfully")
|
||||
return True
|
||||
|
||||
except Exception:
|
||||
logger.exception("Procedure call failed")
|
||||
return False
|
||||
|
||||
finally:
|
||||
if cur:
|
||||
cur.close()
|
||||
if conn:
|
||||
conn.close()
|
||||
@@ -0,0 +1,23 @@
|
||||
import oracledb
|
||||
from logging_config import get_logger
|
||||
|
||||
logger = get_logger(__name__)
|
||||
|
||||
class OracleConnector:
|
||||
|
||||
def __init__(self, config):
|
||||
self.config = config
|
||||
self.pool = None
|
||||
|
||||
def get_connection(self):
|
||||
if not self.pool:
|
||||
logger.info("Initializing Oracle connection pool")
|
||||
self.pool = oracledb.create_pool(
|
||||
user=self.config.db_user,
|
||||
password=self.config.db_password,
|
||||
dsn=self.config.db_dsn,
|
||||
min=1,
|
||||
max=5,
|
||||
increment=1
|
||||
)
|
||||
return self.pool.acquire()
|
||||
@@ -0,0 +1,29 @@
|
||||
import logging
|
||||
import logging.handlers
|
||||
from pathlib import Path
|
||||
|
||||
def setup_logging():
|
||||
Path("logs").mkdir(exist_ok=True)
|
||||
|
||||
root_logger = logging.getLogger()
|
||||
root_logger.setLevel(logging.INFO)
|
||||
root_logger.handlers.clear()
|
||||
|
||||
formatter = logging.Formatter(
|
||||
"%(asctime)s %(levelname)s %(name)s - %(message)s"
|
||||
)
|
||||
|
||||
console = logging.StreamHandler()
|
||||
console.setFormatter(formatter)
|
||||
root_logger.addHandler(console)
|
||||
|
||||
file_handler = logging.handlers.RotatingFileHandler(
|
||||
"logs/application.log",
|
||||
maxBytes=10 * 1024 * 1024,
|
||||
backupCount=5
|
||||
)
|
||||
file_handler.setFormatter(formatter)
|
||||
root_logger.addHandler(file_handler)
|
||||
|
||||
def get_logger(name):
|
||||
return logging.getLogger(name)
|
||||
@@ -0,0 +1,27 @@
|
||||
from logging_config import setup_logging
|
||||
from config import Config
|
||||
from db.oracle_connector import OracleConnector
|
||||
from db.db import OracleDB
|
||||
from repository.repository import Repository
|
||||
from api.api_client import FetchQueueClient
|
||||
from processor.processor import Processor
|
||||
from scheduler.scheduler import Scheduler
|
||||
|
||||
def main():
|
||||
setup_logging()
|
||||
|
||||
config = Config()
|
||||
config.validate()
|
||||
|
||||
connector = OracleConnector(config)
|
||||
db = OracleDB(connector)
|
||||
repo = Repository(db)
|
||||
api = FetchQueueClient(config)
|
||||
|
||||
processor = Processor(config, repo, api)
|
||||
scheduler = Scheduler(processor, config)
|
||||
|
||||
scheduler.run()
|
||||
|
||||
if __name__ == "__main__":
|
||||
main()
|
||||
@@ -0,0 +1,7 @@
|
||||
class BankMapping:
|
||||
|
||||
def __init__(self, mapping):
|
||||
self.reverse = {v: k for k, v in mapping.items()}
|
||||
|
||||
def get_dccb(self, bank_code):
|
||||
return self.reverse.get(bank_code)
|
||||
@@ -0,0 +1,22 @@
|
||||
class OutwardLogModel:
|
||||
|
||||
def __init__(self, bank, r, api):
|
||||
self.bankcode = bank
|
||||
self.jrnl_id = api.get("journalId")
|
||||
self.ref_no = api.get("remarks")
|
||||
|
||||
self.tran_date = r["TXN_DATE"].strftime("%d%m%Y")
|
||||
self.txn_amt = float(r["TXN_AMT"])
|
||||
|
||||
self.recv_ac = r["DEST_AC_NO"]
|
||||
self.send_ac = api.get("accountNum")
|
||||
|
||||
self.ifsc = r["IFSC_CODE"]
|
||||
self.sender_name = r["REMITTER_NAME"]
|
||||
self.receiver_name = r["BENEFICIARY_NAME"]
|
||||
self.beneficiary_address = r["BENEFICIARY_ADD"]
|
||||
|
||||
self.sender_info = api.get("sendeReceiverInfo")
|
||||
|
||||
def to_dict(self):
|
||||
return self.__dict__
|
||||
@@ -0,0 +1,91 @@
|
||||
from logging_config import get_logger
|
||||
from models.bank_mapping import BankMapping
|
||||
from models.outward_log_model import OutwardLogModel
|
||||
|
||||
logger = get_logger(__name__)
|
||||
|
||||
class Processor:
|
||||
|
||||
def __init__(self, config, repo, api):
|
||||
self.config = config
|
||||
self.repo = repo
|
||||
self.api = api
|
||||
self.mapping = BankMapping(config.BANK_CODE_MAPPING)
|
||||
|
||||
def process(self):
|
||||
total_inserted = 0
|
||||
|
||||
logger.info("Starting processing...")
|
||||
|
||||
for bank_code in self.config.bank_codes:
|
||||
|
||||
try:
|
||||
logger.info("Processing bank %s", bank_code)
|
||||
|
||||
dccb = self.mapping.get_dccb(bank_code)
|
||||
|
||||
if not dccb:
|
||||
logger.error("No mapping for bank %s", bank_code)
|
||||
continue
|
||||
|
||||
records = self.repo.get_records(dccb)
|
||||
|
||||
if not records:
|
||||
logger.info("No records found for bank %s", bank_code)
|
||||
continue
|
||||
|
||||
payload = {
|
||||
"bankCode": bank_code,
|
||||
"rrn": "090686552",
|
||||
"branchCode": str(records[0]["BR_CODE"]).zfill(3),
|
||||
"itemType": 1,
|
||||
"queueType": "02",
|
||||
"reterieveTop": "20"
|
||||
}
|
||||
|
||||
response = self.api.fetch(payload)
|
||||
|
||||
if response.get("status") != "SUCCESS":
|
||||
logger.error("API error for bank %s", bank_code)
|
||||
continue
|
||||
|
||||
items = response.get("response", {}).get("VwQueueItems", [])
|
||||
|
||||
api_map = {
|
||||
(int(x["queueItemId"]), float(x["txnAmt"])): x
|
||||
for x in items
|
||||
}
|
||||
|
||||
for r in records:
|
||||
|
||||
key = (int(r["CBS_QUEUE_NO2"]), float(r["TXN_AMT"]))
|
||||
item = api_map.get(key)
|
||||
|
||||
if not item:
|
||||
continue
|
||||
|
||||
if item.get("status") != "Processed":
|
||||
continue
|
||||
|
||||
ref = item.get("remarks")
|
||||
|
||||
if not ref:
|
||||
continue
|
||||
|
||||
if self.repo.exists_by_ref(ref):
|
||||
logger.info("Duplicate ref %s", ref)
|
||||
continue
|
||||
|
||||
model = OutwardLogModel(bank_code, r, item)
|
||||
|
||||
self.repo.insert_outward_log(model.to_dict())
|
||||
total_inserted += 1
|
||||
|
||||
logger.info("Inserted ref %s", ref)
|
||||
|
||||
except Exception:
|
||||
logger.exception("Error processing bank %s", bank_code)
|
||||
|
||||
logger.info("Processing completed total inserted %s", total_inserted)
|
||||
|
||||
return total_inserted
|
||||
@@ -0,0 +1,50 @@
|
||||
from logging_config import get_logger
|
||||
|
||||
logger = get_logger(__name__)
|
||||
|
||||
class Repository:
|
||||
|
||||
def __init__(self, db):
|
||||
self.db = db
|
||||
|
||||
def get_records(self, dccb):
|
||||
logger.info("Fetching records for DCCB=%s", dccb)
|
||||
|
||||
return self.db.fetch_all("""
|
||||
SELECT TXN_DATE,DCCB_CODE,BR_CODE,SRC_AC_NO,DEST_AC_NO,
|
||||
IFSC_CODE,TXN_AMT,BENEFICIARY_NAME,
|
||||
BENEFICIARY_ADD,REMITTER_NAME,CBS_QUEUE_NO2
|
||||
FROM NEFT_RTGS_TXN_QUEUE
|
||||
WHERE DCCB_CODE = :d
|
||||
AND TRUNC(TXN_DATE)=TRUNC(SYSDATE)
|
||||
""", {"d": dccb})
|
||||
|
||||
def exists_by_ref(self, ref):
|
||||
logger.debug("Checking duplicate REF=%s", ref)
|
||||
|
||||
res = self.db.fetch_all(
|
||||
"SELECT 1 FROM outward_neft_api_log WHERE REF_NO=:r",
|
||||
{"r": ref}
|
||||
)
|
||||
|
||||
return len(res) > 0
|
||||
|
||||
def insert_outward_log(self, d):
|
||||
logger.debug("Insert data: %s", d)
|
||||
|
||||
self.db.execute("""
|
||||
INSERT INTO outward_neft_api_log(
|
||||
BANKCODE,TXNCODE,TXNIND,JRNL_ID,REF_NO,TRAN_DATE,
|
||||
TXN_AMT,RECVR_ACCT_NO,SENDER_ACCT_NO,
|
||||
RECIEVER_IFSC,SENDER_NAME,RECIEVER_NAME,
|
||||
BENEFICIARY_ADDRESS,SENDER_TO_RECIVER_INFO,MSG_TYPE
|
||||
)
|
||||
VALUES (
|
||||
:bankcode,'OUTWARD NEFT','DR',:jrnl_id,:ref_no,:tran_date,
|
||||
:txn_amt,:recv_ac,:send_ac,
|
||||
:ifsc,:sender_name,:receiver_name,
|
||||
:beneficiary_address,:sender_info,'N06')
|
||||
""", d)
|
||||
|
||||
def call_proc(self):
|
||||
return self.db.call_procedure("neft_api_txn_post")
|
||||
@@ -0,0 +1,2 @@
|
||||
oracledb==2.0.0
|
||||
requests
|
||||
@@ -0,0 +1,29 @@
|
||||
import time
|
||||
from logging_config import get_logger
|
||||
|
||||
logger = get_logger(__name__)
|
||||
|
||||
class Scheduler:
|
||||
|
||||
def __init__(self, processor, config):
|
||||
self.processor = processor
|
||||
self.config = config
|
||||
|
||||
def run(self):
|
||||
logger.info("Scheduler started")
|
||||
|
||||
while True:
|
||||
try:
|
||||
count = self.processor.process()
|
||||
|
||||
logger.info("Inserted count %s", count)
|
||||
|
||||
if count > 0:
|
||||
logger.info("Calling procedure")
|
||||
self.processor.repo.call_proc()
|
||||
|
||||
except Exception:
|
||||
logger.exception("Scheduler error")
|
||||
|
||||
logger.info("Sleeping for next cycle")
|
||||
time.sleep(self.config.poll_interval_minutes * 60)
|
||||
Reference in New Issue
Block a user