규모확장 Wave A: 전 11모듈 svc 30→75·batch 22→35 + 전 모듈 운영데이터 시드

- svc 330→825 (모듈당 +45 고유 실업무: chargeback·재매입·심사워크플로·전문유형별·
  대사세분·정산시뮬·지급재처리·복식결산·마감워크플로·마스터심사·검증룰·전문라우팅 등)
- batch 242→385 (모듈당 +13), 카피북 헤더·디스패처 advertise 전 75 반영
- db/schema.d/95-module-seed.sql: 전 모듈 테이블 운영데이터(au 8.4k·st 15.9k·mg 16.9k·
  lg 14.6k·rc 7.7k·py 2.9k·vl 8.5k·cm 10.4k 등, 총 ~171k행) — base 거래와 정합
- 화면 매니페스트 825 재생성, 큐 메모리 튜닝(NDRX_MSGMAX 50→30)
- 검증: build DONE modules=11 batches=385, 12서버 runok, xadmin psc 828 AVAIL,
  매입체인 XA 커밋(status=S), 새서비스 시드데이터 스모크(ACQ_MERCHSUM/RC_STATS 실값),
  전 코퍼스 .pgc 클론 0, 헤드리스 스크린샷 확인

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This commit is contained in:
hyeongwoo-choi 2026-07-20 22:09:55 +00:00
parent 19acf4ed96
commit ea90bef264
1292 changed files with 53968 additions and 20 deletions

View file

@ -0,0 +1,59 @@
/*
* mg_chanload_batch.pgc - mg 채널 부하 점유율 순위 배치 (커서 + XA).
*
* 채널별 당일 건수x금액 합성 부하지수를 커서로 내림차순 순회하며
* 순위표와 최대 부하 채널의 점유율(%) 을 산출한다 (채널 부하 감시).
* Usage: mg_chanload_batch [YYYY-MM-DD]
*/
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <atmi.h>
#include "mg_chanload_batch.h"
int main(int argc, char **argv)
{
const char *bizdate = (argc > 1) ? argv[1] : "2026-07-19";
long rank = 0, share;
EXEC SQL BEGIN DECLARE SECTION;
char h_bizdate[16], h_chan[16];
long h_cnt, h_load;
EXEC SQL END DECLARE SECTION;
mg_chanload_rec_t rec;
memset(&rec, 0, sizeof(rec));
strncpy(h_bizdate, bizdate, sizeof(h_bizdate)-1); h_bizdate[sizeof(h_bizdate)-1] = 0;
if (tpinit(NULL) < 0) { fprintf(stderr, "tpinit FAIL: %s\n", tpstrerror(tperrno)); return 1; }
if (tpopen() < 0) { fprintf(stderr, "tpopen FAIL: %s\n", tpstrerror(tperrno)); return 1; }
if (tpbegin(60, 0) < 0) { fprintf(stderr, "tpbegin FAIL: %s\n", tpstrerror(tperrno)); return 1; }
/* 부하지수 = 건수*1000 + 금액/10000 (건수 우선, 금액 보정) */
EXEC SQL DECLARE clb CURSOR FOR
SELECT channel, count(*),
count(*) * 1000 + coalesce(sum(amount),0) / 10000
FROM mg_msg_log WHERE biz_date = :h_bizdate
GROUP BY channel
ORDER BY 3 DESC;
EXEC SQL OPEN clb;
if (sqlca.sqlcode < 0) { fprintf(stderr, "OPEN FAIL [%d] %s\n", sqlca.sqlcode, sqlca.sqlerrm.sqlerrmc); tpabort(0); return 1; }
for (;;) {
EXEC SQL FETCH clb INTO :h_chan, :h_cnt, :h_load;
if (sqlca.sqlcode == 100) break;
if (sqlca.sqlcode < 0) { EXEC SQL CLOSE clb; tpabort(0); return 1; }
rank++;
rec.channels++;
rec.grand_total += h_load;
if (rank == 1) { rec.top_load = h_load; strncpy(rec.top_chan, h_chan, sizeof(rec.top_chan)-1); }
printf(" %ld위 [%s] 건수=%ld 부하지수=%ld\n", rank, h_chan, h_cnt, h_load);
}
EXEC SQL CLOSE clb;
if (tpcommit(0) < 0) { fprintf(stderr, "tpcommit FAIL: %s\n", tpstrerror(tperrno)); tpabort(0); return 1; }
share = (rec.grand_total > 0) ? (rec.top_load * 100L) / rec.grand_total : 0;
printf(">>> mg_chanload_batch COMMIT: bizdate=%s 채널=%ld개 1위=%s 점유율=%ld%%\n",
bizdate, rec.channels, rec.top_chan[0] ? rec.top_chan : "-", share);
tpclose(); tpterm();
return 0;
}

View file

@ -0,0 +1,53 @@
/*
* mg_dupscan_batch.pgc - mg 중복 STAN 전문 전수 스캔 배치 (커서 + XA).
*
* GROUP BY stan HAVING count(*)>1 커서로 당일 중복 순번 그룹을 전수 조사해
* 그룹 수/중복 전문 수/최다 중복 STAN 을 리포트한다 (중복전문 검출의 일배치판).
* Usage: mg_dupscan_batch [YYYY-MM-DD]
*/
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <atmi.h>
#include "mg_dupscan_batch.h"
int main(int argc, char **argv)
{
const char *bizdate = (argc > 1) ? argv[1] : "2026-07-19";
EXEC SQL BEGIN DECLARE SECTION;
char h_bizdate[16];
long h_stan, h_cnt;
EXEC SQL END DECLARE SECTION;
mg_dupscan_rec_t rec;
memset(&rec, 0, sizeof(rec));
strncpy(h_bizdate, bizdate, sizeof(h_bizdate)-1); h_bizdate[sizeof(h_bizdate)-1] = 0;
if (tpinit(NULL) < 0) { fprintf(stderr, "tpinit FAIL: %s\n", tpstrerror(tperrno)); return 1; }
if (tpopen() < 0) { fprintf(stderr, "tpopen FAIL: %s\n", tpstrerror(tperrno)); return 1; }
if (tpbegin(60, 0) < 0) { fprintf(stderr, "tpbegin FAIL: %s\n", tpstrerror(tperrno)); return 1; }
EXEC SQL DECLARE dsc CURSOR FOR
SELECT stan, count(*) FROM mg_msg_log
WHERE biz_date = :h_bizdate
GROUP BY stan HAVING count(*) > 1
ORDER BY count(*) DESC;
EXEC SQL OPEN dsc;
if (sqlca.sqlcode < 0) { fprintf(stderr, "OPEN FAIL [%d] %s\n", sqlca.sqlcode, sqlca.sqlerrm.sqlerrmc); tpabort(0); return 1; }
for (;;) {
EXEC SQL FETCH dsc INTO :h_stan, :h_cnt;
if (sqlca.sqlcode == 100) break;
if (sqlca.sqlcode < 0) { EXEC SQL CLOSE dsc; tpabort(0); return 1; }
rec.dup_groups++;
rec.dup_msgs += h_cnt - 1; /* 원본 1건 제외한 중복분 */
if (h_cnt > rec.worst_cnt) { rec.worst_cnt = h_cnt; rec.worst_stan = h_stan; }
}
EXEC SQL CLOSE dsc;
if (tpcommit(0) < 0) { fprintf(stderr, "tpcommit FAIL: %s\n", tpstrerror(tperrno)); tpabort(0); return 1; }
printf(">>> mg_dupscan_batch COMMIT: bizdate=%s 중복그룹=%ld 중복전문=%ld 최다STAN=%ld(%ld건)\n",
bizdate, rec.dup_groups, rec.dup_msgs, rec.worst_stan, rec.worst_cnt);
tpclose(); tpterm();
return 0;
}

View file

@ -0,0 +1,55 @@
/*
* mg_gapfill_batch.pgc - mg STAN 순번 갭 정밀 탐지 배치 (커서 + XA).
*
* 당일 고유 STAN 을 오름차순 커서로 훑으며 이전 순번과의 간격을 재서
* 갭 개수와 최대 결번 폭을 찾아낸다 (순번 갭 검출의 일배치판).
* Usage: mg_gapfill_batch [YYYY-MM-DD]
*/
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <atmi.h>
#include "mg_gapfill_batch.h"
int main(int argc, char **argv)
{
const char *bizdate = (argc > 1) ? argv[1] : "2026-07-19";
long prev = -1;
EXEC SQL BEGIN DECLARE SECTION;
char h_bizdate[16];
long h_stan;
EXEC SQL END DECLARE SECTION;
mg_gapfill_rec_t rec;
memset(&rec, 0, sizeof(rec));
strncpy(h_bizdate, bizdate, sizeof(h_bizdate)-1); h_bizdate[sizeof(h_bizdate)-1] = 0;
if (tpinit(NULL) < 0) { fprintf(stderr, "tpinit FAIL: %s\n", tpstrerror(tperrno)); return 1; }
if (tpopen() < 0) { fprintf(stderr, "tpopen FAIL: %s\n", tpstrerror(tperrno)); return 1; }
if (tpbegin(60, 0) < 0) { fprintf(stderr, "tpbegin FAIL: %s\n", tpstrerror(tperrno)); return 1; }
EXEC SQL DECLARE gfc CURSOR FOR
SELECT DISTINCT stan FROM mg_msg_log
WHERE biz_date = :h_bizdate ORDER BY stan;
EXEC SQL OPEN gfc;
if (sqlca.sqlcode < 0) { fprintf(stderr, "OPEN FAIL [%d] %s\n", sqlca.sqlcode, sqlca.sqlerrm.sqlerrmc); tpabort(0); return 1; }
for (;;) {
EXEC SQL FETCH gfc INTO :h_stan;
if (sqlca.sqlcode == 100) break;
if (sqlca.sqlcode < 0) { EXEC SQL CLOSE gfc; tpabort(0); return 1; }
rec.scanned++;
if (prev >= 0 && h_stan - prev > 1) { /* 연속이 깨진 지점 = 결번 구간 */
rec.gaps++;
if (h_stan - prev - 1 > rec.widest) rec.widest = h_stan - prev - 1;
}
prev = h_stan;
}
EXEC SQL CLOSE gfc;
if (tpcommit(0) < 0) { fprintf(stderr, "tpcommit FAIL: %s\n", tpstrerror(tperrno)); tpabort(0); return 1; }
printf(">>> mg_gapfill_batch COMMIT: bizdate=%s 스캔=%ld 갭구간=%ld 최대결번폭=%ld\n",
bizdate, rec.scanned, rec.gaps, rec.widest);
tpclose(); tpterm();
return 0;
}

View file

@ -0,0 +1,54 @@
/*
* mg_hourstat_batch.pgc - mg 시간대별 전문량 집계 배치 (커서 + XA).
*
* created_at 의 시(hour) 단위로 당일 전문을 묶어 시간대별 건수를 뽑고
* 피크아워와 그 비중을 리포트한다 (시간대 세분 통계의 일배치판).
* Usage: mg_hourstat_batch [YYYY-MM-DD]
*/
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <atmi.h>
#include "mg_hourstat_batch.h"
int main(int argc, char **argv)
{
const char *bizdate = (argc > 1) ? argv[1] : "2026-07-19";
EXEC SQL BEGIN DECLARE SECTION;
char h_bizdate[16];
long h_hour, h_cnt;
EXEC SQL END DECLARE SECTION;
mg_hourstat_rec_t rec;
memset(&rec, 0, sizeof(rec));
rec.peak_hour = -1;
strncpy(h_bizdate, bizdate, sizeof(h_bizdate)-1); h_bizdate[sizeof(h_bizdate)-1] = 0;
if (tpinit(NULL) < 0) { fprintf(stderr, "tpinit FAIL: %s\n", tpstrerror(tperrno)); return 1; }
if (tpopen() < 0) { fprintf(stderr, "tpopen FAIL: %s\n", tpstrerror(tperrno)); return 1; }
if (tpbegin(60, 0) < 0) { fprintf(stderr, "tpbegin FAIL: %s\n", tpstrerror(tperrno)); return 1; }
EXEC SQL DECLARE hsc CURSOR FOR
SELECT extract(hour FROM created_at)::int, count(*)
FROM mg_msg_log WHERE biz_date = :h_bizdate
GROUP BY 1 ORDER BY 1;
EXEC SQL OPEN hsc;
if (sqlca.sqlcode < 0) { fprintf(stderr, "OPEN FAIL [%d] %s\n", sqlca.sqlcode, sqlca.sqlerrm.sqlerrmc); tpabort(0); return 1; }
for (;;) {
EXEC SQL FETCH hsc INTO :h_hour, :h_cnt;
if (sqlca.sqlcode == 100) break;
if (sqlca.sqlcode < 0) { EXEC SQL CLOSE hsc; tpabort(0); return 1; }
rec.hours++;
rec.total += h_cnt;
printf(" [%02ld시] %ld건\n", h_hour, h_cnt);
if (h_cnt > rec.peak_cnt) { rec.peak_cnt = h_cnt; rec.peak_hour = h_hour; }
}
EXEC SQL CLOSE hsc;
if (tpcommit(0) < 0) { fprintf(stderr, "tpcommit FAIL: %s\n", tpstrerror(tperrno)); tpabort(0); return 1; }
printf(">>> mg_hourstat_batch COMMIT: bizdate=%s 활동시간대=%ld 총=%ld 피크=%ld시(%ld건)\n",
bizdate, rec.hours, rec.total, rec.peak_hour, rec.peak_cnt);
tpclose(); tpterm();
return 0;
}

View file

@ -0,0 +1,53 @@
/*
* mg_keyver_batch.pgc - mg 세대키 일괄 승급/세션키 재발급 배치 (2단계 갱신 + XA).
*
* KEYVER 카운터를 한 번 올린 뒤 UP 채널 전부의 세션키를 새 세대
* "Kvvvv-<채널>" 로 재발급한다 (키 전환의 전 채널 일괄판).
* Usage: mg_keyver_batch [YYYY-MM-DD]
*/
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <atmi.h>
#include "mg_keyver_batch.h"
int main(int argc, char **argv)
{
const char *bizdate = (argc > 1) ? argv[1] : "2026-07-19";
EXEC SQL BEGIN DECLARE SECTION;
long h_kv;
char h_prefix[16];
EXEC SQL END DECLARE SECTION;
mg_keyver_rec_t rec;
memset(&rec, 0, sizeof(rec));
(void)bizdate; /* 키 승급은 영업일 무관 전 채널 대상 */
if (tpinit(NULL) < 0) { fprintf(stderr, "tpinit FAIL: %s\n", tpstrerror(tperrno)); return 1; }
if (tpopen() < 0) { fprintf(stderr, "tpopen FAIL: %s\n", tpstrerror(tperrno)); return 1; }
if (tpbegin(60, 0) < 0) { fprintf(stderr, "tpbegin FAIL: %s\n", tpstrerror(tperrno)); return 1; }
/* 1단계: 세대키 번호 승급 */
EXEC SQL UPDATE mg_seq SET seq_val = seq_val + 1, updated_at = now()
WHERE seq_name = 'KEYVER'
RETURNING seq_val INTO :h_kv;
if (sqlca.sqlcode < 0 || sqlca.sqlerrd[2] == 0) {
fprintf(stderr, "KEYVER 승급 FAIL [%d]\n", sqlca.sqlcode); tpabort(0); return 1;
}
rec.new_keyver = h_kv;
/* 2단계: UP 채널 세션키 재발급 (채널명이 키 꼬리에 박혀 채널별로 달라짐) */
sprintf(h_prefix, "K%04ld-", h_kv);
EXEC SQL UPDATE mg_channel
SET session_key = :h_prefix || channel, updated_at = now()
WHERE status = 'UP';
if (sqlca.sqlcode < 0) { fprintf(stderr, "세션키 재발급 FAIL [%d] %s\n", sqlca.sqlcode, sqlca.sqlerrm.sqlerrmc); tpabort(0); return 1; }
rec.rekeyed = (long)sqlca.sqlerrd[2];
if (tpcommit(0) < 0) { fprintf(stderr, "tpcommit FAIL: %s\n", tpstrerror(tperrno)); tpabort(0); return 1; }
printf(">>> mg_keyver_batch COMMIT: 신규 키버전=%ld 재발급 채널=%ld개\n",
rec.new_keyver, rec.rekeyed);
tpclose(); tpterm();
return 0;
}

View file

@ -0,0 +1,61 @@
/*
* mg_macaudit_batch.pgc - mg MAC 누락 전문 감사/보정 배치 (행단위 커서 갱신 + XA).
*
* mac 이 비어 있는 당일 전문을 커서로 돌며 raw_msg 로 가산 체크섬을 재계산해
* 행마다 채워 넣는다 (무결성 사후 보정: MG_MAC 계열과 동일 알고리즘).
* Usage: mg_macaudit_batch [YYYY-MM-DD]
*/
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <atmi.h>
#include "mg_macaudit_batch.h"
/* 가산 체크섬 4-hex (온라인 MG_MAC 과 같은 계열, 배치측 독립 구현). */
static void mac_stamp(const char *s, char *out)
{
unsigned int a = 0; int i;
for (i = 0; s[i]; i++) a = (a + (unsigned char)s[i] * 31u) & 0xFFFF;
sprintf(out, "%04X", a);
}
int main(int argc, char **argv)
{
const char *bizdate = (argc > 1) ? argv[1] : "2026-07-19";
EXEC SQL BEGIN DECLARE SECTION;
char h_bizdate[16], h_raw[512], h_mac[8];
long h_id;
EXEC SQL END DECLARE SECTION;
mg_macaudit_rec_t rec;
memset(&rec, 0, sizeof(rec));
strncpy(h_bizdate, bizdate, sizeof(h_bizdate)-1); h_bizdate[sizeof(h_bizdate)-1] = 0;
if (tpinit(NULL) < 0) { fprintf(stderr, "tpinit FAIL: %s\n", tpstrerror(tperrno)); return 1; }
if (tpopen() < 0) { fprintf(stderr, "tpopen FAIL: %s\n", tpstrerror(tperrno)); return 1; }
if (tpbegin(60, 0) < 0) { fprintf(stderr, "tpbegin FAIL: %s\n", tpstrerror(tperrno)); return 1; }
EXEC SQL DECLARE mab CURSOR FOR
SELECT msg_id, raw_msg FROM mg_msg_log
WHERE biz_date = :h_bizdate AND mac = '' AND raw_msg <> '';
EXEC SQL OPEN mab;
if (sqlca.sqlcode < 0) { fprintf(stderr, "OPEN FAIL [%d] %s\n", sqlca.sqlcode, sqlca.sqlerrm.sqlerrmc); tpabort(0); return 1; }
for (;;) {
EXEC SQL FETCH mab INTO :h_id, :h_raw;
if (sqlca.sqlcode == 100) break;
if (sqlca.sqlcode < 0) { EXEC SQL CLOSE mab; tpabort(0); return 1; }
rec.audited++;
mac_stamp(h_raw, h_mac);
EXEC SQL UPDATE mg_msg_log SET mac = :h_mac WHERE msg_id = :h_id;
if (sqlca.sqlcode < 0) { fprintf(stderr, "보정 FAIL msg_id=%ld [%d]\n", h_id, sqlca.sqlcode); EXEC SQL CLOSE mab; tpabort(0); return 1; }
rec.stamped++;
}
EXEC SQL CLOSE mab;
if (tpcommit(0) < 0) { fprintf(stderr, "tpcommit FAIL: %s\n", tpstrerror(tperrno)); tpabort(0); return 1; }
printf(">>> mg_macaudit_batch COMMIT: bizdate=%s 감사=%ld건 보정=%ld건\n",
bizdate, rec.audited, rec.stamped);
tpclose(); tpterm();
return 0;
}

