/*==========================================================================*/ /* RECONCILE.pc - 대사(對査) 온라인 서비스 (As-Is 원본, Oracle Pro*C) */ /*--------------------------------------------------------------------------*/ /* 대상 : BC 매입시스템 / Tuxedo ATMI 서비스 RECONCILE */ /* DBMS : Oracle 19c 전처리 : Pro*C/C++ (proc) */ /* 기능 : 당일 매입 vs 원장 대사 — 커서순회 매칭분류 + 예외적재 + 정산연계. */ /* ※ 이 파일은 **As-Is 역사 원본**이다. 현행 오픈스택 이식본은 */ /* app/src/rc/svc/RECONCILE.pgc (ECPG/PostgreSQL). 컴파일 제외. */ /*--------------------------------------------------------------------------*/ /* 변경이력 */ /* 2001.07.16 SEO K.T 최초작성 (일 매입-원장 대사) */ /* 2005.12.02 HAN Y.J 허용오차(tolerance) 매칭 도입, DECODE 등급 */ /* 2009.04.27 LEE S.W 조직/코드 트리 CONNECT BY 집계 추가 */ /* 2015.10.13 JANG M.K 예외원장(rc_exception) 분리, 감사로그 표준화 */ /* 2019.09.01 CHOI H.W Oracle 11g -> 19c, 커서/시퀀스 정비 */ /*==========================================================================*/ #include #include #include #include #include EXEC SQL INCLUDE sqlca; EXEC SQL INCLUDE oraca; EXEC ORACLE OPTION (ORACA=YES); #include "rcfld.h" EXEC SQL BEGIN DECLARE SECTION; VARCHAR h_bizdate[9]; /* YYYYMMDD */ VARCHAR h_merch[64]; VARCHAR h_status[16]; VARCHAR h_pstat[2]; VARCHAR h_orgcode[16]; /* 조직/코드 트리 루트 */ VARCHAR h_orgname[64]; long h_pid; long h_amt; long h_fee; long h_net; long h_act; long h_exp; long h_gross; long h_cnt; long h_mcnt; long h_ecnt; long h_mdr_bps; long h_limit; long h_led; long h_lvl; /* 코드트리 계층레벨 */ long h_orgsum; EXEC SQL END DECLARE SECTION; /*--------------------------------------------------------------------------*/ /* RECONCILE - Tuxedo 서비스 엔트리 */ /*--------------------------------------------------------------------------*/ void RECONCILE(TPSVCINFO *rqst) { FBFR32 *fb = (FBFR32 *)rqst->data; char bizdate[9], merch[64]; long tol = 0, rc_code = 0, adiff = 0; long scan_rows = 0, matched = 0, unmatched = 0, unmatched_amt = 0; userlog("RECONCILE 진입: 당일 매입-원장 대사"); EXEC SQL WHENEVER SQLERROR GOTO sql_error; /* ---- 1) 입력 검증 ------------------------------------------------ */ Fget(fb, RC_BIZDATE, 0, bizdate, (FLDLEN32 *)NULL); if (strlen(bizdate) < 8) { EXEC SQL SELECT TO_CHAR(SYSDATE,'YYYYMMDD') INTO :h_bizdate FROM DUAL; strcpy(bizdate, (char *)h_bizdate.arr); } strcpy((char *)h_bizdate.arr, bizdate); h_bizdate.len = strlen(bizdate); h_pid = Fvall(fb, RC_PURCHASE_ID, 0); h_net = Fvall(fb, RC_NET, 0); /* ---- 2) 마스터/정책 조회 (미커밋 매입은 전문값, 외부조인 (+)) ---- */ EXEC SQL SELECT NVL(p.amount,0), NVL(p.fee,0), NVL(p.net,:h_net), NVL(p.status,'A'), NVL(m.merchant_id, ' '), NVL(m.mdr_bps, 25), NVL(m.daily_limit, 100000000) INTO :h_amt, :h_fee, :h_net, :h_pstat, :h_merch, :h_mdr_bps, :h_limit FROM purchase p, merchant m WHERE p.purchase_id = :h_pid AND m.merchant_id (+) = p.merchant_id; if (sqlca.sqlcode == 1403) { h_amt = Fvall(fb, RC_AMOUNT, 0); h_fee = Fvall(fb, RC_FEE, 0); h_net = Fvall(fb, RC_NET, 0); Fget(fb, RC_MERCHANT, 0, merch, (FLDLEN32 *)NULL); strcpy((char *)h_merch.arr, merch); h_merch.len = strlen(merch); h_mdr_bps = 25; h_limit = 100000000; userlog("RECONCILE: 체인 신규매입(미커밋) pid=%ld — 전문값 대사", h_pid); } /* ---- 3) 당일 매입/대사로그/예외 집계 (NVL 널가드) --------------- */ EXEC SQL SELECT COUNT(*), NVL(SUM(amount),0) INTO :h_cnt, :h_gross FROM purchase WHERE biz_date = TO_DATE(:h_bizdate,'YYYYMMDD'); EXEC SQL SELECT COUNT(*) INTO :h_mcnt FROM rc_match_log WHERE biz_date = TO_DATE(:h_bizdate,'YYYYMMDD') AND status = 'MATCHED'; EXEC SQL SELECT COUNT(*) INTO :h_ecnt FROM rc_exception WHERE biz_date = TO_DATE(:h_bizdate,'YYYYMMDD') AND resolved = 'N'; /* ---- 4) 거래집합 커서순회 + 허용오차 매칭분류 ------------------- */ EXEC SQL DECLARE cur_reconcile CURSOR FOR SELECT p.purchase_id, NVL(p.net,0) FROM purchase p WHERE p.biz_date = TO_DATE(:h_bizdate,'YYYYMMDD') ORDER BY p.purchase_id; EXEC SQL OPEN cur_reconcile; for (;;) { EXEC SQL WHENEVER NOTFOUND DO break; EXEC SQL FETCH cur_reconcile INTO :h_pid, :h_exp; EXEC SQL SELECT NVL(SUM(amount),0) INTO :h_act FROM ledger WHERE purchase_id = :h_pid AND entry_type IN ('RECON','SETTLE'); adiff = h_exp - h_act; if (adiff < 0) adiff = -adiff; scan_rows++; if (adiff <= (tol > 0 ? tol : 0)) matched++; else { unmatched++; unmatched_amt += adiff; } } EXEC SQL WHENEVER NOTFOUND CONTINUE; EXEC SQL CLOSE cur_reconcile; userlog("RECONCILE 대사스캔: scan=%ld match=%ld unmatch=%ld", scan_rows, matched, unmatched); /* ---- 5) 조직/코드 트리 집계 (계층질의 CONNECT BY PRIOR) --------- */ /* 가맹점이 속한 정산조직 코드트리를 루트→하위로 전개하여 소속 조직의 */ /* 당일 정산 순액 합계를 취득한다(대사 대상 조직 범위 확정용). */ EXEC SQL SELECT NVL(org_code,'ROOT'), NVL(org_name,'-') INTO :h_orgcode, :h_orgname FROM rc_org_map WHERE merchant_id = :h_merch AND ROWNUM = 1; if (sqlca.sqlcode == 1403) { strcpy((char *)h_orgcode.arr, "ROOT"); h_orgcode.len = 4; } EXEC SQL SELECT NVL(SUM(s.net),0), NVL(MAX(LEVEL),1) INTO :h_orgsum, :h_lvl FROM settlement s, rc_org o WHERE s.biz_date = TO_DATE(:h_bizdate,'YYYYMMDD') AND s.org_code = o.org_code AND o.org_code IN ( SELECT org_code FROM rc_org START WITH org_code = :h_orgcode CONNECT BY PRIOR org_code = parent_code); userlog("RECONCILE 코드트리: org=%.8s depth=%ld 정산합=%ld", h_orgcode.arr, h_lvl, h_orgsum); /* ---- 6) 업무규칙 분기 (매칭율 기반 상태판정, DECODE) ------------- */ if (h_gross <= 0 && scan_rows == 0) { rc_code = 0; strcpy((char *)h_status.arr, "NODATA"); } else if (unmatched == 0) { rc_code = 0; strcpy((char *)h_status.arr, "MATCHED"); } else if (unmatched * 100 <= (scan_rows > 0 ? scan_rows : 1) * 5) { rc_code = 1; strcpy((char *)h_status.arr, "TOLERATED"); } else { rc_code = 2; strcpy((char *)h_status.arr, "EXCEPTION"); } h_status.len = strlen((char *)h_status.arr); /* 감사 심각도 라벨 (DECODE 코드분기) */ EXEC SQL SELECT DECODE(:rc_code, 0,'I', 1,'W', 2,'E', 'X') INTO :h_pstat FROM DUAL; /* ---- 7) 본처리: RECON 원장 적재 (.NEXTVAL 채번) ---------------- */ EXEC SQL SELECT ledger_seq.NEXTVAL INTO :h_led FROM DUAL; EXEC SQL INSERT INTO ledger (ledger_id, purchase_id, entry_type, amount, biz_date, posted_at) VALUES (:h_led, :h_pid, 'RECON', :h_net, TO_DATE(:h_bizdate,'YYYYMMDD'), SYSDATE); /* ---- 8) 불일치 예외 적재 + 감사로그 (PL/SQL 대사 패키지) -------- */ if (rc_code >= 2) { EXEC SQL INSERT INTO rc_exception (exc_id, biz_date, merchant_id, unmatch_cnt, unmatch_amt, resolved, created_at) VALUES (rc_exception_seq.NEXTVAL, TO_DATE(:h_bizdate,'YYYYMMDD'), :h_merch, :unmatched, :unmatched_amt, 'N', SYSDATE); } EXEC SQL EXECUTE BEGIN pkg_settle.recon_audit(:h_bizdate, :h_merch, :scan_rows, :matched, :unmatched, :rc_code); END; END-EXEC; EXEC SQL COMMIT WORK; /* ---- 9) 응답부 ------------------------------------------------- */ Fchg(fb, RC_STATUS, 0, (char *)h_status.arr, 0L); Fchg(fb, RC_MATCHED, 0, (char *)&matched, 0L); Fchg(fb, RC_UNMATCHED, 0, (char *)&unmatched, 0L); Fchg(fb, RC_RC, 0, (char *)&rc_code, 0L); userlog("RECONCILE 완료: biz=%.8s status=%.10s rc=%ld orgsum=%ld", h_bizdate.arr, h_status.arr, rc_code, h_orgsum); tpreturn(TPSUCCESS, 0, (char *)fb, 0L, 0L); return; sql_error: userlog("RECONCILE SQL 오류: %.70s (sqlcode=%ld)", sqlca.sqlerrm.sqlerrmc, sqlca.sqlcode); EXEC SQL WHENEVER SQLERROR CONTINUE; EXEC SQL ROLLBACK WORK; Fchg(fb, RC_RC, 0, (char *)"E900", 0L); tpreturn(TPFAIL, 0, (char *)fb, 0L, 0L); return; }