Wave H: Oracle 계보(As-Is) 심기 + 시스템 조망 재편(운영 콘솔)

■ Oracle 계보 — "원본은 Oracle 19c + Pro*C 였다"
현행 코드(ECPG/PostgreSQL)가 Oracle 시스템의 1차 이관본임을 콘크리트하게 보존.
- legacy-oracle/proc/*.pc (7본): ACQUIRE/AUTH_APPROVE/ST_MDR/SETTLE/RECONCILE/
  CL_DAILY/PAY_FILEGEN. 순수 Oracle Pro*C 방언 — sqlca/oraca, VARCHAR,
  WHENEVER SQLERROR GOTO, NVL/DECODE/SYSDATE/DUAL/ROWNUM/(+)/CONNECT BY/
  .NEXTVAL/TO_DATE, PL/SQL 패키지 호출, COMMIT WORK. 컴파일 대상 아님(As-Is 원본).
- legacy-oracle/plsql/*.sql (2본): PKG_SETTLE(정산), PKG_LEDGER(복식부기 기표).
  VARCHAR2/%TYPE/%ROWTYPE/CURSOR/EXCEPTION/RAISE_APPLICATION_ERROR.
- docs/ORACLE_PROVENANCE.md: As-Is(Oracle)→현행(ECPG)→To-Be(Spring Boot) 3단 계보
  + Oracle→PostgreSQL 방언 이관 매핑 15종 + "왜 DB는 PostgreSQL인가".
- gen_metrics.py: legacy-oracle 실측(원본 9파일 1,827LOC, 방언 12종 히트) →
  마이그레이션 난이도 화면에 "④ Oracle 계보" 패널 추가.

■ 시스템 조망 재편 — 실제 매입시스템 운영 콘솔
"마이그레이션 난이도"(Forge 관점 메타)를 시스템 조망에서 분리하고, 시스템 자체의
상태·아키텍처를 보여주는 운영 화면 신설.
- 신규 "시스템 현황"(#sys): 헬스 KPI(서버11/서비스1466/미결2PC/당일매입/채널UP/
  큐적체/오류) + 아키텍처 토폴로지(채널·카드망→전문GW→승인·매입→정산·지급→
  원장·마감·대사·검증→마스터·공통 계층도) + 채널상태 + 큐적체 + 처리량·오류율
  + 정산사이클(마감·순액정산·클리어링·프레젠트먼트). 전부 실측 조회.
- nav 재편: [시스템 조망]=시스템현황·아키텍처토폴로지·정산경제,
  [전환 분석(Forge)]=마이그레이션난이도.
- 토폴로지 모듈박스 렌더 버그(return ASI) 수정.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This commit is contained in:
hyeongwoo 2026-07-23 12:10:37 +09:00
parent 2ac4c3c920
commit e1729d6edc
14 changed files with 2054 additions and 7 deletions

View file

@ -0,0 +1,230 @@
/*==========================================================================*/
/* CL_DAILY.pc - 일마감 배치 (As-Is 원본, Oracle Pro*C) */
/*--------------------------------------------------------------------------*/
/* 대상 : BC 매입시스템 / 일일 마감 배치 CLDAILY (야간 JOB) */
/* DBMS : Oracle 19c 전처리 : Pro*C/C++ (proc) */
/* 실행 : cldaily <YYYYMMDD> (인자 생략시 전영업일 = SYSDATE-1) */
/* ※ 이 파일은 **As-Is 역사 원본**이다. 현행 오픈스택 이식본은 */
/* app/src/cl/svc/CL_DAILY.pgc (ECPG/PostgreSQL, Tuxedo 서비스화). */
/* 원본은 순수 배치(main+커서루프) 였다. 컴파일 대상 아님(빌드 제외). */
/*--------------------------------------------------------------------------*/
/* 변경이력 */
/* 1999.02.15 KIM K.H 최초작성 (가맹점별 당일 매입 집계/마감이력) */
/* 2003.10.07 PARK J.S 기간잠금(cl_period_lock) 선점검 추가 */
/* 2009.05.22 LEE S.W 보류(H)/미매칭 잔존시 경고후 마감강행 정책 반영 */
/* 2015.08.11 JUNG M.K 마감 규모별 심각도(sev) 분류, cm_log 요약적재 */
/* 2019.09.01 CHOI H.W Oracle 11g -> 19c, CHAR->VARCHAR2 정비 */
/*==========================================================================*/
#include <stdio.h>
#include <string.h>
#include <stdlib.h>
EXEC SQL INCLUDE sqlca;
EXEC SQL INCLUDE oraca;
EXEC ORACLE OPTION (ORACA=YES);
EXEC SQL BEGIN DECLARE SECTION;
VARCHAR h_user[32];
VARCHAR h_pass[32];
VARCHAR h_dbstr[32];
VARCHAR h_bizdate[9]; /* YYYYMMDD */
VARCHAR h_merch[16];
VARCHAR h_mname[64];
VARCHAR h_status[2];
VARCHAR h_zmsg[64];
long h_locked;
long h_pend;
long h_unmatch;
long h_prevcnt;
long h_cid;
long h_txncnt;
long h_gross;
long h_fee;
long h_net;
long h_totcnt;
long h_totamt;
long h_nmerch;
long h_zsev;
EXEC SQL END DECLARE SECTION;
/* 가맹점별 당일 확정매입 커서 (취소 C 제외) — 마감 대상 스윕 */
EXEC SQL DECLARE cur_merch CURSOR FOR
SELECT m.merchant_id,
NVL(m.name,'(무명)'),
NVL(m.status,'N'),
COUNT(p.purchase_id),
NVL(SUM(p.amount), 0),
NVL(SUM(p.fee), 0),
NVL(SUM(p.net), 0)
FROM merchant m, purchase p
WHERE p.merchant_id (+) = m.merchant_id
AND p.biz_date = TO_DATE(:h_bizdate,'YYYYMMDD')
AND NVL(p.status,'A') <> 'C'
GROUP BY m.merchant_id, m.name, m.status
HAVING COUNT(p.purchase_id) > 0
ORDER BY m.merchant_id;
static void sql_error(const char *stage);
int main(int argc, char **argv)
{
int done = 0;
/* ---- 0) 접속 (배치는 자기 세션으로 CONNECT) ---------------------- */
strcpy((char *)h_user.arr, "clbatch"); h_user.len = strlen((char *)h_user.arr);
strcpy((char *)h_pass.arr, "clbatch"); h_pass.len = strlen((char *)h_pass.arr);
strcpy((char *)h_dbstr.arr, "ACQPRD"); h_dbstr.len = strlen((char *)h_dbstr.arr);
EXEC SQL WHENEVER SQLERROR GOTO connect_err;
EXEC SQL CONNECT :h_user IDENTIFIED BY :h_pass USING :h_dbstr;
/* 이후 오류는 표준 라벨분기 */
EXEC SQL WHENEVER SQLERROR GOTO sqlfail;
/* ---- 1) 영업일 결정 (인자 없으면 전영업일 SYSDATE-1) ------------- */
if (argc >= 2) {
strncpy((char *)h_bizdate.arr, argv[1], 8);
h_bizdate.arr[8] = '\0';
h_bizdate.len = strlen((char *)h_bizdate.arr);
} else {
EXEC SQL SELECT TO_CHAR(SYSDATE-1,'YYYYMMDD') INTO :h_bizdate FROM DUAL;
}
printf("[CL_DAILY] 일마감 개시 bizdate=%.8s\n", h_bizdate.arr);
/* ---- 2) 기간 잠금 선점검 (잠기면 즉시 중단) --------------------- */
EXEC SQL SELECT COUNT(*) INTO :h_locked
FROM cl_period_lock
WHERE period_key = :h_bizdate
AND locked = 'Y';
if (h_locked > 0) {
fprintf(stderr, "[CL_DAILY] 기간잠금 상태 - 일마감 불가 bizdate=%.8s\n",
h_bizdate.arr);
EXEC SQL ROLLBACK WORK RELEASE;
return 4;
}
/* ---- 3) 사전상태 점검 (보류/미매칭/기마감) — 경고후 강행 -------- */
EXEC SQL SELECT COUNT(*) INTO :h_pend
FROM purchase
WHERE biz_date = TO_DATE(:h_bizdate,'YYYYMMDD')
AND status = 'H';
EXEC SQL SELECT NVL(COUNT(*),0) INTO :h_unmatch
FROM ac_unmatch
WHERE biz_date = TO_DATE(:h_bizdate,'YYYYMMDD');
EXEC SQL SELECT COUNT(*) INTO :h_prevcnt
FROM cl_close_log
WHERE close_type = 'DAILY'
AND period_key = :h_bizdate
AND status <> 'X';
if (h_pend > 0)
printf("[CL_DAILY] 경고: 보류(H) %ld건 잔존 - 마감 진행\n", h_pend);
if (h_unmatch > 0)
printf("[CL_DAILY] 경고: 미매칭 %ld건 잔존 - 마감 진행\n", h_unmatch);
if (h_prevcnt > 0)
printf("[CL_DAILY] 기마감 %ld건 존재 - 신규 마감이력 추가생성\n", h_prevcnt);
/* ---- 4) 마감헤더 채번 (시퀀스 .NEXTVAL) ------------------------ */
EXEC SQL SELECT cl_close_seq.NEXTVAL INTO :h_cid FROM DUAL;
EXEC SQL
INSERT INTO cl_close_log
(close_id, close_type, period_key, status,
txn_count, total_amount, opened_at)
VALUES (:h_cid, 'DAILY', :h_bizdate, 'O',
0, 0, SYSDATE);
/* ---- 5) 본처리: 가맹점별 집계 커서 루프 ------------------------ */
h_totcnt = 0; h_totamt = 0; h_nmerch = 0;
EXEC SQL OPEN cur_merch;
for (;;) {
EXEC SQL WHENEVER NOTFOUND DO done = 1;
EXEC SQL FETCH cur_merch
INTO :h_merch, :h_mname, :h_status,
:h_txncnt, :h_gross, :h_fee, :h_net;
if (done) break;
/* 비활성 가맹점은 집계만 하고 정산연계는 보류(로그) */
if (strncmp((char *)h_status.arr, "A", 1) != 0)
printf("[CL_DAILY] 주의: 비활성(%.1s) 가맹점 %.*s 마감집계 포함\n",
h_status.arr, h_merch.len, h_merch.arr);
EXEC SQL
INSERT INTO cl_close_detail
(close_id, merchant_id, txn_count,
gross_amount, fee_amount, net_amount, created_at)
VALUES (:h_cid, :h_merch, :h_txncnt,
:h_gross, :h_fee, :h_net, SYSDATE);
/* 마감된 매입행을 'S'(마감완료)로 상태전이 */
EXEC SQL
UPDATE purchase
SET status = DECODE(status, 'A', 'S', status),
closed_at = SYSDATE,
close_id = :h_cid
WHERE merchant_id = :h_merch
AND biz_date = TO_DATE(:h_bizdate,'YYYYMMDD')
AND status = 'A';
h_totcnt += h_txncnt;
h_totamt += h_gross;
h_nmerch += 1;
if (h_nmerch % 500 == 0) {
printf("[CL_DAILY] 진행 %ld개 가맹점 누적 %ld건 %ld원 - 중간커밋\n",
h_nmerch, h_totcnt, h_totamt);
EXEC SQL COMMIT WORK;
}
}
EXEC SQL CLOSE cur_merch;
/* ---- 6) 마감헤더 확정 (집계 반영, 상태 O->C) ------------------- */
EXEC SQL
UPDATE cl_close_log
SET txn_count = :h_totcnt,
total_amount = :h_totamt,
status = 'C',
closed_at = SYSDATE
WHERE close_id = :h_cid;
/* ---- 7) 마감 규모별 심각도 분류 (DECODE 대체 if-ladder) -------- */
if (h_totcnt == 0) { h_zsev = 0; strcpy((char *)h_zmsg.arr, "마감대상 없음"); }
else if (h_totamt >= 500000000L){ h_zsev = 3; strcpy((char *)h_zmsg.arr, "고액 마감구간"); }
else if (h_totamt >= 50000000L) { h_zsev = 2; strcpy((char *)h_zmsg.arr, "중액 마감구간"); }
else { h_zsev = 1; strcpy((char *)h_zmsg.arr, "소액 마감구간"); }
h_zmsg.len = strlen((char *)h_zmsg.arr);
EXEC SQL
INSERT INTO cm_log (log_id, svc_name, log_level, msg, biz_date)
VALUES (cm_log_seq.NEXTVAL, 'CL_DAILY',
DECODE(SIGN(:h_zsev - 2), 1, 'W', 'I'),
'CL_DAILY 일마감 sev='||:h_zsev||' '||:h_zmsg
||' cnt='||:h_totcnt||' amt='||:h_totamt,
TO_DATE(:h_bizdate,'YYYYMMDD'));
/* ---- 8) 최종 커밋 --------------------------------------------- */
EXEC SQL COMMIT WORK;
printf("[CL_DAILY] 일마감 완료 cid=%ld 가맹점=%ld 건수=%ld 금액=%ld sev=%ld(%s)\n",
h_cid, h_nmerch, h_totcnt, h_totamt, h_zsev, h_zmsg.arr);
EXEC SQL COMMIT WORK RELEASE;
return 0;
connect_err:
fprintf(stderr, "[CL_DAILY] DB 접속 실패: %.70s\n", sqlca.sqlerrm.sqlerrmc);
return 8;
sqlfail:
sql_error("일마감 본처리");
EXEC SQL WHENEVER SQLERROR CONTINUE;
EXEC SQL ROLLBACK WORK RELEASE;
return 12;
}
/*--------------------------------------------------------------------------*/
/* sql_error - 오류 로깅 (sqlca/oraca) */
/*--------------------------------------------------------------------------*/
static void sql_error(const char *stage)
{
fprintf(stderr, "[CL_DAILY] SQL 오류 (%s): %.70s\n",
stage, sqlca.sqlerrm.sqlerrmc);
fprintf(stderr, " sqlcode=%ld rows=%ld stmt=%.60s\n",
sqlca.sqlcode, sqlca.sqlerrd[2], oraca.orastxt.orastxtc);
}