View file

@ -0,0 +1,56 @@
/*
* mg_mtimix_batch.pgc - mg 전문유형(MTI) 구성비 산출 배치 (커서 + XA).
*
* MTI 별로 당일 전문을 묶어 유형 가짓수와 금융전문(02xx/04xx) 대
* 망관리전문(08xx) 비중을 가른다 (전문유형 세분 통계의 일배치판).
* Usage: mg_mtimix_batch [YYYY-MM-DD]
*/
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <atmi.h>
#include "mg_mtimix_batch.h"
int main(int argc, char **argv)
{
const char *bizdate = (argc > 1) ? argv[1] : "2026-07-19";
long pct;
EXEC SQL BEGIN DECLARE SECTION;
char h_bizdate[16], h_mti[8];
long h_cnt;
EXEC SQL END DECLARE SECTION;
mg_mtimix_rec_t rec;
memset(&rec, 0, sizeof(rec));
strncpy(h_bizdate, bizdate, sizeof(h_bizdate)-1); h_bizdate[sizeof(h_bizdate)-1] = 0;
if (tpinit(NULL) < 0) { fprintf(stderr, "tpinit FAIL: %s\n", tpstrerror(tperrno)); return 1; }
if (tpopen() < 0) { fprintf(stderr, "tpopen FAIL: %s\n", tpstrerror(tperrno)); return 1; }
if (tpbegin(60, 0) < 0) { fprintf(stderr, "tpbegin FAIL: %s\n", tpstrerror(tperrno)); return 1; }
EXEC SQL DECLARE mmx CURSOR FOR
SELECT mti, count(*) FROM mg_msg_log
WHERE biz_date = :h_bizdate
GROUP BY mti ORDER BY mti;
EXEC SQL OPEN mmx;
if (sqlca.sqlcode < 0) { fprintf(stderr, "OPEN FAIL [%d] %s\n", sqlca.sqlcode, sqlca.sqlerrm.sqlerrmc); tpabort(0); return 1; }
for (;;) {
EXEC SQL FETCH mmx INTO :h_mti, :h_cnt;
if (sqlca.sqlcode == 100) break;
if (sqlca.sqlcode < 0) { EXEC SQL CLOSE mmx; tpabort(0); return 1; }
rec.kinds++;
rec.total += h_cnt;
if (h_mti[1] == '8') rec.net_cnt += h_cnt; /* 08xx = 망관리 */
else rec.fin_cnt += h_cnt; /* 그 외 = 금융/취소/정산 */
printf(" [MTI %.4s] %ld건\n", h_mti, h_cnt);
}
EXEC SQL CLOSE mmx;
if (tpcommit(0) < 0) { fprintf(stderr, "tpcommit FAIL: %s\n", tpstrerror(tperrno)); tpabort(0); return 1; }
pct = (rec.total > 0) ? (rec.fin_cnt * 100L) / rec.total : 0;
printf(">>> mg_mtimix_batch COMMIT: bizdate=%s 유형=%ld 총=%ld 금융=%ld(%ld%%) 망관리=%ld\n",
bizdate, rec.kinds, rec.total, rec.fin_cnt, pct, rec.net_cnt);
tpclose(); tpterm();
return 0;
}

View file

@ -0,0 +1,63 @@
/*
* mg_panaudit_batch.pgc - mg 비마스킹 PAN 적발/마스킹 배치 (행단위 커서 갱신 + XA).
*
* pan 에 '*' 가 전혀 없는(=평문 의심) 당일 전문을 커서로 적발해
* BIN(앞6)/뒤4 보존 마스킹으로 행마다 덮어쓴다 (정보보호 일제 점검).
* Usage: mg_panaudit_batch [YYYY-MM-DD]
*/
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <atmi.h>
#include "mg_panaudit_batch.h"
/* 앞6/뒤4 보존, 나머지 '*' 치환 (12자리 미만은 전부 '*'). */
static void pan_conceal(const char *in, char *out)
{
int i, n = (int)strlen(in);
if (n > 19) n = 19;
for (i = 0; i < n; i++)
out[i] = (n >= 12 && (i < 6 || i >= n - 4)) ? in[i] : '*';
out[n] = 0;
}
int main(int argc, char **argv)
{
const char *bizdate = (argc > 1) ? argv[1] : "2026-07-19";
EXEC SQL BEGIN DECLARE SECTION;
char h_bizdate[16], h_pan[24], h_masked[24];
long h_id;
EXEC SQL END DECLARE SECTION;
mg_panaudit_rec_t rec;
memset(&rec, 0, sizeof(rec));
strncpy(h_bizdate, bizdate, sizeof(h_bizdate)-1); h_bizdate[sizeof(h_bizdate)-1] = 0;
if (tpinit(NULL) < 0) { fprintf(stderr, "tpinit FAIL: %s\n", tpstrerror(tperrno)); return 1; }
if (tpopen() < 0) { fprintf(stderr, "tpopen FAIL: %s\n", tpstrerror(tperrno)); return 1; }
if (tpbegin(60, 0) < 0) { fprintf(stderr, "tpbegin FAIL: %s\n", tpstrerror(tperrno)); return 1; }
EXEC SQL DECLARE pab CURSOR FOR
SELECT msg_id, pan FROM mg_msg_log
WHERE biz_date = :h_bizdate AND pan <> '' AND position('*' in pan) = 0;
EXEC SQL OPEN pab;
if (sqlca.sqlcode < 0) { fprintf(stderr, "OPEN FAIL [%d] %s\n", sqlca.sqlcode, sqlca.sqlerrm.sqlerrmc); tpabort(0); return 1; }
for (;;) {
EXEC SQL FETCH pab INTO :h_id, :h_pan;
if (sqlca.sqlcode == 100) break;
if (sqlca.sqlcode < 0) { EXEC SQL CLOSE pab; tpabort(0); return 1; }
rec.inspected++;
pan_conceal(h_pan, h_masked);
EXEC SQL UPDATE mg_msg_log SET pan = :h_masked WHERE msg_id = :h_id;
if (sqlca.sqlcode < 0) { fprintf(stderr, "마스킹 FAIL msg_id=%ld [%d]\n", h_id, sqlca.sqlcode); EXEC SQL CLOSE pab; tpabort(0); return 1; }
rec.masked++;
}
EXEC SQL CLOSE pab;
if (tpcommit(0) < 0) { fprintf(stderr, "tpcommit FAIL: %s\n", tpstrerror(tperrno)); tpabort(0); return 1; }
printf(">>> mg_panaudit_batch COMMIT: bizdate=%s 적발=%ld건 마스킹=%ld건\n",
bizdate, rec.inspected, rec.masked);
tpclose(); tpterm();
return 0;
}

View file

@ -0,0 +1,50 @@
/*
* mg_qagesweep_batch.pgc - mg 장기체류 큐 재시도 소진 스윕 배치 (2단계 갱신 + XA).
*
* 10분 넘게 대기(Q)한 큐 항목의 retry_cnt 를 올리고, 재시도 3회를 소진한
* 항목은 실패(F)로 확정한다 (큐 체류시간 기반 단계적 포기 정책).
* Usage: mg_qagesweep_batch [YYYY-MM-DD]
*/
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <atmi.h>
#include "mg_qagesweep_batch.h"
int main(int argc, char **argv)
{
const char *bizdate = (argc > 1) ? argv[1] : "2026-07-19";
EXEC SQL BEGIN DECLARE SECTION;
char h_bizdate[16];
EXEC SQL END DECLARE SECTION;
mg_qagesweep_rec_t rec;
memset(&rec, 0, sizeof(rec));
strncpy(h_bizdate, bizdate, sizeof(h_bizdate)-1); h_bizdate[sizeof(h_bizdate)-1] = 0;
if (tpinit(NULL) < 0) { fprintf(stderr, "tpinit FAIL: %s\n", tpstrerror(tperrno)); return 1; }
if (tpopen() < 0) { fprintf(stderr, "tpopen FAIL: %s\n", tpstrerror(tperrno)); return 1; }
if (tpbegin(60, 0) < 0) { fprintf(stderr, "tpbegin FAIL: %s\n", tpstrerror(tperrno)); return 1; }
/* 1단계: 10분 이상 체류한 대기 항목 재시도 카운트 증가 */
EXEC SQL UPDATE mg_queue
SET retry_cnt = retry_cnt + 1, updated_at = now()
WHERE biz_date = :h_bizdate AND status = 'Q'
AND updated_at < now() - interval '10 minutes';
if (sqlca.sqlcode < 0) { fprintf(stderr, "재시도 가산 FAIL [%d] %s\n", sqlca.sqlcode, sqlca.sqlerrm.sqlerrmc); tpabort(0); return 1; }
rec.bumped = (long)sqlca.sqlerrd[2];
/* 2단계: 재시도 소진(>=3) 항목 실패 확정 */
EXEC SQL UPDATE mg_queue
SET status = 'F', updated_at = now()
WHERE biz_date = :h_bizdate AND status = 'Q' AND retry_cnt >= 3;
if (sqlca.sqlcode < 0) { fprintf(stderr, "실패 확정 FAIL [%d] %s\n", sqlca.sqlcode, sqlca.sqlerrm.sqlerrmc); tpabort(0); return 1; }
rec.failed = (long)sqlca.sqlerrd[2];
if (tpcommit(0) < 0) { fprintf(stderr, "tpcommit FAIL: %s\n", tpstrerror(tperrno)); tpabort(0); return 1; }
printf(">>> mg_qagesweep_batch COMMIT: bizdate=%s 재시도가산=%ld건 실패확정=%ld건\n",
bizdate, rec.bumped, rec.failed);
tpclose(); tpterm();
return 0;
}

View file

@ -0,0 +1,58 @@
/*
* mg_rcdist_batch.pgc - mg 응답코드 분포 리포트 배치 (커서 + XA).
*
* rc 별 GROUP BY 커서로 당일 정상(00)/거절 분포와 최빈 거절코드를 뽑아
* 거절 코드 급증을 조기에 드러낸다 (응답코드 세분 통계의 일배치판).
* Usage: mg_rcdist_batch [YYYY-MM-DD]
*/
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <atmi.h>
#include "mg_rcdist_batch.h"
int main(int argc, char **argv)
{
const char *bizdate = (argc > 1) ? argv[1] : "2026-07-19";
long top_cnt = 0;
EXEC SQL BEGIN DECLARE SECTION;
char h_bizdate[16], h_rc[4];
long h_cnt;
EXEC SQL END DECLARE SECTION;
mg_rcdist_rec_t rec;
memset(&rec, 0, sizeof(rec));
strncpy(h_bizdate, bizdate, sizeof(h_bizdate)-1); h_bizdate[sizeof(h_bizdate)-1] = 0;
if (tpinit(NULL) < 0) { fprintf(stderr, "tpinit FAIL: %s\n", tpstrerror(tperrno)); return 1; }
if (tpopen() < 0) { fprintf(stderr, "tpopen FAIL: %s\n", tpstrerror(tperrno)); return 1; }
if (tpbegin(60, 0) < 0) { fprintf(stderr, "tpbegin FAIL: %s\n", tpstrerror(tperrno)); return 1; }
EXEC SQL DECLARE rdb CURSOR FOR
SELECT rc, count(*) FROM mg_msg_log
WHERE biz_date = :h_bizdate
GROUP BY rc ORDER BY count(*) DESC, rc;
EXEC SQL OPEN rdb;
if (sqlca.sqlcode < 0) { fprintf(stderr, "OPEN FAIL [%d] %s\n", sqlca.sqlcode, sqlca.sqlerrm.sqlerrmc); tpabort(0); return 1; }
for (;;) {
EXEC SQL FETCH rdb INTO :h_rc, :h_cnt;
if (sqlca.sqlcode == 100) break;
if (sqlca.sqlcode < 0) { EXEC SQL CLOSE rdb; tpabort(0); return 1; }
rec.kinds++;
if (h_rc[0] == '0' && h_rc[1] == '0') {
rec.ok_cnt += h_cnt;
} else {
rec.bad_cnt += h_cnt;
if (h_cnt > top_cnt) { top_cnt = h_cnt; strncpy(rec.top_rc, h_rc, 3); rec.top_rc[3] = 0; }
}
printf(" [rc %.2s] %ld건\n", h_rc, h_cnt);
}
EXEC SQL CLOSE rdb;
if (tpcommit(0) < 0) { fprintf(stderr, "tpcommit FAIL: %s\n", tpstrerror(tperrno)); tpabort(0); return 1; }
printf(">>> mg_rcdist_batch COMMIT: bizdate=%s 코드=%ld종 정상=%ld 거절=%ld 최빈거절=%s\n",
bizdate, rec.kinds, rec.ok_cnt, rec.bad_cnt, rec.top_rc[0] ? rec.top_rc : "-");
tpclose(); tpterm();
return 0;
}

View file

@ -0,0 +1,45 @@
/*
* mg_reroute_batch.pgc - mg 미라우팅 전문 기본룰 보충 배치 (INSERT..SELECT + XA).
*
* 당일 원장에 나타난 채널/MTI 조합 중 활성 라우팅 룰이 하나도 없는 조합을
* 찾아 기본 목적지 ACQUIRE(우선순위 900) 룰을 자동 보충한다.
* Usage: mg_reroute_batch [YYYY-MM-DD]
*/
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <atmi.h>
#include "mg_reroute_batch.h"
int main(int argc, char **argv)
{
const char *bizdate = (argc > 1) ? argv[1] : "2026-07-19";
EXEC SQL BEGIN DECLARE SECTION;
char h_bizdate[16];
EXEC SQL END DECLARE SECTION;
mg_reroute_rec_t rec;
memset(&rec, 0, sizeof(rec));
strncpy(h_bizdate, bizdate, sizeof(h_bizdate)-1); h_bizdate[sizeof(h_bizdate)-1] = 0;
if (tpinit(NULL) < 0) { fprintf(stderr, "tpinit FAIL: %s\n", tpstrerror(tperrno)); return 1; }
if (tpopen() < 0) { fprintf(stderr, "tpopen FAIL: %s\n", tpstrerror(tperrno)); return 1; }
if (tpbegin(60, 0) < 0) { fprintf(stderr, "tpbegin FAIL: %s\n", tpstrerror(tperrno)); return 1; }
/* 원장에 존재하나 활성 룰이 없는 채널/MTI 조합에 최후순위 기본룰 보충 */
EXEC SQL INSERT INTO mg_route (route_id, channel, mti, dest, priority, active, biz_date)
SELECT nextval('mg_route_seq'), s.channel, s.mti, 'ACQUIRE', 900, true, CAST(:h_bizdate AS date)
FROM (SELECT DISTINCT channel, mti FROM mg_msg_log WHERE biz_date = :h_bizdate) s
WHERE NOT EXISTS (
SELECT 1 FROM mg_route r
WHERE r.channel = s.channel AND r.mti = s.mti AND r.active = true
);
if (sqlca.sqlcode < 0) { fprintf(stderr, "기본룰 보충 FAIL [%d] %s\n", sqlca.sqlcode, sqlca.sqlerrm.sqlerrmc); tpabort(0); return 1; }
rec.added_rules = (long)sqlca.sqlerrd[2];
if (tpcommit(0) < 0) { fprintf(stderr, "tpcommit FAIL: %s\n", tpstrerror(tperrno)); tpabort(0); return 1; }
printf(">>> mg_reroute_batch COMMIT: bizdate=%s 보충된 기본룰=%ld건\n", bizdate, rec.added_rules);
tpclose(); tpterm();
return 0;
}

View file

@ -0,0 +1,60 @@
/*
* mg_seqsync_batch.pgc - mg 채널 last_stan 원장 동기화 배치 (커서 + 대사 갱신 + XA).
*
* 채널마다 원장의 당일 최대 STAN 을 구해 채널 테이블의 last_stan 과 대조하고,
* 뒤처진 채널만 골라 원장 기준으로 전진 동기화한다 (순번 감사 후속 조치).
* Usage: mg_seqsync_batch [YYYY-MM-DD]
*/
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <atmi.h>
#include "mg_seqsync_batch.h"
int main(int argc, char **argv)
{
const char *bizdate = (argc > 1) ? argv[1] : "2026-07-19";
EXEC SQL BEGIN DECLARE SECTION;
char h_bizdate[16], h_chan[16];
long h_last, h_maxstan;
EXEC SQL END DECLARE SECTION;
mg_seqsync_rec_t rec;
memset(&rec, 0, sizeof(rec));
strncpy(h_bizdate, bizdate, sizeof(h_bizdate)-1); h_bizdate[sizeof(h_bizdate)-1] = 0;
if (tpinit(NULL) < 0) { fprintf(stderr, "tpinit FAIL: %s\n", tpstrerror(tperrno)); return 1; }
if (tpopen() < 0) { fprintf(stderr, "tpopen FAIL: %s\n", tpstrerror(tperrno)); return 1; }
if (tpbegin(60, 0) < 0) { fprintf(stderr, "tpbegin FAIL: %s\n", tpstrerror(tperrno)); return 1; }
EXEC SQL DECLARE ssb CURSOR FOR
SELECT c.channel, c.last_stan, coalesce(max(m.stan),0)
FROM mg_channel c
LEFT JOIN mg_msg_log m ON m.channel = c.channel AND m.biz_date = :h_bizdate
GROUP BY c.channel, c.last_stan
ORDER BY c.channel;
EXEC SQL OPEN ssb;
if (sqlca.sqlcode < 0) { fprintf(stderr, "OPEN FAIL [%d] %s\n", sqlca.sqlcode, sqlca.sqlerrm.sqlerrmc); tpabort(0); return 1; }
for (;;) {
EXEC SQL FETCH ssb INTO :h_chan, :h_last, :h_maxstan;
if (sqlca.sqlcode == 100) break;
if (sqlca.sqlcode < 0) { EXEC SQL CLOSE ssb; tpabort(0); return 1; }
rec.checked++;
if (h_maxstan > h_last) { /* 채널이 원장보다 뒤처짐 -> 전진 */
EXEC SQL UPDATE mg_channel
SET last_stan = :h_maxstan, updated_at = now()
WHERE channel = :h_chan;
if (sqlca.sqlcode < 0) { fprintf(stderr, "동기화 FAIL %s [%d]\n", h_chan, sqlca.sqlcode); EXEC SQL CLOSE ssb; tpabort(0); return 1; }
rec.synced++;
printf(" [%s] last_stan %ld -> %ld\n", h_chan, h_last, h_maxstan);
}
}
EXEC SQL CLOSE ssb;
if (tpcommit(0) < 0) { fprintf(stderr, "tpcommit FAIL: %s\n", tpstrerror(tperrno)); tpabort(0); return 1; }
printf(">>> mg_seqsync_batch COMMIT: bizdate=%s 점검=%ld채널 동기화=%ld채널\n",
bizdate, rec.checked, rec.synced);
tpclose(); tpterm();
return 0;
}

View file

@ -0,0 +1,49 @@
/*
* mg_timeout_batch.pgc - mg 응답지연 전문 일괄 타임아웃 배치 (XA 단일 갱신 배치).
*
* mg_seq(TMOUTSEC) 임계초를 읽어, 그 시간을 넘긴 수신(R) 상태 전문을
* rc=68/N 으로 한 문장에 전이시킨다 (MG_TIMEOUT 스윕의 일배치 대량판).
* Usage: mg_timeout_batch [YYYY-MM-DD]
*/
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <atmi.h>
#include "mg_timeout_batch.h"
int main(int argc, char **argv)
{
const char *bizdate = (argc > 1) ? argv[1] : "2026-07-19";
EXEC SQL BEGIN DECLARE SECTION;
char h_bizdate[16];
long h_sec;
EXEC SQL END DECLARE SECTION;
mg_timeout_rec_t rec;
memset(&rec, 0, sizeof(rec));
strncpy(h_bizdate, bizdate, sizeof(h_bizdate)-1); h_bizdate[sizeof(h_bizdate)-1] = 0;
if (tpinit(NULL) < 0) { fprintf(stderr, "tpinit FAIL: %s\n", tpstrerror(tperrno)); return 1; }
if (tpopen() < 0) { fprintf(stderr, "tpopen FAIL: %s\n", tpstrerror(tperrno)); return 1; }
if (tpbegin(60, 0) < 0) { fprintf(stderr, "tpbegin FAIL: %s\n", tpstrerror(tperrno)); return 1; }
h_sec = 60; /* 설정 없으면 60초 기본 */
EXEC SQL SELECT seq_val INTO :h_sec FROM mg_seq WHERE seq_name = 'TMOUTSEC';
if (sqlca.sqlcode < 0) { fprintf(stderr, "TMOUTSEC 조회 FAIL [%d]\n", sqlca.sqlcode); tpabort(0); return 1; }
if (h_sec <= 0) h_sec = 60;
rec.threshold_sec = h_sec;
EXEC SQL UPDATE mg_msg_log
SET rc = '68', status = 'N'
WHERE biz_date = :h_bizdate AND status = 'R'
AND created_at < now() - (:h_sec * interval '1 second');
if (sqlca.sqlcode < 0) { fprintf(stderr, "타임아웃 전이 FAIL [%d] %s\n", sqlca.sqlcode, sqlca.sqlerrm.sqlerrmc); tpabort(0); return 1; }
rec.expired = (long)sqlca.sqlerrd[2];
if (tpcommit(0) < 0) { fprintf(stderr, "tpcommit FAIL: %s\n", tpstrerror(tperrno)); tpabort(0); return 1; }
printf(">>> mg_timeout_batch COMMIT: bizdate=%s 임계=%ld초 만료전이=%ld건\n",
bizdate, rec.threshold_sec, rec.expired);
tpclose(); tpterm();
return 0;
}

View file

@ -0,0 +1,10 @@
/*
* mg_chanload_batch.h - mg mg_chanload_batch (copybook). mg
* + . Included by mg_chanload_batch.pgc.
*/
#ifndef MG_CHANLOAD_BATCH_H
#define MG_CHANLOAD_BATCH_H
typedef struct { long channels; long grand_total; long top_load; char top_chan[16]; } mg_chanload_rec_t;
#endif /* MG_CHANLOAD_BATCH_H */

View file

@ -0,0 +1,10 @@
/*
* mg_dupscan_batch.h - mg mg_dupscan_batch (copybook). mg STAN
* + . Included by mg_dupscan_batch.pgc.
*/
#ifndef MG_DUPSCAN_BATCH_H
#define MG_DUPSCAN_BATCH_H
typedef struct { long dup_groups; long dup_msgs; long worst_stan; long worst_cnt; } mg_dupscan_rec_t;
#endif /* MG_DUPSCAN_BATCH_H */

View file

@ -0,0 +1,10 @@
/*
* mg_gapfill_batch.h - mg mg_gapfill_batch (copybook). mg STAN
* + . Included by mg_gapfill_batch.pgc.
*/
#ifndef MG_GAPFILL_BATCH_H
#define MG_GAPFILL_BATCH_H
typedef struct { long scanned; long gaps; long widest; } mg_gapfill_rec_t;
#endif /* MG_GAPFILL_BATCH_H */

View file

@ -0,0 +1,10 @@
/*
* mg_hourstat_batch.h - mg mg_hourstat_batch (copybook). mg
* + . Included by mg_hourstat_batch.pgc.
*/
#ifndef MG_HOURSTAT_BATCH_H
#define MG_HOURSTAT_BATCH_H
typedef struct { long hours; long peak_hour; long peak_cnt; long total; } mg_hourstat_rec_t;
#endif /* MG_HOURSTAT_BATCH_H */

View file

@ -0,0 +1,10 @@
/*
* mg_keyver_batch.h - mg mg_keyver_batch (copybook). mg /
* + . Included by mg_keyver_batch.pgc.
*/
#ifndef MG_KEYVER_BATCH_H
#define MG_KEYVER_BATCH_H
typedef struct { long new_keyver; long rekeyed; } mg_keyver_rec_t;
#endif /* MG_KEYVER_BATCH_H */

View file

@ -0,0 +1,10 @@
/*
* mg_macaudit_batch.h - mg mg_macaudit_batch (copybook). mg MAC /
* + . Included by mg_macaudit_batch.pgc.
*/
#ifndef MG_MACAUDIT_BATCH_H
#define MG_MACAUDIT_BATCH_H
typedef struct { long audited; long stamped; } mg_macaudit_rec_t;
#endif /* MG_MACAUDIT_BATCH_H */

View file

@ -0,0 +1,10 @@
/*
* mg_mtimix_batch.h - mg mg_mtimix_batch (copybook). mg (MTI)
* + . Included by mg_mtimix_batch.pgc.
*/
#ifndef MG_MTIMIX_BATCH_H
#define MG_MTIMIX_BATCH_H
typedef struct { long kinds; long total; long fin_cnt; long net_cnt; } mg_mtimix_rec_t;
#endif /* MG_MTIMIX_BATCH_H */

View file

@ -0,0 +1,10 @@
/*
* mg_panaudit_batch.h - mg mg_panaudit_batch (copybook). mg PAN /
* + . Included by mg_panaudit_batch.pgc.
*/
#ifndef MG_PANAUDIT_BATCH_H
#define MG_PANAUDIT_BATCH_H
typedef struct { long inspected; long masked; } mg_panaudit_rec_t;
#endif /* MG_PANAUDIT_BATCH_H */

View file

@ -0,0 +1,10 @@
/*
* mg_qagesweep_batch.h - mg mg_qagesweep_batch (copybook). mg
* + . Included by mg_qagesweep_batch.pgc.
*/
#ifndef MG_QAGESWEEP_BATCH_H
#define MG_QAGESWEEP_BATCH_H
typedef struct { long bumped; long failed; } mg_qagesweep_rec_t;
#endif /* MG_QAGESWEEP_BATCH_H */

View file

@ -0,0 +1,10 @@
/*
* mg_rcdist_batch.h - mg mg_rcdist_batch (copybook). mg
* + . Included by mg_rcdist_batch.pgc.
*/
#ifndef MG_RCDIST_BATCH_H
#define MG_RCDIST_BATCH_H
typedef struct { long kinds; long ok_cnt; long bad_cnt; char top_rc[4]; } mg_rcdist_rec_t;
#endif /* MG_RCDIST_BATCH_H */

View file

@ -0,0 +1,10 @@
/*
* mg_reroute_batch.h - mg mg_reroute_batch (copybook). mg
* + . Included by mg_reroute_batch.pgc.
*/
#ifndef MG_REROUTE_BATCH_H
#define MG_REROUTE_BATCH_H
typedef struct { long added_rules; } mg_reroute_rec_t;
#endif /* MG_REROUTE_BATCH_H */

View file

@ -0,0 +1,10 @@
/*
* mg_seqsync_batch.h - mg mg_seqsync_batch (copybook). mg last_stan
* + . Included by mg_seqsync_batch.pgc.
*/
#ifndef MG_SEQSYNC_BATCH_H
#define MG_SEQSYNC_BATCH_H
typedef struct { long checked; long synced; } mg_seqsync_rec_t;
#endif /* MG_SEQSYNC_BATCH_H */

View file

@ -0,0 +1,10 @@
/*
* mg_timeout_batch.h - mg mg_timeout_batch (copybook). mg
* + . Included by mg_timeout_batch.pgc.
*/
#ifndef MG_TIMEOUT_BATCH_H
#define MG_TIMEOUT_BATCH_H
typedef struct { long threshold_sec; long expired; } mg_timeout_rec_t;
#endif /* MG_TIMEOUT_BATCH_H */

View file

@ -2,13 +2,13 @@
* mg_svr.pgc - mg (전문게이트웨이 / message gateway) MODULE SERVER (thin dispatcher).
*
* The legacy "one module server, many services" pattern: this binary owns NO
* business logic. Each of mg's 30 online services now lives in its own file
* business logic. Each of mg's 75 online services now lives in its own file
* (app/src/mg/svc/<SVCNAME>.pgc) with its own copybook header
* (app/src/mg/svc/<SVCNAME>.h); those objects are archived into libmgsvc.a
* and linked into this server. Here we only:
* - include every service copybook (for the extern prototypes),
* - tpopen() the ECPG XA RM,
* - tpadvertise() all 30 services from a {name, fn} table,
* - tpadvertise() all 75 services from a {name, fn} table,
* - tpclose() at shutdown.
*
* XA: the connection is opened once by tpopen() (ECPG XA switch libndrxxaecpg.so);
@ -49,6 +49,60 @@
#include "MG_CHANSTAT.h"
#include "MG_STATUS.h"
#include "MG_CONFIRM.h"
/* --- 전문유형별 처리 (0100/0110/0200/0210/0400/0420/0800) --- */
#include "MG_AUTHREQ.h"
#include "MG_AUTHRSP.h"
#include "MG_CAPTURE.h"
#include "MG_CAPRSP.h"
#include "MG_REVERSAL.h"
#include "MG_REVADVICE.h"
#include "MG_NETMGMT.h"
/* --- 채널별 어댑터 / 전문 재조립 --- */
#include "MG_VAN1ADPT.h"
#include "MG_VAN2ADPT.h"
#include "MG_DIRECTADPT.h"
#include "MG_REASM.h"
/* --- MAC 세분 / 키 관리 --- */
#include "MG_MACGEN.h"
#include "MG_MACVERIFY.h"
#include "MG_KEYROTATE.h"
#include "MG_KEYSTATUS.h"
/* --- 타임아웃 / 중복·순번 검출 --- */
#include "MG_TIMEOUT.h"
#include "MG_TIMEOUTSET.h"
#include "MG_DUPCHK.h"
#include "MG_GAPSCAN.h"
#include "MG_SEQAUDIT.h"
/* --- 전문 변환 (내부<->ISO) / 라우팅 룰 관리 --- */
#include "MG_TOISO.h"
#include "MG_FROMISO.h"
#include "MG_RTADD.h"
#include "MG_RTDROP.h"
#include "MG_RTLIST.h"
#include "MG_RTSWITCH.h"
/* --- 채널 상태 모니터/절체 --- */
#include "MG_CHANOPEN.h"
#include "MG_CHANCLOSE.h"
#include "MG_CHANSWITCH.h"
#include "MG_CHANHEALTH.h"
/* --- 전문 통계 세분 --- */
#include "MG_STATMTI.h"
#include "MG_STATHOUR.h"
#include "MG_STATDIR.h"
#include "MG_STATRC.h"
/* --- 오류전문 재처리 워크플로 / 트래픽 제어 --- */
#include "MG_ERRRETRY.h"
#include "MG_ERRLIST.h"
#include "MG_ERRPURGE.h"
#include "MG_THROTTLE.h"
#include "MG_TPSCHECK.h"
/* --- 검증·마스킹 / 정산집계 전문 / 큐 모니터 --- */
#include "MG_PANMASK.h"
#include "MG_AMTCHECK.h"
#include "MG_ADVICE.h"
#include "MG_SETTLEREQ.h"
#include "MG_SETTLERSP.h"
#include "MG_QDEPTH.h"
/* ----- advertise table: {service name, function} (fn is extern from its file) */
static struct { const char *name; void (*fn)(TPSVCINFO *); } SVCS[] = {
@ -67,6 +121,29 @@ static struct { const char *name; void (*fn)(TPSVCINFO *); } SVCS[] = {
{"MG_DEQUEUE", MG_DEQUEUE}, {"MG_RETRY", MG_RETRY},
{"MG_STATS", MG_STATS}, {"MG_CHANSTAT", MG_CHANSTAT},
{"MG_STATUS", MG_STATUS}, {"MG_CONFIRM", MG_CONFIRM},
{"MG_AUTHREQ", MG_AUTHREQ}, {"MG_AUTHRSP", MG_AUTHRSP},
{"MG_CAPTURE", MG_CAPTURE}, {"MG_CAPRSP", MG_CAPRSP},
{"MG_REVERSAL", MG_REVERSAL}, {"MG_REVADVICE", MG_REVADVICE},
{"MG_NETMGMT", MG_NETMGMT}, {"MG_VAN1ADPT", MG_VAN1ADPT},
{"MG_VAN2ADPT", MG_VAN2ADPT}, {"MG_DIRECTADPT", MG_DIRECTADPT},
{"MG_REASM", MG_REASM}, {"MG_MACGEN", MG_MACGEN},
{"MG_MACVERIFY", MG_MACVERIFY}, {"MG_KEYROTATE", MG_KEYROTATE},
{"MG_KEYSTATUS", MG_KEYSTATUS}, {"MG_TIMEOUT", MG_TIMEOUT},
{"MG_TIMEOUTSET", MG_TIMEOUTSET}, {"MG_DUPCHK", MG_DUPCHK},
{"MG_GAPSCAN", MG_GAPSCAN}, {"MG_SEQAUDIT", MG_SEQAUDIT},
{"MG_TOISO", MG_TOISO}, {"MG_FROMISO", MG_FROMISO},
{"MG_RTADD", MG_RTADD}, {"MG_RTDROP", MG_RTDROP},
{"MG_RTLIST", MG_RTLIST}, {"MG_RTSWITCH", MG_RTSWITCH},
{"MG_CHANOPEN", MG_CHANOPEN}, {"MG_CHANCLOSE", MG_CHANCLOSE},
{"MG_CHANSWITCH", MG_CHANSWITCH}, {"MG_CHANHEALTH", MG_CHANHEALTH},
{"MG_STATMTI", MG_STATMTI}, {"MG_STATHOUR", MG_STATHOUR},
{"MG_STATDIR", MG_STATDIR}, {"MG_STATRC", MG_STATRC},
{"MG_ERRRETRY", MG_ERRRETRY}, {"MG_ERRLIST", MG_ERRLIST},
{"MG_ERRPURGE", MG_ERRPURGE}, {"MG_THROTTLE", MG_THROTTLE},
{"MG_TPSCHECK", MG_TPSCHECK}, {"MG_PANMASK", MG_PANMASK},
{"MG_AMTCHECK", MG_AMTCHECK}, {"MG_ADVICE", MG_ADVICE},
{"MG_SETTLEREQ", MG_SETTLEREQ}, {"MG_SETTLERSP", MG_SETTLERSP},
{"MG_QDEPTH", MG_QDEPTH},
{NULL, NULL}
};

View file

@ -0,0 +1,12 @@
/*
* MG_ADVICE.h - mg () MG_ADVICE (copybook / record header).
* Split out of the former inline mg_svr.pgc; advertised by mg_svr.
*/
#ifndef MG_ADVICE_H
#define MG_ADVICE_H
#include "mg_svc.h"
void MG_ADVICE(TPSVCINFO *p);
#endif /* MG_ADVICE_H */

View file

@ -0,0 +1,39 @@
/*
* MG_ADVICE.pgc - mg 모듈 서비스 MG_ADVICE (one service = one file).
* Split out of the former inline mg_svr.pgc; runs on the caller's XA
* branch (no EXEC SQL CONNECT). Advertised by mg_svr at tpsvrinit.
*/
#include "MG_ADVICE.h"
/* 72. MG_ADVICE - 0220 매입 어드바이스 수신: 응답이 필요 없는 통지 전문이라
* 즉시 수용(A) 적재하고, 당일 누적 어드바이스 건수를 회신해 폭주를 감시. */
void MG_ADVICE(TPSVCINFO *p)
{
UBFH *b = (UBFH *)p->data;
char chan[16], raw[512], bizdate[16];
long stan, msg_id, amount;
EXEC SQL BEGIN DECLARE SECTION;
char h_biz[16];
long h_advcnt;
EXEC SQL END DECLARE SECTION;
gets_(b, T_CHANNEL, chan, sizeof(chan)); if (chan[0] == 0) strcpy(chan, "CH02");
gets_(b, T_STR3, raw, sizeof(raw));
gets_(b, T_BIZDATE, bizdate, sizeof(bizdate));
amount = getl(b, T_AMOUNT);
stan = mgdb_next_stan(); if (stan < 0) FAIL(b);
msg_id = mgdb_next_msg_id(); if (msg_id < 0) FAIL(b);
if (mgdb_insert_msg(msg_id, stan, "0220", chan, "IN", "000000", "", amount,
"", "00", raw, "A", bizdate) < 0) FAIL(b);
strncpy(h_biz, bizdate, sizeof(h_biz)-1); h_biz[sizeof(h_biz)-1] = 0;
EXEC SQL SELECT count(*) INTO :h_advcnt
FROM mg_msg_log WHERE mti = '0220' AND biz_date = :h_biz;
if (sqlca.sqlcode < 0) FAIL(b);
setl(b, T_ID1, msg_id);
setl(b, T_COUNT, h_advcnt);
setl(b, T_RC, (h_advcnt > 1000) ? 1 : 0); /* 어드바이스 폭주 경보 */
OK(b);
}

View file

@ -0,0 +1,12 @@
/*
* MG_AMTCHECK.h - mg () MG_AMTCHECK (copybook / record header).
* Split out of the former inline mg_svr.pgc; advertised by mg_svr.
*/
#ifndef MG_AMTCHECK_H
#define MG_AMTCHECK_H
#include "mg_svc.h"
void MG_AMTCHECK(TPSVCINFO *p);
#endif /* MG_AMTCHECK_H */

View file

@ -0,0 +1,31 @@
/*
* MG_AMTCHECK.pgc - mg 모듈 서비스 MG_AMTCHECK (one service = one file).
* Split out of the former inline mg_svr.pgc; runs on the caller's XA
* branch (no EXEC SQL CONNECT). Advertised by mg_svr at tpsvrinit.
*/
#include "MG_AMTCHECK.h"
/* 71. MG_AMTCHECK - 전문 금액/카드 사전검증: 금액 범위(0 < 금액 <= 한도)와
* PAN Luhn 을 한 번에 검사해 게이트웨이 입구에서 불량 전문을 걸러낸다. */
void MG_AMTCHECK(TPSVCINFO *p)
{
UBFH *b = (UBFH *)p->data;
char pan[24];
long amount, limit, rc = 0;
amount = getl(b, T_AMOUNT);
limit = getl(b, T_AMT4); if (limit <= 0) limit = 100000000L; /* 기본 한도 1억 */
gets_(b, T_STR5, pan, sizeof(pan));
if (amount <= 0) rc = 13; /* 13 = 금액 오류 */
else if (amount > limit) rc = 61; /* 61 = 한도 초과 */
else if (pan[0] && !acq_luhn_ok(pan)) rc = 14; /* 14 = 무효 카드번호 */
if (rc != 0)
userlog("MG_AMTCHECK 거절 rc=%ld 금액=%ld 한도=%ld", rc, amount, limit);
setl(b, T_AMT4, limit);
setl(b, T_RC, rc);
if (rc != 0) FAIL(b);
OK(b);
}

View file

@ -0,0 +1,12 @@
/*
* MG_AUTHREQ.h - mg () MG_AUTHREQ (copybook / record header).
* Split out of the former inline mg_svr.pgc; advertised by mg_svr.
*/
#ifndef MG_AUTHREQ_H
#define MG_AUTHREQ_H
#include "mg_svc.h"
void MG_AUTHREQ(TPSVCINFO *p);
#endif /* MG_AUTHREQ_H */

View file

@ -0,0 +1,50 @@
/*
* MG_AUTHREQ.pgc - mg 모듈 서비스 MG_AUTHREQ (one service = one file).
* Split out of the former inline mg_svr.pgc; runs on the caller's XA
* branch (no EXEC SQL CONNECT). Advertised by mg_svr at tpsvrinit.
*/
#include "MG_AUTHREQ.h"
/* ----- service-local pure helper(s) ----- */
/* PAN 앞6/뒤4 만 남기고 '*' 마스킹 (승인요청 로그 저장용). */
static void mg_authreq_mask(const char *pan, char *out /* >= 20 */)
{
int i, n = (int)strlen(pan);
for (i = 0; i < n && i < 19; i++)
out[i] = (i < 6 || i >= n - 4) ? pan[i] : '*';
out[i] = 0;
}
/* 31. MG_AUTHREQ - 0100 승인요청 전문 수신: 고정길이 파싱(MTI/처리코드/금액/PAN),
* PAN Luhn 검증 후 수신 원장 적재. Luhn 실패 시 rc=14(무효카드)로 기록. */
void MG_AUTHREQ(TPSVCINFO *p)
{
UBFH *b = (UBFH *)p->data;
char chan[16], raw[512], bizdate[16], proc[8], pan[24], masked[24], rc[4];
long stan, msg_id, amount, n;
gets_(b, T_CHANNEL, chan, sizeof(chan)); if (chan[0] == 0) strcpy(chan, "CH01");
gets_(b, T_STR3, raw, sizeof(raw));
gets_(b, T_BIZDATE, bizdate, sizeof(bizdate));
n = (long)strlen(raw);
/* 0100 고정길이: [0..3]=MTI(0100 강제) [4..9]=처리코드 [10..21]=금액 [22..37]=PAN */
memset(proc, 0, sizeof(proc)); memset(pan, 0, sizeof(pan));
if (n >= 10) strncpy(proc, raw + 4, 6); else strcpy(proc, "000000");
amount = (n >= 22) ? atol(raw + 10) : getl(b, T_AMOUNT);
if (n >= 38) strncpy(pan, raw + 22, 16);
strcpy(rc, (pan[0] && !acq_luhn_ok(pan)) ? "14" : "00");
mg_authreq_mask(pan, masked);
stan = mgdb_next_stan(); if (stan < 0) FAIL(b);
msg_id = mgdb_next_msg_id(); if (msg_id < 0) FAIL(b);
if (mgdb_insert_msg(msg_id, stan, "0100", chan, "IN", proc, "", amount,
masked, rc, raw, "R", bizdate) < 0) FAIL(b);
sets_(b, T_STR2, rc);
sets_(b, T_STR5, masked);
setl(b, T_ID1, msg_id);
setl(b, T_ARG1, stan);
OK(b);
}

View file

@ -0,0 +1,12 @@
/*
* MG_AUTHRSP.h - mg () MG_AUTHRSP (copybook / record header).
* Split out of the former inline mg_svr.pgc; advertised by mg_svr.
*/
#ifndef MG_AUTHRSP_H
#define MG_AUTHRSP_H
#include "mg_svc.h"
void MG_AUTHRSP(TPSVCINFO *p);
#endif /* MG_AUTHRSP_H */

View file

@ -0,0 +1,34 @@
/*
* MG_AUTHRSP.pgc - mg 모듈 서비스 MG_AUTHRSP (one service = one file).
* Split out of the former inline mg_svr.pgc; runs on the caller's XA
* branch (no EXEC SQL CONNECT). Advertised by mg_svr at tpsvrinit.
*/
#include "MG_AUTHRSP.h"
/* 32. MG_AUTHRSP - 0110 승인응답 생성: 승인한도(5천만원) 초과 시 rc=61 거절,
* 이내면 rc=00 승인. 원 0100 전문의 rc/상태를 갱신하고 0110 송신 로그 적재. */
void MG_AUTHRSP(TPSVCINFO *p)
{
UBFH *b = (UBFH *)p->data;
char chan[16], bizdate[16], rc[4];
long orig_id, stan, msg_id, amount;
const long AUTH_LIMIT = 50000000L; /* 건당 승인한도 5천만원 */
gets_(b, T_CHANNEL, chan, sizeof(chan)); if (chan[0] == 0) strcpy(chan, "CH01");
gets_(b, T_BIZDATE, bizdate, sizeof(bizdate));
orig_id = getl(b, T_ID1);
amount = getl(b, T_AMOUNT);
strcpy(rc, (amount > AUTH_LIMIT) ? "61" : "00"); /* 61 = 한도초과 */
if (orig_id > 0 && mgdb_update_msg_rc(orig_id, rc, "C") < 0) FAIL(b);
stan = mgdb_next_stan(); if (stan < 0) FAIL(b);
msg_id = mgdb_next_msg_id(); if (msg_id < 0) FAIL(b);
if (mgdb_insert_msg(msg_id, stan, "0110", chan, "OUT", "000000", "", amount,
"", rc, "", "S", bizdate) < 0) FAIL(b);
sets_(b, T_STR2, rc);
setl(b, T_ID2, msg_id);
setl(b, T_RC, (rc[0] == '0' && rc[1] == '0') ? 0 : 1);
OK(b);
}

View file

@ -0,0 +1,12 @@
/*
* MG_CAPRSP.h - mg () MG_CAPRSP (copybook / record header).
* Split out of the former inline mg_svr.pgc; advertised by mg_svr.
*/
#ifndef MG_CAPRSP_H
#define MG_CAPRSP_H
#include "mg_svc.h"
void MG_CAPRSP(TPSVCINFO *p);
#endif /* MG_CAPRSP_H */

View file

@ -0,0 +1,35 @@
/*
* MG_CAPRSP.pgc - mg 모듈 서비스 MG_CAPRSP (one service = one file).
* Split out of the former inline mg_svr.pgc; runs on the caller's XA
* branch (no EXEC SQL CONNECT). Advertised by mg_svr at tpsvrinit.
*/
#include "MG_CAPRSP.h"
/* 34. MG_CAPRSP - 0210 매입응답 처리: STAN 으로 원 0200 전문을 찾아
* 확인(C) 전이시키고 0210 송신 로그 적재. 원거래 없으면 rc=25. */
void MG_CAPRSP(TPSVCINFO *p)
{
UBFH *b = (UBFH *)p->data;
char chan[16], bizdate[16];
long stan, orig_id, msg_id, out_stan;
gets_(b, T_CHANNEL, chan, sizeof(chan)); if (chan[0] == 0) strcpy(chan, "CH02");
gets_(b, T_BIZDATE, bizdate, sizeof(bizdate));
stan = getl(b, T_ARG1);
orig_id = mgdb_find_msg_by_stan(stan);
if (orig_id < 0) { /* 25 = 원거래 없음 */
setl(b, T_RC, 25);
OK(b);
}
if (mgdb_update_msg_rc(orig_id, "00", "C") < 0) FAIL(b);
out_stan = mgdb_next_stan(); if (out_stan < 0) FAIL(b);
msg_id = mgdb_next_msg_id(); if (msg_id < 0) FAIL(b);
if (mgdb_insert_msg(msg_id, out_stan, "0210", chan, "OUT", "000000", "", 0,
"", "00", "", "S", bizdate) < 0) FAIL(b);
setl(b, T_ID1, orig_id);
setl(b, T_ID2, msg_id);
setl(b, T_RC, 0);
OK(b);
}

View file

@ -0,0 +1,12 @@
/*
* MG_CAPTURE.h - mg () MG_CAPTURE (copybook / record header).
* Split out of the former inline mg_svr.pgc; advertised by mg_svr.
*/
#ifndef MG_CAPTURE_H
#define MG_CAPTURE_H
#include "mg_svc.h"
void MG_CAPTURE(TPSVCINFO *p);
#endif /* MG_CAPTURE_H */

View file

@ -0,0 +1,35 @@
/*
* MG_CAPTURE.pgc - mg 모듈 서비스 MG_CAPTURE (one service = one file).
* Split out of the former inline mg_svr.pgc; runs on the caller's XA
* branch (no EXEC SQL CONNECT). Advertised by mg_svr at tpsvrinit.
*/
#include "MG_CAPTURE.h"
/* 33. MG_CAPTURE - 0200 매입요청 전문 처리: MDR(bps) 수수료/정산액을 산출해
* 수신 원장에 적재하고 수수료 내역을 회신 (전문게이트웨이 매입 접수). */
void MG_CAPTURE(TPSVCINFO *p)
{
UBFH *b = (UBFH *)p->data;
char chan[16], raw[512], bizdate[16];
long stan, msg_id, amount, bps, fee, net;
gets_(b, T_CHANNEL, chan, sizeof(chan)); if (chan[0] == 0) strcpy(chan, "CH02");
gets_(b, T_STR3, raw, sizeof(raw));
gets_(b, T_BIZDATE, bizdate, sizeof(bizdate));
amount = getl(b, T_AMOUNT);
bps = getl(b, T_ARG2); if (bps <= 0) bps = 25; /* 기본 MDR 0.25% */
if (amount <= 0) { setl(b, T_RC, 13); OK(b); } /* 13 = 금액오류 */
fee = acq_fee(amount, bps);
net = amount - fee;
stan = mgdb_next_stan(); if (stan < 0) FAIL(b);
msg_id = mgdb_next_msg_id(); if (msg_id < 0) FAIL(b);
if (mgdb_insert_msg(msg_id, stan, "0200", chan, "IN", "000000", "", amount,
"", "00", raw, "R", bizdate) < 0) FAIL(b);
setl(b, T_FEE, fee);
setl(b, T_NET, net);
setl(b, T_ID1, msg_id);
setl(b, T_RC, 0);
OK(b);
}

View file

@ -0,0 +1,12 @@
/*
* MG_CHANCLOSE.h - mg () MG_CHANCLOSE (copybook / record header).
* Split out of the former inline mg_svr.pgc; advertised by mg_svr.
*/
#ifndef MG_CHANCLOSE_H
#define MG_CHANCLOSE_H
#include "mg_svc.h"
void MG_CHANCLOSE(TPSVCINFO *p);
#endif /* MG_CHANCLOSE_H */

View file

@ -0,0 +1,36 @@
/*
* MG_CHANCLOSE.pgc - mg 모듈 서비스 MG_CHANCLOSE (one service = one file).
* Split out of the former inline mg_svr.pgc; runs on the caller's XA
* branch (no EXEC SQL CONNECT). Advertised by mg_svr at tpsvrinit.
*/
#include "MG_CHANCLOSE.h"
/* 58. MG_CHANCLOSE - 채널 폐쇄: 채널을 DOWN 으로 내리고 세션키를 폐기하되,
* 그 채널에 대기(Q) 중인 송신 큐 잔량을 세어 함께 회신 (폐쇄 영향도). */
void MG_CHANCLOSE(TPSVCINFO *p)
{
UBFH *b = (UBFH *)p->data;
EXEC SQL BEGIN DECLARE SECTION;
char h_chan[16];
long h_pending;
EXEC SQL END DECLARE SECTION;
gets_(b, T_CHANNEL, h_chan, sizeof(h_chan)); if (h_chan[0] == 0) strcpy(h_chan, "CH01");
EXEC SQL SELECT count(*) INTO :h_pending
FROM mg_queue WHERE channel = :h_chan AND status = 'Q';
if (sqlca.sqlcode < 0) FAIL(b);
EXEC SQL UPDATE mg_channel
SET status = 'DOWN', session_key = '', updated_at = now()
WHERE channel = :h_chan;
if (sqlca.sqlcode < 0) { userlog("MG_CHANCLOSE 폐쇄 FAIL [%d]", sqlca.sqlcode); FAIL(b); }
if (sqlca.sqlerrd[2] == 0) { setl(b, T_RC, 25); FAIL(b); } /* 미등록 채널 */
if (h_pending > 0)
userlog("MG_CHANCLOSE %s 폐쇄: 대기 큐 %ld건 잔류", h_chan, h_pending);
sets_(b, T_STATUS, "DOWN");
setl(b, T_COUNT, h_pending);
setl(b, T_RC, (h_pending > 0) ? 1 : 0); /* 잔량 있으면 경고 */
OK(b);
}

View file

@ -0,0 +1,12 @@
/*
* MG_CHANHEALTH.h - mg () MG_CHANHEALTH (copybook / record header).
* Split out of the former inline mg_svr.pgc; advertised by mg_svr.
*/
#ifndef MG_CHANHEALTH_H
#define MG_CHANHEALTH_H
#include "mg_svc.h"
void MG_CHANHEALTH(TPSVCINFO *p);
#endif /* MG_CHANHEALTH_H */

View file

@ -0,0 +1,51 @@
/*
* MG_CHANHEALTH.pgc - mg 모듈 서비스 MG_CHANHEALTH (one service = one file).
* Split out of the former inline mg_svr.pgc; runs on the caller's XA
* branch (no EXEC SQL CONNECT). Advertised by mg_svr at tpsvrinit.
*/
#include "MG_CHANHEALTH.h"
/* 60. MG_CHANHEALTH - 채널 종합 건강점검: 상태(UP 여부) + 당일 오류율(bps) +
* 큐 적체를 합산한 0~100 건강점수를 산출 (60 미만이면 절체 권고 rc=1). */
void MG_CHANHEALTH(TPSVCINFO *p)
{
UBFH *b = (UBFH *)p->data;
long score, err_bps, up;
char status[16];
EXEC SQL BEGIN DECLARE SECTION;
char h_chan[16], h_bizdate[16];
long h_total, h_err, h_backlog;
EXEC SQL END DECLARE SECTION;
gets_(b, T_CHANNEL, h_chan, sizeof(h_chan)); if (h_chan[0] == 0) strcpy(h_chan, "CH01");
gets_(b, T_BIZDATE, h_bizdate, sizeof(h_bizdate));
if (mgdb_get_channel_status(h_chan, status) < 0) { setl(b, T_RC, 25); FAIL(b); }
up = (strcmp(status, "UP") == 0) ? 1 : 0;
EXEC SQL SELECT count(*),
coalesce(sum(CASE WHEN rc <> '00' THEN 1 ELSE 0 END),0)
INTO :h_total, :h_err
FROM mg_msg_log WHERE channel = :h_chan AND biz_date = :h_bizdate;
if (sqlca.sqlcode < 0) FAIL(b);
EXEC SQL SELECT count(*) INTO :h_backlog
FROM mg_queue WHERE channel = :h_chan AND status IN ('Q','F');
if (sqlca.sqlcode < 0) FAIL(b);
err_bps = (h_total > 0) ? (h_err * 10000L) / h_total : 0;
/* 점수: 기본 100, 다운 -50, 오류율 100bps당 -10 (최대 -30), 적체 10건당 -5 (최대 -20) */
score = 100;
if (!up) score -= 50;
score -= (err_bps / 100L) * 10L > 30 ? 30 : (err_bps / 100L) * 10L;
score -= (h_backlog / 10L) * 5L > 20 ? 20 : (h_backlog / 10L) * 5L;
if (score < 0) score = 0;
sets_(b, T_STATUS, status);
setl(b, T_ARG3, err_bps);
setl(b, T_ARG7, h_backlog);
setl(b, T_ARG8, score);
setl(b, T_RC, (score < 60) ? 1 : 0);
OK(b);
}

View file

@ -0,0 +1,12 @@
/*
* MG_CHANOPEN.h - mg () MG_CHANOPEN (copybook / record header).
* Split out of the former inline mg_svr.pgc; advertised by mg_svr.
*/
#ifndef MG_CHANOPEN_H
#define MG_CHANOPEN_H
#include "mg_svc.h"
void MG_CHANOPEN(TPSVCINFO *p);
#endif /* MG_CHANOPEN_H */

View file

@ -0,0 +1,33 @@
/*
* MG_CHANOPEN.pgc - mg 모듈 서비스 MG_CHANOPEN (one service = one file).
* Split out of the former inline mg_svr.pgc; runs on the caller's XA
* branch (no EXEC SQL CONNECT). Advertised by mg_svr at tpsvrinit.
*/
#include "MG_CHANOPEN.h"
/* 57. MG_CHANOPEN - 채널 개통: DOWN 채널만 UP 으로 올리고 sign-on 시각을 기록.
* 이미 UP 이면 rc=1 (이중 개통 무해화), 미등록 채널이면 rc=25. */
void MG_CHANOPEN(TPSVCINFO *p)
{
UBFH *b = (UBFH *)p->data;
char chan[16], cur[16];
EXEC SQL BEGIN DECLARE SECTION;
char h_chan[16];
EXEC SQL END DECLARE SECTION;
gets_(b, T_CHANNEL, chan, sizeof(chan)); if (chan[0] == 0) strcpy(chan, "CH01");
if (mgdb_get_channel_status(chan, cur) < 0) { setl(b, T_RC, 25); FAIL(b); }
if (strcmp(cur, "UP") == 0) { sets_(b, T_STATUS, cur); setl(b, T_RC, 1); OK(b); }
strncpy(h_chan, chan, sizeof(h_chan)-1); h_chan[sizeof(h_chan)-1] = 0;
EXEC SQL UPDATE mg_channel
SET status = 'UP', signon_at = now(), updated_at = now()
WHERE channel = :h_chan;
if (sqlca.sqlcode < 0) { userlog("MG_CHANOPEN 개통 FAIL [%d]", sqlca.sqlcode); FAIL(b); }
userlog("MG_CHANOPEN 채널 개통 %s (%s -> UP)", chan, cur);
sets_(b, T_STATUS, "UP");
sets_(b, T_STR5, cur); /* 개통 전 상태 */
setl(b, T_RC, 0);
OK(b);
}

View file

@ -0,0 +1,12 @@
/*
* MG_CHANSWITCH.h - mg () MG_CHANSWITCH (copybook / record header).
* Split out of the former inline mg_svr.pgc; advertised by mg_svr.
*/
#ifndef MG_CHANSWITCH_H
#define MG_CHANSWITCH_H
#include "mg_svc.h"
void MG_CHANSWITCH(TPSVCINFO *p);
#endif /* MG_CHANSWITCH_H */

View file

@ -0,0 +1,48 @@
/*
* MG_CHANSWITCH.pgc - mg 모듈 서비스 MG_CHANSWITCH (one service = one file).
* Split out of the former inline mg_svr.pgc; runs on the caller's XA
* branch (no EXEC SQL CONNECT). Advertised by mg_svr at tpsvrinit.
*/
#include "MG_CHANSWITCH.h"
/* 59. MG_CHANSWITCH - 채널 절체: 주채널을 DOWN, 예비채널을 UP 으로 한 트랜잭션
* 안에서 뒤집고, 주채널 대기 큐를 예비채널로 이관 (게이트웨이 페일오버). */
void MG_CHANSWITCH(TPSVCINFO *p)
{
UBFH *b = (UBFH *)p->data;
char pri[16], bak[16];
long moved;
EXEC SQL BEGIN DECLARE SECTION;
char h_pri[16], h_bak[16];
EXEC SQL END DECLARE SECTION;
gets_(b, T_CHANNEL, pri, sizeof(pri)); if (pri[0] == 0) strcpy(pri, "CH01");
gets_(b, T_STR2, bak, sizeof(bak)); if (bak[0] == 0) strcpy(bak, "CH02");
if (strcmp(pri, bak) == 0) { setl(b, T_RC, 30); FAIL(b); } /* 자기 자신 절체 불가 */
strncpy(h_pri, pri, sizeof(h_pri)-1); h_pri[sizeof(h_pri)-1] = 0;
strncpy(h_bak, bak, sizeof(h_bak)-1); h_bak[sizeof(h_bak)-1] = 0;
EXEC SQL UPDATE mg_channel SET status = 'DOWN', updated_at = now()
WHERE channel = :h_pri;
if (sqlca.sqlcode < 0) FAIL(b);
if (sqlca.sqlerrd[2] == 0) { setl(b, T_RC, 25); FAIL(b); }
EXEC SQL UPDATE mg_channel SET status = 'UP', signon_at = now(), updated_at = now()
WHERE channel = :h_bak;
if (sqlca.sqlcode < 0) FAIL(b);
if (sqlca.sqlerrd[2] == 0) { setl(b, T_RC, 25); FAIL(b); }
/* 주채널에 물려 있던 대기 전문을 예비채널로 이관 */
EXEC SQL UPDATE mg_queue SET channel = :h_bak, updated_at = now()
WHERE channel = :h_pri AND status = 'Q';
if (sqlca.sqlcode < 0) FAIL(b);
moved = (long)sqlca.sqlerrd[2];
userlog("MG_CHANSWITCH 절체 %s -> %s (큐 이관 %ld건)", pri, bak, moved);
sets_(b, T_STR5, pri);
sets_(b, T_STR2, bak);
setl(b, T_COUNT, moved);
setl(b, T_RC, 0);
OK(b);
}

View file

@ -0,0 +1,12 @@
/*
* MG_DIRECTADPT.h - mg () MG_DIRECTADPT (copybook / record header).
* Split out of the former inline mg_svr.pgc; advertised by mg_svr.
*/
#ifndef MG_DIRECTADPT_H
#define MG_DIRECTADPT_H
#include "mg_svc.h"
void MG_DIRECTADPT(TPSVCINFO *p);
#endif /* MG_DIRECTADPT_H */

View file

@ -0,0 +1,45 @@
/*
* MG_DIRECTADPT.pgc - mg 모듈 서비스 MG_DIRECTADPT (one service = one file).
* Split out of the former inline mg_svr.pgc; runs on the caller's XA
* branch (no EXEC SQL CONNECT). Advertised by mg_svr at tpsvrinit.
*/
#include "MG_DIRECTADPT.h"
/* ----- service-local pure helper(s) ----- */
/* 구간 [s, s+n) 이 전부 십진 숫자인지 검사 (직연동 전문 필드 검증). */
static int mg_direct_digits(const char *s, int n)
{
int i;
for (i = 0; i < n; i++)
if (s[i] < '0' || s[i] > '9') return 0;
return 1;
}
/* 40. MG_DIRECTADPT - 직연동 채널 어댑터: 이미 내부 고정길이 포맷이므로 변환 없이
* 길이(>=22)와 MTI(4)/처리코드(6)/금액(12) 숫자 여부만 엄격 검증. */
void MG_DIRECTADPT(TPSVCINFO *p)
{
UBFH *b = (UBFH *)p->data;
char raw[512], mti[8];
long n, amount;
gets_(b, T_STR3, raw, sizeof(raw));
n = (long)strlen(raw);
if (n < 22) { setl(b, T_RC, 31); FAIL(b); } /* 길이 부족 */
if (!mg_direct_digits(raw, 4)) { setl(b, T_RC, 32); FAIL(b); } /* MTI 오류 */
if (!mg_direct_digits(raw + 4, 6)) { setl(b, T_RC, 33); FAIL(b); } /* 처리코드 오류 */
if (!mg_direct_digits(raw + 10, 12)) { setl(b, T_RC, 34); FAIL(b); } /* 금액 오류 */
memset(mti, 0, sizeof(mti));
strncpy(mti, raw, 4);
amount = atol(raw + 10);
sets_(b, T_STR1, mti);
setl(b, T_AMOUNT, amount);
sets_(b, T_CHANNEL, "CH03"); /* 직연동 = CH03 고정 매핑 */
setl(b, T_COUNT, n);
setl(b, T_RC, 0);
OK(b);
}

View file

@ -0,0 +1,12 @@
/*
* MG_DUPCHK.h - mg () MG_DUPCHK (copybook / record header).
* Split out of the former inline mg_svr.pgc; advertised by mg_svr.
*/
#ifndef MG_DUPCHK_H
#define MG_DUPCHK_H
#include "mg_svc.h"
void MG_DUPCHK(TPSVCINFO *p);
#endif /* MG_DUPCHK_H */

View file

@ -0,0 +1,35 @@
/*
* MG_DUPCHK.pgc - mg 모듈 서비스 MG_DUPCHK (one service = one file).
* Split out of the former inline mg_svr.pgc; runs on the caller's XA
* branch (no EXEC SQL CONNECT). Advertised by mg_svr at tpsvrinit.
*/
#include "MG_DUPCHK.h"
/* 48. MG_DUPCHK - 중복전문 검출: 동일 STAN 이 당일 원장에 이미 몇 건인지 세어
* 2건 이상이면 중복(rc=94) 판정. 중복 초과분 건수를 함께 회신. */
void MG_DUPCHK(TPSVCINFO *p)
{
UBFH *b = (UBFH *)p->data;
EXEC SQL BEGIN DECLARE SECTION;
long h_stan, h_cnt;
char h_bizdate[16];
EXEC SQL END DECLARE SECTION;
gets_(b, T_BIZDATE, h_bizdate, sizeof(h_bizdate));
h_stan = getl(b, T_ARG1);
if (h_stan <= 0) { setl(b, T_RC, 13); FAIL(b); }
EXEC SQL SELECT count(*) INTO :h_cnt
FROM mg_msg_log WHERE stan = :h_stan AND biz_date = :h_bizdate;
if (sqlca.sqlcode < 0) FAIL(b);
if (h_cnt > 1) {
userlog("MG_DUPCHK 중복 STAN=%ld 건수=%ld", h_stan, h_cnt);
setl(b, T_RC, 94); /* 94 = 중복 전문 */
} else {
setl(b, T_RC, 0);
}
setl(b, T_COUNT, h_cnt);
setl(b, T_ARG2, (h_cnt > 1) ? h_cnt - 1 : 0);
OK(b);
}

View file

@ -0,0 +1,12 @@
/*
* MG_ERRLIST.h - mg () MG_ERRLIST (copybook / record header).
* Split out of the former inline mg_svr.pgc; advertised by mg_svr.
*/
#ifndef MG_ERRLIST_H
#define MG_ERRLIST_H
#include "mg_svc.h"
void MG_ERRLIST(TPSVCINFO *p);
#endif /* MG_ERRLIST_H */

View file

@ -0,0 +1,33 @@
/*
* MG_ERRLIST.pgc - mg 모듈 서비스 MG_ERRLIST (one service = one file).
* Split out of the former inline mg_svr.pgc; runs on the caller's XA
* branch (no EXEC SQL CONNECT). Advertised by mg_svr at tpsvrinit.
*/
#include "MG_ERRLIST.h"
/* 66. MG_ERRLIST - 오류전문 대기목록 요약: 당일 NACK(N) 전문의 건수, 금액합,
* 가장 오래된/최신 msg_id 를 회신 (재처리 워크플로의 조회 단계). */
void MG_ERRLIST(TPSVCINFO *p)
{
UBFH *b = (UBFH *)p->data;
EXEC SQL BEGIN DECLARE SECTION;
char h_bizdate[16];
long h_cnt, h_sum, h_first, h_last;
EXEC SQL END DECLARE SECTION;
gets_(b, T_BIZDATE, h_bizdate, sizeof(h_bizdate));
EXEC SQL SELECT count(*), coalesce(sum(amount),0),
coalesce(min(msg_id),0), coalesce(max(msg_id),0)
INTO :h_cnt, :h_sum, :h_first, :h_last
FROM mg_msg_log
WHERE biz_date = :h_bizdate AND status = 'N';
if (sqlca.sqlcode < 0) FAIL(b);
setl(b, T_COUNT, h_cnt);
setl(b, T_GROSS, h_sum);
setl(b, T_ID1, h_first); /* 가장 오래된 오류전문부터 재처리 */
setl(b, T_ID2, h_last);
setl(b, T_RC, (h_cnt > 0) ? 1 : 0);
OK(b);
}

View file

@ -0,0 +1,12 @@
/*
* MG_ERRPURGE.h - mg () MG_ERRPURGE (copybook / record header).
* Split out of the former inline mg_svr.pgc; advertised by mg_svr.
*/
#ifndef MG_ERRPURGE_H
#define MG_ERRPURGE_H
#include "mg_svc.h"
void MG_ERRPURGE(TPSVCINFO *p);
#endif /* MG_ERRPURGE_H */

View file

@ -0,0 +1,35 @@
/*
* MG_ERRPURGE.pgc - mg 모듈 서비스 MG_ERRPURGE (one service = one file).
* Split out of the former inline mg_svr.pgc; runs on the caller's XA
* branch (no EXEC SQL CONNECT). Advertised by mg_svr at tpsvrinit.
*/
#include "MG_ERRPURGE.h"
/* 67. MG_ERRPURGE - 오류전문 폐기: 재처리를 포기한 당일 NACK(N) 전문 중
* 무결성 실패(96)/중복(94) 등 지정 rc 만 골라 삭제 (워크플로 종결 단계). */
void MG_ERRPURGE(TPSVCINFO *p)
{
UBFH *b = (UBFH *)p->data;
char rcsel[4];
long purged;
EXEC SQL BEGIN DECLARE SECTION;
char h_bizdate[16], h_rc[4];
EXEC SQL END DECLARE SECTION;
gets_(b, T_BIZDATE, h_bizdate, sizeof(h_bizdate));
gets_(b, T_STR2, rcsel, sizeof(rcsel));
if (rcsel[0] == 0) strcpy(rcsel, "96"); /* 기본: 무결성 실패 전문만 폐기 */
strncpy(h_rc, rcsel, sizeof(h_rc)-1); h_rc[sizeof(h_rc)-1] = 0;
if (h_rc[0] == '0' && h_rc[1] == '0') { setl(b, T_RC, 30); FAIL(b); } /* 정상 전문 폐기 금지 */
EXEC SQL DELETE FROM mg_msg_log
WHERE biz_date = :h_bizdate AND status = 'N' AND rc = :h_rc;
if (sqlca.sqlcode < 0) { userlog("MG_ERRPURGE 삭제 FAIL [%d]", sqlca.sqlcode); FAIL(b); }
purged = (long)sqlca.sqlerrd[2];
userlog("MG_ERRPURGE rc=%s 폐기 %ld건", rcsel, purged);
setl(b, T_COUNT, purged);
setl(b, T_RC, 0);
OK(b);
}

View file

@ -0,0 +1,12 @@
/*
* MG_ERRRETRY.h - mg () MG_ERRRETRY (copybook / record header).
* Split out of the former inline mg_svr.pgc; advertised by mg_svr.
*/
#ifndef MG_ERRRETRY_H
#define MG_ERRRETRY_H
#include "mg_svc.h"
void MG_ERRRETRY(TPSVCINFO *p);
#endif /* MG_ERRRETRY_H */

View file

@ -0,0 +1,41 @@
/*
* MG_ERRRETRY.pgc - mg 모듈 서비스 MG_ERRRETRY (one service = one file).
* Split out of the former inline mg_svr.pgc; runs on the caller's XA
* branch (no EXEC SQL CONNECT). Advertised by mg_svr at tpsvrinit.
*/
#include "MG_ERRRETRY.h"
/* 65. MG_ERRRETRY - 오류전문 단건 재처리: NACK(N) 된 전문을 rc=00/R 로 리셋하고
* 원문 그대로 송신 큐에 재적재 (오류전문 재처리 워크플로의 실행 단계). */
void MG_ERRRETRY(TPSVCINFO *p)
{
UBFH *b = (UBFH *)p->data;
long msg_id, qid;
EXEC SQL BEGIN DECLARE SECTION;
long h_id, h_stan;
char h_status[4], h_chan[16], h_mti[8], h_raw[512], h_biz[16];
EXEC SQL END DECLARE SECTION;
msg_id = getl(b, T_ID1);
if (msg_id <= 0) { setl(b, T_RC, 13); FAIL(b); }
h_id = msg_id;
EXEC SQL SELECT status, stan, channel, mti, raw_msg, to_char(biz_date,'YYYY-MM-DD')
INTO :h_status, :h_stan, :h_chan, :h_mti, :h_raw, :h_biz
FROM mg_msg_log WHERE msg_id = :h_id;
if (sqlca.sqlcode == 100) { setl(b, T_RC, 25); FAIL(b); }
if (sqlca.sqlcode < 0) FAIL(b);
if (h_status[0] != 'N') { setl(b, T_RC, 1); OK(b); } /* 오류 상태 아님 = 재처리 불필요 */
EXEC SQL UPDATE mg_msg_log SET rc = '00', status = 'R' WHERE msg_id = :h_id;
if (sqlca.sqlcode < 0) FAIL(b);
qid = mgdb_next_queue_id(); if (qid < 0) FAIL(b);
if (mgdb_enqueue(qid, h_stan, h_chan, h_mti, h_raw, h_biz) < 0) FAIL(b);
userlog("MG_ERRRETRY 재처리 msg_id=%ld -> queue_id=%ld", msg_id, qid);
setl(b, T_ID2, qid);
setl(b, T_RC, 0);
OK(b);
}

View file

@ -0,0 +1,12 @@
/*
* MG_FROMISO.h - mg () MG_FROMISO (copybook / record header).
* Split out of the former inline mg_svr.pgc; advertised by mg_svr.
*/
#ifndef MG_FROMISO_H
#define MG_FROMISO_H
#include "mg_svc.h"
void MG_FROMISO(TPSVCINFO *p);
#endif /* MG_FROMISO_H */

View file

@ -0,0 +1,33 @@
/*
* MG_FROMISO.pgc - mg 모듈 서비스 MG_FROMISO (one service = one file).
* Split out of the former inline mg_svr.pgc; runs on the caller's XA
* branch (no EXEC SQL CONNECT). Advertised by mg_svr at tpsvrinit.
*/
#include "MG_FROMISO.h"
/* 52. MG_FROMISO - ISO -> 내부전문 변환: 고정길이 ISO 전문을 잘라
* 콜론 구분 내부포맷 "MTI:처리코드:금액" 으로 풀어낸다 (선행 0 제거). */
void MG_FROMISO(TPSVCINFO *p)
{
UBFH *b = (UBFH *)p->data;
char iso[512], mti[8], proc[8], internal[64];
long amount, n;
gets_(b, T_STR3, iso, sizeof(iso));
n = (long)strlen(iso);
if (n < 22) { setl(b, T_RC, 31); FAIL(b); } /* 최소 22바이트 필요 */
memset(mti, 0, sizeof(mti)); memset(proc, 0, sizeof(proc));
strncpy(mti, iso, 4);
strncpy(proc, iso + 4, 6);
amount = atol(iso + 10); /* atol 이 선행 0 제거 */
sprintf(internal, "%.4s:%.6s:%ld", mti, proc, amount);
sets_(b, T_STR3, internal);
sets_(b, T_STR1, mti);
sets_(b, T_STR4, proc);
setl(b, T_AMOUNT, amount);
setl(b, T_COUNT, (long)strlen(internal));
setl(b, T_RC, 0);
OK(b);
}

View file

@ -0,0 +1,12 @@
/*
* MG_GAPSCAN.h - mg () MG_GAPSCAN (copybook / record header).
* Split out of the former inline mg_svr.pgc; advertised by mg_svr.
*/
#ifndef MG_GAPSCAN_H
#define MG_GAPSCAN_H
#include "mg_svc.h"
void MG_GAPSCAN(TPSVCINFO *p);
#endif /* MG_GAPSCAN_H */

View file

@ -0,0 +1,35 @@
/*
* MG_GAPSCAN.pgc - mg 모듈 서비스 MG_GAPSCAN (one service = one file).
* Split out of the former inline mg_svr.pgc; runs on the caller's XA
* branch (no EXEC SQL CONNECT). Advertised by mg_svr at tpsvrinit.
*/
#include "MG_GAPSCAN.h"
/* 49. MG_GAPSCAN - STAN 순번 갭 검출: 당일 최소~최대 순번 구간 폭에서 실제
* 고유 순번 수를 빼 유실(갭) 개수를 산출. 갭 존재 시 rc=1. */
void MG_GAPSCAN(TPSVCINFO *p)
{
UBFH *b = (UBFH *)p->data;
long gaps;
EXEC SQL BEGIN DECLARE SECTION;
long h_min, h_max, h_uniq;
char h_bizdate[16];
EXEC SQL END DECLARE SECTION;
gets_(b, T_BIZDATE, h_bizdate, sizeof(h_bizdate));
EXEC SQL SELECT coalesce(min(stan),0), coalesce(max(stan),0), count(DISTINCT stan)
INTO :h_min, :h_max, :h_uniq
FROM mg_msg_log WHERE biz_date = :h_bizdate;
if (sqlca.sqlcode < 0) FAIL(b);
gaps = (h_uniq > 0) ? (h_max - h_min + 1) - h_uniq : 0;
if (gaps > 0) userlog("MG_GAPSCAN 순번 갭 %ld건 (구간 %ld..%ld)", gaps, h_min, h_max);
setl(b, T_ARG1, h_min);
setl(b, T_ARG2, h_max);
setl(b, T_COUNT, h_uniq);
setl(b, T_ARG6, gaps);
setl(b, T_RC, (gaps > 0) ? 1 : 0);
OK(b);
}

View file

@ -0,0 +1,12 @@
/*
* MG_KEYROTATE.h - mg () MG_KEYROTATE (copybook / record header).
* Split out of the former inline mg_svr.pgc; advertised by mg_svr.
*/
#ifndef MG_KEYROTATE_H
#define MG_KEYROTATE_H
#include "mg_svc.h"
void MG_KEYROTATE(TPSVCINFO *p);
#endif /* MG_KEYROTATE_H */

View file

@ -0,0 +1,30 @@
/*
* MG_KEYROTATE.pgc - mg 모듈 서비스 MG_KEYROTATE (one service = one file).
* Split out of the former inline mg_svr.pgc; runs on the caller's XA
* branch (no EXEC SQL CONNECT). Advertised by mg_svr at tpsvrinit.
*/
#include "MG_KEYROTATE.h"
/* 44. MG_KEYROTATE - 키 전환(교체): KEYVER 카운터를 올려 새 세대키 번호를 따고,
* 대상 채널의 세션키를 "Kvvvv-ssss" 형식으로 재발급 (키교환 후속 절차). */
void MG_KEYROTATE(TPSVCINFO *p)
{
UBFH *b = (UBFH *)p->data;
char chan[16], newkey[24];
long kv, sess, stan;
gets_(b, T_CHANNEL, chan, sizeof(chan)); if (chan[0] == 0) strcpy(chan, "CH01");
kv = mgdb_seq_bump("KEYVER"); if (kv < 0) FAIL(b);
sess = mgdb_seq_bump("SESSION"); if (sess < 0) FAIL(b);
stan = mgdb_next_stan(); if (stan < 0) FAIL(b);
sprintf(newkey, "K%04ld-%04ld", kv, sess);
if (mgdb_set_channel_session(chan, newkey, stan) < 0) FAIL(b);
userlog("MG_KEYROTATE 채널=%s 키버전=%ld 세션=%ld", chan, kv, sess);
sets_(b, T_KEY1, newkey);
setl(b, T_ARG3, kv);
setl(b, T_ARG2, sess);
OK(b);
}

View file

@ -0,0 +1,12 @@
/*
* MG_KEYSTATUS.h - mg () MG_KEYSTATUS (copybook / record header).
* Split out of the former inline mg_svr.pgc; advertised by mg_svr.
*/
#ifndef MG_KEYSTATUS_H
#define MG_KEYSTATUS_H
#include "mg_svc.h"
void MG_KEYSTATUS(TPSVCINFO *p);
#endif /* MG_KEYSTATUS_H */

View file

@ -0,0 +1,37 @@
/*
* MG_KEYSTATUS.pgc - mg 모듈 서비스 MG_KEYSTATUS (one service = one file).
* Split out of the former inline mg_svr.pgc; runs on the caller's XA
* branch (no EXEC SQL CONNECT). Advertised by mg_svr at tpsvrinit.
*/
#include "MG_KEYSTATUS.h"
/* 45. MG_KEYSTATUS - 키 상태 조회: 현재 키버전(KEYVER)과 채널의 세션키/사인온
* 시각 유무를 함께 회신 (키 전환 필요 여부 판단용 열람 전용 서비스). */
void MG_KEYSTATUS(TPSVCINFO *p)
{
UBFH *b = (UBFH *)p->data;
char chan[16];
EXEC SQL BEGIN DECLARE SECTION;
long h_kv, h_has_signon;
char h_chan[16], h_skey[64];
EXEC SQL END DECLARE SECTION;
gets_(b, T_CHANNEL, chan, sizeof(chan)); if (chan[0] == 0) strcpy(chan, "CH01");
strncpy(h_chan, chan, sizeof(h_chan)-1); h_chan[sizeof(h_chan)-1] = 0;
h_kv = 0;
EXEC SQL SELECT seq_val INTO :h_kv FROM mg_seq WHERE seq_name = 'KEYVER';
if (sqlca.sqlcode < 0) FAIL(b);
EXEC SQL SELECT session_key, CASE WHEN signon_at IS NULL THEN 0 ELSE 1 END
INTO :h_skey, :h_has_signon
FROM mg_channel WHERE channel = :h_chan;
if (sqlca.sqlcode == 100) { setl(b, T_RC, 25); FAIL(b); } /* 미등록 채널 */
if (sqlca.sqlcode < 0) FAIL(b);
sets_(b, T_KEY1, h_skey);
setl(b, T_ARG3, h_kv);
setl(b, T_ARG4, h_has_signon);
setl(b, T_RC, (h_skey[0] == 0) ? 1 : 0); /* 세션키 미발급이면 1 */
OK(b);
}

View file

@ -0,0 +1,12 @@
/*
* MG_MACGEN.h - mg () MG_MACGEN (copybook / record header).
* Split out of the former inline mg_svr.pgc; advertised by mg_svr.
*/
#ifndef MG_MACGEN_H
#define MG_MACGEN_H
#include "mg_svc.h"
void MG_MACGEN(TPSVCINFO *p);
#endif /* MG_MACGEN_H */

View file

@ -0,0 +1,45 @@
/*
* MG_MACGEN.pgc - mg 모듈 서비스 MG_MACGEN (one service = one file).
* Split out of the former inline mg_svr.pgc; runs on the caller's XA
* branch (no EXEC SQL CONNECT). Advertised by mg_svr at tpsvrinit.
*/
#include "MG_MACGEN.h"
/* ----- service-local pure helper(s) ----- */
/* XOR-회전 8-hex MAC: 키버전을 시드로 섞어 세대별로 값이 달라진다. */
static void mg_macgen_calc(const char *s, long keyver, char *out /* >= 9 */)
{
unsigned long acc = 0x9E3779B9UL ^ (unsigned long)keyver;
int i;
for (i = 0; s[i]; i++) {
acc ^= (unsigned char)s[i];
acc = (acc << 5) | (acc >> 27); /* 5비트 좌회전 */
}
sprintf(out, "%08lX", acc & 0xFFFFFFFFUL);
}
/* 42. MG_MACGEN - 세대키 기반 MAC 생성: 현재 키버전(mg_seq KEYVER)을 시드로
* XOR-회전 MAC 을 만들어 회신 (검증은 MG_MACVERIFY 가 담당). */
void MG_MACGEN(TPSVCINFO *p)
{
UBFH *b = (UBFH *)p->data;
char raw[512], mac[12];
long keyver;
EXEC SQL BEGIN DECLARE SECTION;
long h_kv;
EXEC SQL END DECLARE SECTION;
gets_(b, T_STR3, raw, sizeof(raw));
if (raw[0] == 0) { setl(b, T_RC, 30); FAIL(b); }
EXEC SQL SELECT seq_val INTO :h_kv FROM mg_seq WHERE seq_name = 'KEYVER';
if (sqlca.sqlcode < 0) FAIL(b);
keyver = (sqlca.sqlcode == 100) ? 0 : h_kv;
mg_macgen_calc(raw, keyver, mac);
sets_(b, T_KEY1, mac);
setl(b, T_ARG3, keyver);
setl(b, T_RC, 0);
OK(b);
}

View file

@ -0,0 +1,12 @@
/*
* MG_MACVERIFY.h - mg () MG_MACVERIFY (copybook / record header).
* Split out of the former inline mg_svr.pgc; advertised by mg_svr.
*/
#ifndef MG_MACVERIFY_H
#define MG_MACVERIFY_H
#include "mg_svc.h"
void MG_MACVERIFY(TPSVCINFO *p);
#endif /* MG_MACVERIFY_H */

View file

@ -0,0 +1,50 @@
/*
* MG_MACVERIFY.pgc - mg 모듈 서비스 MG_MACVERIFY (one service = one file).
* Split out of the former inline mg_svr.pgc; runs on the caller's XA
* branch (no EXEC SQL CONNECT). Advertised by mg_svr at tpsvrinit.
*/
#include "MG_MACVERIFY.h"
/* ----- service-local pure helper(s) ----- */
/* MG_MAC 과 동일 계열의 가산 체크섬 (검증측 독립 구현, 4-hex). */
static void mg_macverify_sum(const char *s, char *out /* >= 5 */)
{
unsigned int v = 0; int i;
for (i = 0; s[i]; i++) v = (v + (unsigned char)s[i] * 31u) & 0xFFFF;
sprintf(out, "%04X", v);
}
/* 43. MG_MACVERIFY - 수신전문 MAC 검증: 원장의 raw_msg 로 MAC 을 재계산해
* 저장된 mac 과 대조. 불일치면 rc=96/상태 N(무결성 실패) 전이. */
void MG_MACVERIFY(TPSVCINFO *p)
{
UBFH *b = (UBFH *)p->data;
char calc[8];
long msg_id;
EXEC SQL BEGIN DECLARE SECTION;
long h_id;
char h_mac[16], h_raw[512];
EXEC SQL END DECLARE SECTION;
msg_id = getl(b, T_ID1);
if (msg_id <= 0) { setl(b, T_RC, 25); FAIL(b); }
h_id = msg_id;
EXEC SQL SELECT mac, raw_msg INTO :h_mac, :h_raw
FROM mg_msg_log WHERE msg_id = :h_id;
if (sqlca.sqlcode == 100) { setl(b, T_RC, 25); FAIL(b); }
if (sqlca.sqlcode < 0) FAIL(b);
mg_macverify_sum(h_raw, calc);
if (strncmp(h_mac, calc, 4) == 0) {
setl(b, T_RC, 0); /* MAC 일치 */
} else {
EXEC SQL UPDATE mg_msg_log SET rc = '96', status = 'N' WHERE msg_id = :h_id;
if (sqlca.sqlcode < 0) FAIL(b);
userlog("MG_MACVERIFY 무결성 실패 msg_id=%ld 저장=%s 계산=%s", msg_id, h_mac, calc);
setl(b, T_RC, 96);
}
sets_(b, T_KEY2, calc);
OK(b);
}

View file

@ -0,0 +1,12 @@
/*
* MG_NETMGMT.h - mg () MG_NETMGMT (copybook / record header).
* Split out of the former inline mg_svr.pgc; advertised by mg_svr.
*/
#ifndef MG_NETMGMT_H
#define MG_NETMGMT_H
#include "mg_svc.h"
void MG_NETMGMT(TPSVCINFO *p);
#endif /* MG_NETMGMT_H */

View file

@ -0,0 +1,39 @@
/*
* MG_NETMGMT.pgc - mg 모듈 서비스 MG_NETMGMT (one service = one file).
* Split out of the former inline mg_svr.pgc; runs on the caller's XA
* branch (no EXEC SQL CONNECT). Advertised by mg_svr at tpsvrinit.
*/
#include "MG_NETMGMT.h"
/* 37. MG_NETMGMT - 0800 망관리 전문 라우터: 처리코드 001=사인온(UP),
* 002=사인오프(DOWN), 301=에코(상태유지). 처리 후 0810 응답 MTI 회신. */
void MG_NETMGMT(TPSVCINFO *p)
{
UBFH *b = (UBFH *)p->data;
char chan[16], proc[8], bizdate[16], rc[4];
long stan, msg_id;
gets_(b, T_CHANNEL, chan, sizeof(chan)); if (chan[0] == 0) strcpy(chan, "CH01");
gets_(b, T_STR4, proc, sizeof(proc)); if (proc[0] == 0) strcpy(proc, "301");
gets_(b, T_BIZDATE, bizdate, sizeof(bizdate));
strcpy(rc, "00");
if (strncmp(proc, "001", 3) == 0) {
if (mgdb_set_channel_status(chan, "UP") < 0) FAIL(b);
} else if (strncmp(proc, "002", 3) == 0) {
if (mgdb_set_channel_status(chan, "DOWN") < 0) FAIL(b);
} else if (strncmp(proc, "301", 3) == 0) {
/* 에코: 상태 변경 없음, 수신 자체가 생존 신호 */
} else {
strcpy(rc, "12"); /* 12 = 무효 처리코드 */
}
stan = mgdb_next_stan(); if (stan < 0) FAIL(b);
msg_id = mgdb_next_msg_id(); if (msg_id < 0) FAIL(b);
if (mgdb_insert_msg(msg_id, stan, "0800", chan, "IN", proc, "", 0,
"", rc, "", "R", bizdate) < 0) FAIL(b);
sets_(b, T_STR1, "0810");
sets_(b, T_STR2, rc);
setl(b, T_ID1, msg_id);
OK(b);
}

View file

@ -0,0 +1,12 @@
/*
* MG_PANMASK.h - mg () MG_PANMASK (copybook / record header).
* Split out of the former inline mg_svr.pgc; advertised by mg_svr.
*/
#ifndef MG_PANMASK_H
#define MG_PANMASK_H
#include "mg_svc.h"
void MG_PANMASK(TPSVCINFO *p);
#endif /* MG_PANMASK_H */

View file

@ -0,0 +1,52 @@
/*
* MG_PANMASK.pgc - mg 모듈 서비스 MG_PANMASK (one service = one file).
* Split out of the former inline mg_svr.pgc; runs on the caller's XA
* branch (no EXEC SQL CONNECT). Advertised by mg_svr at tpsvrinit.
*/
#include "MG_PANMASK.h"
/* ----- service-local pure helper(s) ----- */
/* BIN(앞6)/뒤4 보존 마스킹. 12자리 미만 짧은 PAN 은 전체 마스킹. */
static int mg_panmask_apply(const char *pan, char *out /* >= 20 */)
{
int i, n = (int)strlen(pan);
if (n > 19) n = 19;
if (n < 12) {
for (i = 0; i < n; i++) out[i] = '*';
out[n] = 0;
return 1; /* 비정상 길이 표시 */
}
for (i = 0; i < n; i++)
out[i] = (i < 6 || i >= n - 4) ? pan[i] : '*';
out[n] = 0;
return 0;
}
/* 70. MG_PANMASK - PAN 마스킹: 카드번호를 BIN/뒤4자리만 남겨 가리고, msg_id 가
* 주어지면 원장의 pan 컬럼도 마스킹본으로 덮어쓴다 (정보보호 후처리). */
void MG_PANMASK(TPSVCINFO *p)
{
UBFH *b = (UBFH *)p->data;
char pan[24], masked[24];
int shortpan;
EXEC SQL BEGIN DECLARE SECTION;
long h_id;
char h_pan[24];
EXEC SQL END DECLARE SECTION;
gets_(b, T_STR5, pan, sizeof(pan));
if (pan[0] == 0) { setl(b, T_RC, 30); FAIL(b); }
shortpan = mg_panmask_apply(pan, masked);
sets_(b, T_STR5, masked);
h_id = getl(b, T_ID1);
if (h_id > 0) {
strncpy(h_pan, masked, sizeof(h_pan)-1); h_pan[sizeof(h_pan)-1] = 0;
EXEC SQL UPDATE mg_msg_log SET pan = :h_pan WHERE msg_id = :h_id;
if (sqlca.sqlcode < 0) { userlog("MG_PANMASK 원장 마스킹 FAIL [%d]", sqlca.sqlcode); FAIL(b); }
}
setl(b, T_RC, shortpan); /* 1 = 길이 비정상 PAN 이었음 */
OK(b);
}

View file

@ -0,0 +1,12 @@
/*
* MG_QDEPTH.h - mg () MG_QDEPTH (copybook / record header).
* Split out of the former inline mg_svr.pgc; advertised by mg_svr.
*/
#ifndef MG_QDEPTH_H
#define MG_QDEPTH_H
#include "mg_svc.h"
void MG_QDEPTH(TPSVCINFO *p);
#endif /* MG_QDEPTH_H */

View file

@ -0,0 +1,37 @@
/*
* MG_QDEPTH.pgc - mg 모듈 서비스 MG_QDEPTH (one service = one file).
* Split out of the former inline mg_svr.pgc; runs on the caller's XA
* branch (no EXEC SQL CONNECT). Advertised by mg_svr at tpsvrinit.
*/
#include "MG_QDEPTH.h"
/* 75. MG_QDEPTH - 송수신 큐 깊이 조회: 대기(Q)/송신(S)/완료(D)/실패(F) 상태별
* 건수와 최대 재시도 횟수를 한 집계로 회신 (큐 적체 대시보드). */
void MG_QDEPTH(TPSVCINFO *p)
{
UBFH *b = (UBFH *)p->data;
EXEC SQL BEGIN DECLARE SECTION;
char h_bizdate[16];
long h_q, h_s, h_d, h_f, h_maxretry;
EXEC SQL END DECLARE SECTION;
gets_(b, T_BIZDATE, h_bizdate, sizeof(h_bizdate));
EXEC SQL SELECT
coalesce(sum(CASE WHEN status = 'Q' THEN 1 ELSE 0 END),0),
coalesce(sum(CASE WHEN status = 'S' THEN 1 ELSE 0 END),0),
coalesce(sum(CASE WHEN status = 'D' THEN 1 ELSE 0 END),0),
coalesce(sum(CASE WHEN status = 'F' THEN 1 ELSE 0 END),0),
coalesce(max(retry_cnt),0)
INTO :h_q, :h_s, :h_d, :h_f, :h_maxretry
FROM mg_queue WHERE biz_date = :h_bizdate;
if (sqlca.sqlcode < 0) FAIL(b);
setl(b, T_ARG1, h_q);
setl(b, T_ARG2, h_s);
setl(b, T_ARG3, h_d);
setl(b, T_ARG4, h_f);
setl(b, T_ARG7, h_maxretry);
setl(b, T_RC, (h_q + h_f > 100) ? 1 : 0); /* 적체+실패 100건 초과 경보 */
OK(b);
}

12
app/src/mg/svc/MG_REASM.h Normal file
View file

@ -0,0 +1,12 @@
/*
* MG_REASM.h - mg () MG_REASM (copybook / record header).
* Split out of the former inline mg_svr.pgc; advertised by mg_svr.
*/
#ifndef MG_REASM_H
#define MG_REASM_H
#include "mg_svc.h"
void MG_REASM(TPSVCINFO *p);
#endif /* MG_REASM_H */

View file

@ -0,0 +1,40 @@
/*
* MG_REASM.pgc - mg 모듈 서비스 MG_REASM (one service = one file).
* Split out of the former inline mg_svr.pgc; runs on the caller's XA
* branch (no EXEC SQL CONNECT). Advertised by mg_svr at tpsvrinit.
*/
#include "MG_REASM.h"
/* 41. MG_REASM - 분할전문 재조립: 세그먼트 T_STR5~T_STR8 (최대 4편) 을 순서대로
* 이어붙여 원 전문을 복원. 기대 편수(T_ARG1)와 실제 편수 불일치 시 rc=35. */
void MG_REASM(TPSVCINFO *p)
{
UBFH *b = (UBFH *)p->data;
char seg[4][128], full[512];
long expect, got = 0, total = 0;
int i;
BFLDID segf[4];
segf[0] = T_STR5; segf[1] = T_STR6; segf[2] = T_STR7; segf[3] = T_STR8;
expect = getl(b, T_ARG1); if (expect <= 0 || expect > 4) expect = 4;
full[0] = 0;
for (i = 0; i < 4; i++) {
gets_(b, segf[i], seg[i], sizeof(seg[i]));
if (seg[i][0] == 0) break; /* 첫 빈 세그먼트에서 종료 */
got++;
total += (long)strlen(seg[i]);
strcat(full, seg[i]);
}
if (got != expect) { /* 편수 불일치 = 재조립 실패 */
userlog("MG_REASM 편수 불일치 기대=%ld 수신=%ld", expect, got);
setl(b, T_RC, 35);
FAIL(b);
}
sets_(b, T_STR3, full);
setl(b, T_COUNT, got);
setl(b, T_ARG2, total);
setl(b, T_RC, 0);
OK(b);
}

View file

@ -0,0 +1,12 @@
/*
* MG_REVADVICE.h - mg () MG_REVADVICE (copybook / record header).
* Split out of the former inline mg_svr.pgc; advertised by mg_svr.
*/
#ifndef MG_REVADVICE_H
#define MG_REVADVICE_H
#include "mg_svc.h"
void MG_REVADVICE(TPSVCINFO *p);
#endif /* MG_REVADVICE_H */

View file

@ -0,0 +1,41 @@
/*
* MG_REVADVICE.pgc - mg 모듈 서비스 MG_REVADVICE (one service = one file).
* Split out of the former inline mg_svr.pgc; runs on the caller's XA
* branch (no EXEC SQL CONNECT). Advertised by mg_svr at tpsvrinit.
*/
#include "MG_REVADVICE.h"
/* 36. MG_REVADVICE - 0420 취소 어드바이스 수신: 통지성 전문이므로 무조건 수용(A),
* 적재 후 채널 last_stan 을 수신 STAN 까지 전진시켜 순번 동기 유지. */
void MG_REVADVICE(TPSVCINFO *p)
{
UBFH *b = (UBFH *)p->data;
char chan[16], bizdate[16], raw[512];
long stan, msg_id, amount;
EXEC SQL BEGIN DECLARE SECTION;
long h_stan;
char h_chan[16];
EXEC SQL END DECLARE SECTION;
gets_(b, T_CHANNEL, chan, sizeof(chan)); if (chan[0] == 0) strcpy(chan, "CH01");
gets_(b, T_BIZDATE, bizdate, sizeof(bizdate));
gets_(b, T_STR3, raw, sizeof(raw));
amount = getl(b, T_AMOUNT);
stan = getl(b, T_ARG1);
if (stan <= 0) { stan = mgdb_next_stan(); if (stan < 0) FAIL(b); }
msg_id = mgdb_next_msg_id(); if (msg_id < 0) FAIL(b);
if (mgdb_insert_msg(msg_id, stan, "0420", chan, "IN", "170000", "", -amount,
"", "00", raw, "A", bizdate) < 0) FAIL(b);
h_stan = stan;
strncpy(h_chan, chan, sizeof(h_chan)-1); h_chan[sizeof(h_chan)-1] = 0;
EXEC SQL UPDATE mg_channel
SET last_stan = greatest(last_stan, :h_stan), updated_at = now()
WHERE channel = :h_chan;
if (sqlca.sqlcode < 0) { userlog("MG_REVADVICE 순번동기 FAIL [%d]", sqlca.sqlcode); FAIL(b); }
setl(b, T_ID1, msg_id);
setl(b, T_ARG1, stan);
OK(b);
}

View file

@ -0,0 +1,12 @@
/*
* MG_REVERSAL.h - mg () MG_REVERSAL (copybook / record header).
* Split out of the former inline mg_svr.pgc; advertised by mg_svr.
*/
#ifndef MG_REVERSAL_H
#define MG_REVERSAL_H
#include "mg_svc.h"
void MG_REVERSAL(TPSVCINFO *p);
#endif /* MG_REVERSAL_H */

View file

@ -0,0 +1,39 @@
/*
* MG_REVERSAL.pgc - mg 모듈 서비스 MG_REVERSAL (one service = one file).
* Split out of the former inline mg_svr.pgc; runs on the caller's XA
* branch (no EXEC SQL CONNECT). Advertised by mg_svr at tpsvrinit.
*/
#include "MG_REVERSAL.h"
/* 35. MG_REVERSAL - 0400 취소요청 처리: 원전문을 STAN 으로 찾아 rc=17(고객취소)
* 확정 전이 후, 음수 금액의 0400 수신 로그를 별도 적재 (취소 원장). */
void MG_REVERSAL(TPSVCINFO *p)
{
UBFH *b = (UBFH *)p->data;
char chan[16], bizdate[16];
long stan, orig_id, msg_id, rev_stan, amount;
EXEC SQL BEGIN DECLARE SECTION;
long h_oid;
EXEC SQL END DECLARE SECTION;
gets_(b, T_CHANNEL, chan, sizeof(chan)); if (chan[0] == 0) strcpy(chan, "CH01");
gets_(b, T_BIZDATE, bizdate, sizeof(bizdate));
stan = getl(b, T_ARG1);
amount = getl(b, T_AMOUNT);
orig_id = mgdb_find_msg_by_stan(stan);
if (orig_id < 0) { setl(b, T_RC, 25); OK(b); } /* 원거래 없음 */
h_oid = orig_id;
EXEC SQL UPDATE mg_msg_log SET rc = '17', status = 'C' WHERE msg_id = :h_oid;
if (sqlca.sqlcode < 0) { userlog("MG_REVERSAL 원전문 취소전이 FAIL [%d]", sqlca.sqlcode); FAIL(b); }
rev_stan = mgdb_next_stan(); if (rev_stan < 0) FAIL(b);
msg_id = mgdb_next_msg_id(); if (msg_id < 0) FAIL(b);
if (mgdb_insert_msg(msg_id, rev_stan, "0400", chan, "IN", "170000", "", -amount,
"", "00", "", "R", bizdate) < 0) FAIL(b);
setl(b, T_ID1, orig_id);
setl(b, T_ID2, msg_id);
setl(b, T_RC, 0);
OK(b);
}

12
app/src/mg/svc/MG_RTADD.h Normal file
View file

@ -0,0 +1,12 @@
/*
* MG_RTADD.h - mg () MG_RTADD (copybook / record header).
* Split out of the former inline mg_svr.pgc; advertised by mg_svr.
*/
#ifndef MG_RTADD_H
#define MG_RTADD_H
#include "mg_svc.h"
void MG_RTADD(TPSVCINFO *p);
#endif /* MG_RTADD_H */

View file

@ -0,0 +1,35 @@
/*
* MG_RTADD.pgc - mg 모듈 서비스 MG_RTADD (one service = one file).
* Split out of the former inline mg_svr.pgc; runs on the caller's XA
* branch (no EXEC SQL CONNECT). Advertised by mg_svr at tpsvrinit.
*/
#include "MG_RTADD.h"
/* 53. MG_RTADD - 라우팅 룰 등록: 채널+MTI -> 목적지 룰을 신규 채번해 추가.
* 동일 채널/MTI 활성 룰이 이미 있으면 rc=1 로 중복 등록을 알린다. */
void MG_RTADD(TPSVCINFO *p)
{
UBFH *b = (UBFH *)p->data;
char chan[16], mti[8], dest[64], bizdate[16], old_dest[64];
long prio, dup = 0;
EXEC SQL BEGIN DECLARE SECTION;
long h_rid;
EXEC SQL END DECLARE SECTION;
gets_(b, T_CHANNEL, chan, sizeof(chan)); if (chan[0] == 0) strcpy(chan, "CH01");
gets_(b, T_STR1, mti, sizeof(mti)); if (mti[0] == 0) strcpy(mti, "0200");
gets_(b, T_STR3, dest, sizeof(dest));
gets_(b, T_BIZDATE, bizdate, sizeof(bizdate));
prio = getl(b, T_ARG4); if (prio <= 0) prio = 100;
if (dest[0] == 0) { setl(b, T_RC, 30); FAIL(b); }
if (mgdb_lookup_route(chan, mti, old_dest) == 0) dup = 1; /* 기존 활성 룰 존재 */
EXEC SQL SELECT nextval('mg_route_seq') INTO :h_rid;
if (sqlca.sqlcode < 0) FAIL(b);
if (mgdb_insert_route(h_rid, chan, mti, dest, prio, bizdate) < 0) FAIL(b);
setl(b, T_ID1, h_rid);
setl(b, T_RC, dup);
OK(b);
}

View file

@ -0,0 +1,12 @@
/*
* MG_RTDROP.h - mg () MG_RTDROP (copybook / record header).
* Split out of the former inline mg_svr.pgc; advertised by mg_svr.
*/
#ifndef MG_RTDROP_H
#define MG_RTDROP_H
#include "mg_svc.h"
void MG_RTDROP(TPSVCINFO *p);
#endif /* MG_RTDROP_H */

View file

@ -0,0 +1,30 @@
/*
* MG_RTDROP.pgc - mg 모듈 서비스 MG_RTDROP (one service = one file).
* Split out of the former inline mg_svr.pgc; runs on the caller's XA
* branch (no EXEC SQL CONNECT). Advertised by mg_svr at tpsvrinit.
*/
#include "MG_RTDROP.h"
/* 54. MG_RTDROP - 라우팅 룰 비활성: 채널+MTI 의 활성 룰을 전부 내려서
* 라우팅에서 제외. 내린 룰 수를 회신하고 대상이 없으면 rc=25. */
void MG_RTDROP(TPSVCINFO *p)
{
UBFH *b = (UBFH *)p->data;
long dropped;
EXEC SQL BEGIN DECLARE SECTION;
char h_chan[16], h_mti[8];
EXEC SQL END DECLARE SECTION;
gets_(b, T_CHANNEL, h_chan, sizeof(h_chan)); if (h_chan[0] == 0) strcpy(h_chan, "CH01");
gets_(b, T_STR1, h_mti, sizeof(h_mti)); if (h_mti[0] == 0) strcpy(h_mti, "0200");
EXEC SQL UPDATE mg_route SET active = false
WHERE channel = :h_chan AND mti = :h_mti AND active = true;
if (sqlca.sqlcode < 0) { userlog("MG_RTDROP 비활성 FAIL [%d]", sqlca.sqlcode); FAIL(b); }
dropped = (long)sqlca.sqlerrd[2];
if (dropped == 0) { setl(b, T_RC, 25); OK(b); } /* 내릴 룰 없음 */
setl(b, T_COUNT, dropped);
setl(b, T_RC, 0);
OK(b);
}

View file

@ -0,0 +1,12 @@
/*
* MG_RTLIST.h - mg () MG_RTLIST (copybook / record header).
* Split out of the former inline mg_svr.pgc; advertised by mg_svr.
*/
#ifndef MG_RTLIST_H
#define MG_RTLIST_H
#include "mg_svc.h"
void MG_RTLIST(TPSVCINFO *p);
#endif /* MG_RTLIST_H */

View file

@ -0,0 +1,47 @@
/*
* MG_RTLIST.pgc - mg 모듈 서비스 MG_RTLIST (one service = one file).
* Split out of the former inline mg_svr.pgc; runs on the caller's XA
* branch (no EXEC SQL CONNECT). Advertised by mg_svr at tpsvrinit.
*/
#include "MG_RTLIST.h"
/* 55. MG_RTLIST - 라우팅 룰 열람 요약: 채널의 활성/비활성 룰 수와 최우선(최소)
* priority 의 목적지를 커서로 훑어 회신 (룰 대시보드용). */
void MG_RTLIST(TPSVCINFO *p)
{
UBFH *b = (UBFH *)p->data;
long act = 0, inact = 0, best_prio = -1;
char best_dest[64];
EXEC SQL BEGIN DECLARE SECTION;
char h_chan[16], h_dest[64];
long h_prio, h_active;
EXEC SQL END DECLARE SECTION;
gets_(b, T_CHANNEL, h_chan, sizeof(h_chan)); if (h_chan[0] == 0) strcpy(h_chan, "CH01");
best_dest[0] = 0;
EXEC SQL DECLARE rtl CURSOR FOR
SELECT dest, priority, CASE WHEN active THEN 1 ELSE 0 END
FROM mg_route WHERE channel = :h_chan ORDER BY priority;
EXEC SQL OPEN rtl;
if (sqlca.sqlcode < 0) FAIL(b);
for (;;) {
EXEC SQL FETCH rtl INTO :h_dest, :h_prio, :h_active;
if (sqlca.sqlcode == 100) break;
if (sqlca.sqlcode < 0) { EXEC SQL CLOSE rtl; FAIL(b); }
if (h_active) {
act++;
if (best_prio < 0) { best_prio = h_prio; strncpy(best_dest, h_dest, sizeof(best_dest)-1); }
} else {
inact++;
}
}
EXEC SQL CLOSE rtl;
sets_(b, T_STR3, best_dest);
setl(b, T_ARG4, (best_prio < 0) ? 0 : best_prio);
setl(b, T_COUNT, act);
setl(b, T_ARG7, inact);
setl(b, T_RC, (act == 0) ? 1 : 0); /* 활성 룰 전무 = 경고 */
OK(b);
}

View file

@ -0,0 +1,12 @@
/*
* MG_RTSWITCH.h - mg () MG_RTSWITCH (copybook / record header).
* Split out of the former inline mg_svr.pgc; advertised by mg_svr.
*/
#ifndef MG_RTSWITCH_H
#define MG_RTSWITCH_H
#include "mg_svc.h"
void MG_RTSWITCH(TPSVCINFO *p);
#endif /* MG_RTSWITCH_H */

View file

@ -0,0 +1,37 @@
/*
* MG_RTSWITCH.pgc - mg 모듈 서비스 MG_RTSWITCH (one service = one file).
* Split out of the former inline mg_svr.pgc; runs on the caller's XA
* branch (no EXEC SQL CONNECT). Advertised by mg_svr at tpsvrinit.
*/
#include "MG_RTSWITCH.h"
/* 56. MG_RTSWITCH - 라우팅 목적지 절체: 채널+MTI 활성 룰의 dest 를 새 목적지로
* 일괄 교체 (장애 우회). 교체 전 목적지를 T_STR5 로 돌려줘 원복을 돕는다. */
void MG_RTSWITCH(TPSVCINFO *p)
{
UBFH *b = (UBFH *)p->data;
char prev[64];
long switched;
EXEC SQL BEGIN DECLARE SECTION;
char h_chan[16], h_mti[8], h_new[64];
EXEC SQL END DECLARE SECTION;
gets_(b, T_CHANNEL, h_chan, sizeof(h_chan)); if (h_chan[0] == 0) strcpy(h_chan, "CH01");
gets_(b, T_STR1, h_mti, sizeof(h_mti)); if (h_mti[0] == 0) strcpy(h_mti, "0200");
gets_(b, T_STR3, h_new, sizeof(h_new));
if (h_new[0] == 0) { setl(b, T_RC, 30); FAIL(b); }
if (mgdb_lookup_route(h_chan, h_mti, prev) < 0) { setl(b, T_RC, 25); FAIL(b); }
if (strcmp(prev, h_new) == 0) { setl(b, T_RC, 1); OK(b); } /* 이미 그 목적지 */
EXEC SQL UPDATE mg_route SET dest = :h_new
WHERE channel = :h_chan AND mti = :h_mti AND active = true;
if (sqlca.sqlcode < 0) { userlog("MG_RTSWITCH 절체 FAIL [%d]", sqlca.sqlcode); FAIL(b); }
switched = (long)sqlca.sqlerrd[2];
userlog("MG_RTSWITCH %s/%s: %s -> %s (%ld룰)", h_chan, h_mti, prev, h_new, switched);
sets_(b, T_STR5, prev);
setl(b, T_COUNT, switched);
setl(b, T_RC, 0);
OK(b);
}

View file

@ -0,0 +1,12 @@
/*
* MG_SEQAUDIT.h - mg () MG_SEQAUDIT (copybook / record header).
* Split out of the former inline mg_svr.pgc; advertised by mg_svr.
*/
#ifndef MG_SEQAUDIT_H
#define MG_SEQAUDIT_H
#include "mg_svc.h"
void MG_SEQAUDIT(TPSVCINFO *p);
#endif /* MG_SEQAUDIT_H */

View file

@ -0,0 +1,40 @@
/*
* MG_SEQAUDIT.pgc - mg 모듈 서비스 MG_SEQAUDIT (one service = one file).
* Split out of the former inline mg_svr.pgc; runs on the caller's XA
* branch (no EXEC SQL CONNECT). Advertised by mg_svr at tpsvrinit.
*/
#include "MG_SEQAUDIT.h"
/* 50. MG_SEQAUDIT - 채널 순번 감사: 채널이 기억하는 last_stan 과 원장의 실제
* 최대 STAN 을 대조해 어긋난 폭(드리프트)을 회신. 어긋나면 rc=1. */
void MG_SEQAUDIT(TPSVCINFO *p)
{
UBFH *b = (UBFH *)p->data;
long drift;
EXEC SQL BEGIN DECLARE SECTION;
long h_last, h_maxstan;
char h_chan[16], h_bizdate[16];
EXEC SQL END DECLARE SECTION;
gets_(b, T_CHANNEL, h_chan, sizeof(h_chan)); if (h_chan[0] == 0) strcpy(h_chan, "CH01");
gets_(b, T_BIZDATE, h_bizdate, sizeof(h_bizdate));
EXEC SQL SELECT last_stan INTO :h_last FROM mg_channel WHERE channel = :h_chan;
if (sqlca.sqlcode == 100) { setl(b, T_RC, 25); FAIL(b); }
if (sqlca.sqlcode < 0) FAIL(b);
EXEC SQL SELECT coalesce(max(stan),0) INTO :h_maxstan
FROM mg_msg_log WHERE channel = :h_chan AND biz_date = :h_bizdate;
if (sqlca.sqlcode < 0) FAIL(b);
drift = h_maxstan - h_last;
if (drift != 0)
userlog("MG_SEQAUDIT 채널=%s 드리프트=%ld (채널=%ld 원장=%ld)",
h_chan, drift, h_last, h_maxstan);
setl(b, T_ARG1, h_last);
setl(b, T_ARG2, h_maxstan);
setl(b, T_ARG6, drift);
setl(b, T_RC, (drift != 0) ? 1 : 0);
OK(b);
}

View file

@ -0,0 +1,12 @@
/*
* MG_SETTLEREQ.h - mg () MG_SETTLEREQ (copybook / record header).
* Split out of the former inline mg_svr.pgc; advertised by mg_svr.
*/
#ifndef MG_SETTLEREQ_H
#define MG_SETTLEREQ_H
#include "mg_svc.h"
void MG_SETTLEREQ(TPSVCINFO *p);
#endif /* MG_SETTLEREQ_H */

View file

@ -0,0 +1,43 @@
/*
* MG_SETTLEREQ.pgc - mg 모듈 서비스 MG_SETTLEREQ (one service = one file).
* Split out of the former inline mg_svr.pgc; runs on the caller's XA
* branch (no EXEC SQL CONNECT). Advertised by mg_svr at tpsvrinit.
*/
#include "MG_SETTLEREQ.h"
/* 73. MG_SETTLEREQ - 0500 정산집계 요청 생성: 당일 채널의 수신 0200 건수/금액을
* 집계해 대사 원문("C:건수:합계")으로 삼은 0500 송신 전문을 적재. */
void MG_SETTLEREQ(TPSVCINFO *p)
{
UBFH *b = (UBFH *)p->data;
char chan[16], bizdate[16], recon[64];
long stan, msg_id;
EXEC SQL BEGIN DECLARE SECTION;
char h_chan[16], h_biz[16];
long h_cnt, h_sum;
EXEC SQL END DECLARE SECTION;
gets_(b, T_CHANNEL, chan, sizeof(chan)); if (chan[0] == 0) strcpy(chan, "CH02");
gets_(b, T_BIZDATE, bizdate, sizeof(bizdate));
strncpy(h_chan, chan, sizeof(h_chan)-1); h_chan[sizeof(h_chan)-1] = 0;
strncpy(h_biz, bizdate, sizeof(h_biz)-1); h_biz[sizeof(h_biz)-1] = 0;
EXEC SQL SELECT count(*), coalesce(sum(amount),0)
INTO :h_cnt, :h_sum
FROM mg_msg_log
WHERE channel = :h_chan AND biz_date = :h_biz
AND mti = '0200' AND direction = 'IN';
if (sqlca.sqlcode < 0) FAIL(b);
sprintf(recon, "C:%ld:%ld", h_cnt, h_sum); /* 대사 원문 */
stan = mgdb_next_stan(); if (stan < 0) FAIL(b);
msg_id = mgdb_next_msg_id(); if (msg_id < 0) FAIL(b);
if (mgdb_insert_msg(msg_id, stan, "0500", chan, "OUT", "920000", "", h_sum,
"", "00", recon, "S", bizdate) < 0) FAIL(b);
setl(b, T_COUNT, h_cnt);
setl(b, T_GROSS, h_sum);
setl(b, T_ID1, msg_id);
sets_(b, T_STR3, recon);
OK(b);
}

View file

@ -0,0 +1,12 @@
/*
* MG_SETTLERSP.h - mg () MG_SETTLERSP (copybook / record header).
* Split out of the former inline mg_svr.pgc; advertised by mg_svr.
*/
#ifndef MG_SETTLERSP_H
#define MG_SETTLERSP_H
#include "mg_svc.h"
void MG_SETTLERSP(TPSVCINFO *p);
#endif /* MG_SETTLERSP_H */

View file

@ -0,0 +1,50 @@
/*
* MG_SETTLERSP.pgc - mg 모듈 서비스 MG_SETTLERSP (one service = one file).
* Split out of the former inline mg_svr.pgc; runs on the caller's XA
* branch (no EXEC SQL CONNECT). Advertised by mg_svr at tpsvrinit.
*/
#include "MG_SETTLERSP.h"
/* 74. MG_SETTLERSP - 0510 정산집계 응답 대사: 상대기관이 보낸 건수/합계를 당일
* 자체 집계와 대조해 일치 rc=00, 불일치 rc=95(대사불일치)로 판정 적재. */
void MG_SETTLERSP(TPSVCINFO *p)
{
UBFH *b = (UBFH *)p->data;
char chan[16], bizdate[16], rc[4];
long their_cnt, their_sum, stan, msg_id;
EXEC SQL BEGIN DECLARE SECTION;
char h_chan[16], h_biz[16];
long h_cnt, h_sum;
EXEC SQL END DECLARE SECTION;
gets_(b, T_CHANNEL, chan, sizeof(chan)); if (chan[0] == 0) strcpy(chan, "CH02");
gets_(b, T_BIZDATE, bizdate, sizeof(bizdate));
their_cnt = getl(b, T_COUNT); /* 상대기관 집계 건수 */
their_sum = getl(b, T_GROSS); /* 상대기관 집계 합계 */
strncpy(h_chan, chan, sizeof(h_chan)-1); h_chan[sizeof(h_chan)-1] = 0;
strncpy(h_biz, bizdate, sizeof(h_biz)-1); h_biz[sizeof(h_biz)-1] = 0;
EXEC SQL SELECT count(*), coalesce(sum(amount),0)
INTO :h_cnt, :h_sum
FROM mg_msg_log
WHERE channel = :h_chan AND biz_date = :h_biz
AND mti = '0200' AND direction = 'IN';
if (sqlca.sqlcode < 0) FAIL(b);
strcpy(rc, (their_cnt == h_cnt && their_sum == h_sum) ? "00" : "95");
if (rc[0] == '9')
userlog("MG_SETTLERSP 대사 불일치 채널=%s 건수 %ld/%ld 합계 %ld/%ld",
chan, their_cnt, h_cnt, their_sum, h_sum);
stan = mgdb_next_stan(); if (stan < 0) FAIL(b);
msg_id = mgdb_next_msg_id(); if (msg_id < 0) FAIL(b);
if (mgdb_insert_msg(msg_id, stan, "0510", chan, "IN", "920000", "", their_sum,
"", rc, "", "C", bizdate) < 0) FAIL(b);
sets_(b, T_STR2, rc);
setl(b, T_ARG1, h_cnt);
setl(b, T_AMT1, h_sum);
setl(b, T_ID1, msg_id);
setl(b, T_RC, (rc[0] == '0') ? 0 : 95);
OK(b);
}

View file

@ -0,0 +1,12 @@
/*
* MG_STATDIR.h - mg () MG_STATDIR (copybook / record header).
* Split out of the former inline mg_svr.pgc; advertised by mg_svr.
*/
#ifndef MG_STATDIR_H
#define MG_STATDIR_H
#include "mg_svc.h"
void MG_STATDIR(TPSVCINFO *p);
#endif /* MG_STATDIR_H */

View file

@ -0,0 +1,38 @@
/*
* MG_STATDIR.pgc - mg 모듈 서비스 MG_STATDIR (one service = one file).
* Split out of the former inline mg_svr.pgc; runs on the caller's XA
* branch (no EXEC SQL CONNECT). Advertised by mg_svr at tpsvrinit.
*/
#include "MG_STATDIR.h"
/* 63. MG_STATDIR - 송수신 방향별 통계: 당일 IN/OUT 각각의 건수와 금액합,
* 그리고 수신 대비 송신 응답률(%) 을 산출 (미응답 적체 감시). */
void MG_STATDIR(TPSVCINFO *p)
{
UBFH *b = (UBFH *)p->data;
long rsp_pct;
EXEC SQL BEGIN DECLARE SECTION;
char h_bizdate[16];
long h_in_cnt, h_in_sum, h_out_cnt, h_out_sum;
EXEC SQL END DECLARE SECTION;
gets_(b, T_BIZDATE, h_bizdate, sizeof(h_bizdate));
EXEC SQL SELECT
coalesce(sum(CASE WHEN direction = 'IN' THEN 1 ELSE 0 END),0),
coalesce(sum(CASE WHEN direction = 'IN' THEN amount ELSE 0 END),0),
coalesce(sum(CASE WHEN direction = 'OUT' THEN 1 ELSE 0 END),0),
coalesce(sum(CASE WHEN direction = 'OUT' THEN amount ELSE 0 END),0)
INTO :h_in_cnt, :h_in_sum, :h_out_cnt, :h_out_sum
FROM mg_msg_log WHERE biz_date = :h_bizdate;
if (sqlca.sqlcode < 0) FAIL(b);
rsp_pct = (h_in_cnt > 0) ? (h_out_cnt * 100L) / h_in_cnt : 100;
setl(b, T_ARG1, h_in_cnt);
setl(b, T_AMT1, h_in_sum);
setl(b, T_ARG2, h_out_cnt);
setl(b, T_AMT2, h_out_sum);
setl(b, T_ARG8, rsp_pct);
setl(b, T_RC, (rsp_pct < 90) ? 1 : 0); /* 응답률 90% 미만 = 경고 */
OK(b);
}

View file

@ -0,0 +1,12 @@
/*
* MG_STATHOUR.h - mg () MG_STATHOUR (copybook / record header).
* Split out of the former inline mg_svr.pgc; advertised by mg_svr.
*/
#ifndef MG_STATHOUR_H
#define MG_STATHOUR_H
#include "mg_svc.h"
void MG_STATHOUR(TPSVCINFO *p);
#endif /* MG_STATHOUR_H */

View file

@ -0,0 +1,42 @@
/*
* MG_STATHOUR.pgc - mg 모듈 서비스 MG_STATHOUR (one service = one file).
* Split out of the former inline mg_svr.pgc; runs on the caller's XA
* branch (no EXEC SQL CONNECT). Advertised by mg_svr at tpsvrinit.
*/
#include "MG_STATHOUR.h"
/* 62. MG_STATHOUR - 시간대별 통계: 요청 시(0~23)의 당일 건수/금액합과 함께
* 당일 최다 유입 시간대(피크아워)를 커서 없이 집계 질의로 회신. */
void MG_STATHOUR(TPSVCINFO *p)
{
UBFH *b = (UBFH *)p->data;
long hour;
EXEC SQL BEGIN DECLARE SECTION;
long h_hour, h_cnt, h_sum, h_peak;
char h_bizdate[16];
EXEC SQL END DECLARE SECTION;
gets_(b, T_BIZDATE, h_bizdate, sizeof(h_bizdate));
hour = getl(b, T_ARG5);
if (hour < 0 || hour > 23) hour = 0;
h_hour = hour;
EXEC SQL SELECT count(*), coalesce(sum(amount),0)
INTO :h_cnt, :h_sum
FROM mg_msg_log
WHERE biz_date = :h_bizdate
AND extract(hour FROM created_at) = :h_hour;
if (sqlca.sqlcode < 0) FAIL(b);
h_peak = -1;
EXEC SQL SELECT extract(hour FROM created_at)::int INTO :h_peak
FROM mg_msg_log WHERE biz_date = :h_bizdate
GROUP BY 1 ORDER BY count(*) DESC, 1 LIMIT 1;
if (sqlca.sqlcode < 0) FAIL(b);
setl(b, T_COUNT, h_cnt);
setl(b, T_GROSS, h_sum);
setl(b, T_ARG5, hour);
setl(b, T_ARG6, h_peak); /* -1 = 당일 전문 없음 */
OK(b);
}

View file

@ -0,0 +1,12 @@
/*
* MG_STATMTI.h - mg () MG_STATMTI (copybook / record header).
* Split out of the former inline mg_svr.pgc; advertised by mg_svr.
*/
#ifndef MG_STATMTI_H
#define MG_STATMTI_H
#include "mg_svc.h"
void MG_STATMTI(TPSVCINFO *p);
#endif /* MG_STATMTI_H */

Some files were not shown because too many files have changed in this diff Show more