Phase 2b: 11개 모듈 전량 실동작 (333 서비스, 전서버 부팅)
- 10개 모듈 추가/확장(au py rc st lg cl mm vl mg cm), 각 30 서비스 = 330 + tmsrv - build.sh 자동발견으로 11 모듈서버 + dbio 라이브러리 23 + 배치 22 빌드 - schema.d 11파일 → 75 테이블, 모듈별 disjoint - 부팅 리소스 규명: fs.mqueue.queues_max=8192(330큐>256), NDRX_MSGSIZEMAX 16000· MSGMAX 50(큐당 5.6MB→0.8MB), msgqueue ulimit 2GB → 전 12서버 runok - 검증: xadmin psc 333 AVAIL, 매입체인 XA 커밋, prepared_xacts=0 Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This commit is contained in:
parent
8219b85f14
commit
bec83c721e
73 changed files with 10705 additions and 35 deletions
|
|
@ -9,8 +9,8 @@ NDRX_IPCKEY=44100
|
|||
NDRX_LDBAL=0
|
||||
NDRX_LEV=5
|
||||
NDRX_LOG=${NDRX_APPHOME}/log/xadmin.log
|
||||
NDRX_MSGMAX=100
|
||||
NDRX_MSGSIZEMAX=56000
|
||||
NDRX_MSGMAX=50
|
||||
NDRX_MSGSIZEMAX=16000
|
||||
NDRX_NODEID=1
|
||||
NDRX_QPATH=/dev/mqueue
|
||||
NDRX_QPREFIX=/acq
|
||||
|
|
|
|||
|
|
@ -35,17 +35,65 @@
|
|||
<max>1</max>
|
||||
<sysopt>-e ${NDRX_APPHOME}/log/ac_svr.log -r</sysopt>
|
||||
</server>
|
||||
<server name="rc_svr">
|
||||
<server name="au_svr">
|
||||
<srvid>110</srvid>
|
||||
<min>1</min>
|
||||
<max>1</max>
|
||||
<sysopt>-e ${NDRX_APPHOME}/log/au_svr.log -r</sysopt>
|
||||
</server>
|
||||
<server name="cl_svr">
|
||||
<srvid>120</srvid>
|
||||
<min>1</min>
|
||||
<max>1</max>
|
||||
<sysopt>-e ${NDRX_APPHOME}/log/cl_svr.log -r</sysopt>
|
||||
</server>
|
||||
<server name="cm_svr">
|
||||
<srvid>130</srvid>
|
||||
<min>1</min>
|
||||
<max>1</max>
|
||||
<sysopt>-e ${NDRX_APPHOME}/log/cm_svr.log -r</sysopt>
|
||||
</server>
|
||||
<server name="lg_svr">
|
||||
<srvid>140</srvid>
|
||||
<min>1</min>
|
||||
<max>1</max>
|
||||
<sysopt>-e ${NDRX_APPHOME}/log/lg_svr.log -r</sysopt>
|
||||
</server>
|
||||
<server name="mg_svr">
|
||||
<srvid>150</srvid>
|
||||
<min>1</min>
|
||||
<max>1</max>
|
||||
<sysopt>-e ${NDRX_APPHOME}/log/mg_svr.log -r</sysopt>
|
||||
</server>
|
||||
<server name="mm_svr">
|
||||
<srvid>160</srvid>
|
||||
<min>1</min>
|
||||
<max>1</max>
|
||||
<sysopt>-e ${NDRX_APPHOME}/log/mm_svr.log -r</sysopt>
|
||||
</server>
|
||||
<server name="py_svr">
|
||||
<srvid>170</srvid>
|
||||
<min>1</min>
|
||||
<max>1</max>
|
||||
<sysopt>-e ${NDRX_APPHOME}/log/py_svr.log -r</sysopt>
|
||||
</server>
|
||||
<server name="rc_svr">
|
||||
<srvid>180</srvid>
|
||||
<min>1</min>
|
||||
<max>1</max>
|
||||
<sysopt>-e ${NDRX_APPHOME}/log/rc_svr.log -r</sysopt>
|
||||
</server>
|
||||
<server name="st_svr">
|
||||
<srvid>120</srvid>
|
||||
<srvid>190</srvid>
|
||||
<min>1</min>
|
||||
<max>1</max>
|
||||
<sysopt>-e ${NDRX_APPHOME}/log/st_svr.log -r</sysopt>
|
||||
</server>
|
||||
<server name="vl_svr">
|
||||
<srvid>200</srvid>
|
||||
<min>1</min>
|
||||
<max>1</max>
|
||||
<sysopt>-e ${NDRX_APPHOME}/log/vl_svr.log -r</sysopt>
|
||||
</server>
|
||||
</servers>
|
||||
</endurox>
|
||||
|
|
|
|||
660
app/src/au/au_svr.pgc
Normal file
660
app/src/au/au_svr.pgc
Normal file
|
|
@ -0,0 +1,660 @@
|
|||
/*
|
||||
* au_svr.pgc - au (승인/한도) MODULE SERVER.
|
||||
*
|
||||
* ONE binary that tpadvertise()s ALL ~30 online au services (the real Tuxedo
|
||||
* "one module server, many services" pattern). Every service is a distinct
|
||||
* 승인/한도 operation with genuine EXEC SQL (directly, or via the linked
|
||||
* libaudbio.a dbio layer) and common math from libacqcommon.a.
|
||||
*
|
||||
* XA: the connection is opened once by tpopen() (ECPG XA switch); there is NO
|
||||
* EXEC SQL CONNECT. Owner-writes discipline: services in this server never
|
||||
* tpcall each other; each owns its tables on this process's XA branch.
|
||||
*/
|
||||
#include <stdio.h>
|
||||
#include <stdlib.h>
|
||||
#include <string.h>
|
||||
#include <atmi.h>
|
||||
#include <ubf.h>
|
||||
#include <userlog.h>
|
||||
#include <ndebug.h>
|
||||
#include "acq.fd.h"
|
||||
#include "au_dbio.h"
|
||||
#include "acq_common.h"
|
||||
|
||||
/* ----- small UBF helpers ------------------------------------------------- */
|
||||
static long getl(UBFH *b, BFLDID f) { long v = 0; Bget(b, f, 0, (char *)&v, 0L); return v; }
|
||||
static void gets_(UBFH *b, BFLDID f, char *out, int cap)
|
||||
{ BFLDLEN l = cap; out[0] = 0; Bget(b, f, 0, out, &l); }
|
||||
static void setl(UBFH *b, BFLDID f, long v) { Bchg(b, f, 0, (char *)&v, 0L); }
|
||||
|
||||
#define FAIL(b) do { tpreturn(TPFAIL, 0, (char *)(b), 0L, 0L); return; } while (0)
|
||||
#define OK(b) do { tpreturn(TPSUCCESS, 0, (char *)(b), 0L, 0L); return; } while (0)
|
||||
|
||||
/* ----- service prototypes ------------------------------------------------ */
|
||||
void AUTH(TPSVCINFO *p); void AUTH_CANCEL(TPSVCINFO *p);
|
||||
void AUTH_PARTCXL(TPSVCINFO *p); void LIMIT_CHK(TPSVCINFO *p);
|
||||
void LIMIT_DEC(TPSVCINFO *p); void LIMIT_RST(TPSVCINFO *p);
|
||||
void LIMIT_INQ(TPSVCINFO *p); void STANDIN(TPSVCINFO *p);
|
||||
void FRAUD_DETECT(TPSVCINFO *p); void AUTH_RETRY(TPSVCINFO *p);
|
||||
void INSTALL_AUTH(TPSVCINFO *p); void AUTH_STATUS(TPSVCINFO *p);
|
||||
void AUTH_REVERSE(TPSVCINFO *p); void AUTH_APPROVE(TPSVCINFO *p);
|
||||
void AUTH_DECLINE(TPSVCINFO *p); void CARD_VALIDATE(TPSVCINFO *p);
|
||||
void VELOCITY_CHK(TPSVCINFO *p); void BLACKLIST_CHK(TPSVCINFO *p);
|
||||
void AUTH_HOLD(TPSVCINFO *p); void AUTH_RELEASE(TPSVCINFO *p);
|
||||
void AUTH_HISTORY(TPSVCINFO *p); void LIMIT_SET(TPSVCINFO *p);
|
||||
void LIMIT_TEMP(TPSVCINFO *p); void AUTH_SUMMARY(TPSVCINFO *p);
|
||||
void AUTH_ROUTE(TPSVCINFO *p); void DCC_QUOTE(TPSVCINFO *p);
|
||||
void AUTH_CAPTURE(TPSVCINFO *p); void PREAUTH(TPSVCINFO *p);
|
||||
void PREAUTH_COMPLETE(TPSVCINFO *p); void AUTH_VOID(TPSVCINFO *p);
|
||||
|
||||
/* ----- advertise table (this is what "scales to 30" cleanly) ------------- */
|
||||
static struct { const char *name; void (*fn)(TPSVCINFO *); } SVCS[] = {
|
||||
{"AUTH", AUTH}, {"AUTH_CANCEL", AUTH_CANCEL},
|
||||
{"AUTH_PARTCXL", AUTH_PARTCXL}, {"LIMIT_CHK", LIMIT_CHK},
|
||||
{"LIMIT_DEC", LIMIT_DEC}, {"LIMIT_RST", LIMIT_RST},
|
||||
{"LIMIT_INQ", LIMIT_INQ}, {"STANDIN", STANDIN},
|
||||
{"FRAUD_DETECT", FRAUD_DETECT}, {"AUTH_RETRY", AUTH_RETRY},
|
||||
{"INSTALL_AUTH", INSTALL_AUTH}, {"AUTH_STATUS", AUTH_STATUS},
|
||||
{"AUTH_REVERSE", AUTH_REVERSE}, {"AUTH_APPROVE", AUTH_APPROVE},
|
||||
{"AUTH_DECLINE", AUTH_DECLINE}, {"CARD_VALIDATE", CARD_VALIDATE},
|
||||
{"VELOCITY_CHK", VELOCITY_CHK}, {"BLACKLIST_CHK", BLACKLIST_CHK},
|
||||
{"AUTH_HOLD", AUTH_HOLD}, {"AUTH_RELEASE", AUTH_RELEASE},
|
||||
{"AUTH_HISTORY", AUTH_HISTORY}, {"LIMIT_SET", LIMIT_SET},
|
||||
{"LIMIT_TEMP", LIMIT_TEMP}, {"AUTH_SUMMARY", AUTH_SUMMARY},
|
||||
{"AUTH_ROUTE", AUTH_ROUTE}, {"DCC_QUOTE", DCC_QUOTE},
|
||||
{"AUTH_CAPTURE", AUTH_CAPTURE}, {"PREAUTH", PREAUTH},
|
||||
{"PREAUTH_COMPLETE", PREAUTH_COMPLETE}, {"AUTH_VOID", AUTH_VOID},
|
||||
{NULL, NULL}
|
||||
};
|
||||
|
||||
int tpsvrinit(int argc, char **argv)
|
||||
{
|
||||
int i;
|
||||
if (tpopen() < 0) {
|
||||
userlog("au_svr: tpopen FAIL: %s", tpstrerror(tperrno));
|
||||
return -1;
|
||||
}
|
||||
for (i = 0; SVCS[i].name != NULL; i++) {
|
||||
if (tpadvertise((char *)SVCS[i].name, SVCS[i].fn) < 0) {
|
||||
userlog("au_svr: tpadvertise(%s) FAIL: %s", SVCS[i].name, tpstrerror(tperrno));
|
||||
return -1;
|
||||
}
|
||||
}
|
||||
userlog("au_svr: %d au(승인/한도) 서비스 광고 완료, RM 오픈", i);
|
||||
return 0;
|
||||
}
|
||||
|
||||
void tpsvrdone(void)
|
||||
{
|
||||
tpclose();
|
||||
userlog("au_svr: tpsvrdone");
|
||||
}
|
||||
|
||||
/* ========================================================================= */
|
||||
/* 1. AUTH - 승인 접수: 한도 점검 후 authorization 원장 기록 + 한도 차감. */
|
||||
/* ========================================================================= */
|
||||
void AUTH(TPSVCINFO *p)
|
||||
{
|
||||
UBFH *b = (UBFH *)p->data;
|
||||
char card[24], merch[64], bizdate[16], appr[12];
|
||||
long amount, auth_id, lim = 0, used = 0;
|
||||
|
||||
gets_(b, T_KEY1, card, sizeof(card));
|
||||
if (card[0] == 0) { userlog("AUTH: T_KEY1(카드번호) 누락"); FAIL(b); }
|
||||
gets_(b, T_MERCHANT, merch, sizeof(merch));
|
||||
amount = getl(b, T_AMOUNT);
|
||||
gets_(b, T_BIZDATE, bizdate, sizeof(bizdate));
|
||||
|
||||
/* 한도 점검: 초과면 거절(D) 상태로 기록 */
|
||||
if (audb_limit_get(card, &lim, &used) < 0) FAIL(b);
|
||||
if (used + amount > lim) {
|
||||
userlog("AUTH 한도초과 card=%s used=%ld amt=%ld lim=%ld -> 거절", card, used, amount, lim);
|
||||
auth_id = audb_next_auth_id();
|
||||
if (auth_id < 0) FAIL(b);
|
||||
if (audb_insert_auth(auth_id, card, merch, amount, "000000", "AUTH", "D", bizdate) < 0) FAIL(b);
|
||||
setl(b, T_ID1, auth_id);
|
||||
setl(b, T_RC, 1);
|
||||
Bchg(b, T_STATUS, 0, "D", 0L);
|
||||
OK(b);
|
||||
}
|
||||
auth_id = audb_next_auth_id();
|
||||
if (auth_id < 0) FAIL(b);
|
||||
sprintf(appr, "%06ld", auth_id % 1000000);
|
||||
if (audb_insert_auth(auth_id, card, merch, amount, appr, "AUTH", "A", bizdate) < 0) FAIL(b);
|
||||
if (audb_limit_dec(card, amount) < 0) FAIL(b);
|
||||
|
||||
setl(b, T_ID1, auth_id);
|
||||
setl(b, T_RC, 0);
|
||||
Bchg(b, T_STR2, 0, appr, 0L);
|
||||
Bchg(b, T_STATUS, 0, "A", 0L);
|
||||
userlog("AUTH 승인 auth_id=%ld card=%s merch=%s amt=%ld appr=%s", auth_id, card, merch, amount, appr);
|
||||
OK(b);
|
||||
}
|
||||
|
||||
/* 2. AUTH_CANCEL - 승인 전체 취소 + 한도 원복. */
|
||||
void AUTH_CANCEL(TPSVCINFO *p)
|
||||
{
|
||||
UBFH *b = (UBFH *)p->data;
|
||||
char card[24], bizdate[16], reason[128];
|
||||
EXEC SQL BEGIN DECLARE SECTION;
|
||||
long h_id, h_auth, h_amt;
|
||||
char h_card[24], h_bizdate[16], h_reason[128];
|
||||
EXEC SQL END DECLARE SECTION;
|
||||
|
||||
h_auth = getl(b, T_ID1);
|
||||
h_amt = getl(b, T_AMOUNT);
|
||||
gets_(b, T_KEY1, card, sizeof(card));
|
||||
gets_(b, T_BIZDATE, bizdate, sizeof(bizdate));
|
||||
gets_(b, T_REASON, reason, sizeof(reason)); if (reason[0] == 0) strcpy(reason, "CUSTOMER");
|
||||
strncpy(h_card, card, sizeof(h_card)-1); h_card[sizeof(h_card)-1] = 0;
|
||||
strncpy(h_bizdate, bizdate, sizeof(h_bizdate)-1); h_bizdate[sizeof(h_bizdate)-1] = 0;
|
||||
strncpy(h_reason, reason, sizeof(h_reason)-1); h_reason[sizeof(h_reason)-1] = 0;
|
||||
|
||||
EXEC SQL SELECT nextval('au_cancel_seq') INTO :h_id;
|
||||
if (sqlca.sqlcode < 0) FAIL(b);
|
||||
EXEC SQL INSERT INTO au_cancel (cancel_id, auth_id, cancel_type, amount, reason, biz_date)
|
||||
VALUES (:h_id, :h_auth, 'FULL', :h_amt, :h_reason, :h_bizdate);
|
||||
if (sqlca.sqlcode < 0) FAIL(b);
|
||||
if (audb_update_auth_status(h_auth, "C") < 0) FAIL(b);
|
||||
if (card[0] && audb_limit_dec(card, -h_amt) < 0) FAIL(b);
|
||||
setl(b, T_CANCEL_ID, h_id);
|
||||
Bchg(b, T_STATUS, 0, "C", 0L);
|
||||
OK(b);
|
||||
}
|
||||
|
||||
/* 3. AUTH_PARTCXL - 부분 취소: 승인 금액 감액 + 한도 부분 원복. */
|
||||
void AUTH_PARTCXL(TPSVCINFO *p)
|
||||
{
|
||||
UBFH *b = (UBFH *)p->data;
|
||||
char card[24], bizdate[16];
|
||||
EXEC SQL BEGIN DECLARE SECTION;
|
||||
long h_id, h_auth, h_amt, h_new;
|
||||
char h_bizdate[16];
|
||||
EXEC SQL END DECLARE SECTION;
|
||||
|
||||
h_auth = getl(b, T_ID1);
|
||||
h_amt = getl(b, T_AMOUNT); /* 취소 금액 */
|
||||
gets_(b, T_KEY1, card, sizeof(card));
|
||||
gets_(b, T_BIZDATE, bizdate, sizeof(bizdate));
|
||||
strncpy(h_bizdate, bizdate, sizeof(h_bizdate)-1); h_bizdate[sizeof(h_bizdate)-1] = 0;
|
||||
|
||||
EXEC SQL SELECT amount INTO :h_new FROM au_authorization WHERE auth_id = :h_auth;
|
||||
if (sqlca.sqlcode < 0 || sqlca.sqlcode == 100) FAIL(b);
|
||||
h_new = h_new - h_amt;
|
||||
EXEC SQL SELECT nextval('au_cancel_seq') INTO :h_id;
|
||||
if (sqlca.sqlcode < 0) FAIL(b);
|
||||
EXEC SQL INSERT INTO au_cancel (cancel_id, auth_id, cancel_type, amount, reason, biz_date)
|
||||
VALUES (:h_id, :h_auth, 'PARTIAL', :h_amt, 'PARTIAL', :h_bizdate);
|
||||
if (sqlca.sqlcode < 0) FAIL(b);
|
||||
if (audb_update_auth_amount(h_auth, h_new) < 0) FAIL(b);
|
||||
if (card[0] && audb_limit_dec(card, -h_amt) < 0) FAIL(b);
|
||||
setl(b, T_CANCEL_ID, h_id);
|
||||
setl(b, T_AMOUNT, h_new);
|
||||
OK(b);
|
||||
}
|
||||
|
||||
/* 4. LIMIT_CHK - 카드 한도 점검 (사용가능 여부만 리턴). */
|
||||
void LIMIT_CHK(TPSVCINFO *p)
|
||||
{
|
||||
UBFH *b = (UBFH *)p->data;
|
||||
char card[24];
|
||||
long amount, lim = 0, used = 0;
|
||||
gets_(b, T_KEY1, card, sizeof(card));
|
||||
amount = getl(b, T_AMOUNT);
|
||||
if (audb_limit_get(card, &lim, &used) < 0) FAIL(b);
|
||||
setl(b, T_AMT1, lim);
|
||||
setl(b, T_AMT2, used);
|
||||
setl(b, T_RC, (used + amount > lim) ? 1 : 0);
|
||||
OK(b);
|
||||
}
|
||||
|
||||
/* 5. LIMIT_DEC - 한도 차감(사용금액 증가). */
|
||||
void LIMIT_DEC(TPSVCINFO *p)
|
||||
{
|
||||
UBFH *b = (UBFH *)p->data;
|
||||
char card[24];
|
||||
long amount = getl(b, T_AMOUNT);
|
||||
gets_(b, T_KEY1, card, sizeof(card));
|
||||
if (audb_limit_dec(card, amount) < 0) FAIL(b);
|
||||
OK(b);
|
||||
}
|
||||
|
||||
/* 6. LIMIT_RST - 한도 사용금액 초기화(일마감 원복). */
|
||||
void LIMIT_RST(TPSVCINFO *p)
|
||||
{
|
||||
UBFH *b = (UBFH *)p->data;
|
||||
char card[24];
|
||||
gets_(b, T_KEY1, card, sizeof(card));
|
||||
if (audb_limit_reset(card) < 0) FAIL(b);
|
||||
OK(b);
|
||||
}
|
||||
|
||||
/* 7. LIMIT_INQ - 한도 조회 (가용한도 계산). */
|
||||
void LIMIT_INQ(TPSVCINFO *p)
|
||||
{
|
||||
UBFH *b = (UBFH *)p->data;
|
||||
char card[24];
|
||||
long lim = 0, used = 0;
|
||||
gets_(b, T_KEY1, card, sizeof(card));
|
||||
if (audb_limit_get(card, &lim, &used) < 0) FAIL(b);
|
||||
setl(b, T_AMT1, lim);
|
||||
setl(b, T_AMT2, used);
|
||||
setl(b, T_AMT3, lim - used); /* 가용한도 */
|
||||
OK(b);
|
||||
}
|
||||
|
||||
/* 8. STANDIN - 발급사 무응답 시 대리승인(STANDIN, 소액 한도 내). */
|
||||
void STANDIN(TPSVCINFO *p)
|
||||
{
|
||||
UBFH *b = (UBFH *)p->data;
|
||||
char card[24], merch[64], bizdate[16];
|
||||
long amount, auth_id, stand_lim = 50000;
|
||||
gets_(b, T_KEY1, card, sizeof(card));
|
||||
gets_(b, T_MERCHANT, merch, sizeof(merch));
|
||||
amount = getl(b, T_AMOUNT);
|
||||
gets_(b, T_BIZDATE, bizdate, sizeof(bizdate));
|
||||
if (amount > stand_lim) { setl(b, T_RC, 1); Bchg(b, T_STATUS, 0, "D", 0L); OK(b); }
|
||||
auth_id = audb_next_auth_id();
|
||||
if (auth_id < 0) FAIL(b);
|
||||
if (audb_insert_auth(auth_id, card, merch, amount, "STDIN0", "STANDIN", "A", bizdate) < 0) FAIL(b);
|
||||
setl(b, T_ID1, auth_id);
|
||||
setl(b, T_RC, 0);
|
||||
Bchg(b, T_STATUS, 0, "A", 0L);
|
||||
OK(b);
|
||||
}
|
||||
|
||||
/* 9. FRAUD_DETECT - 부정거래 스코어링 + 위험시 fraud_log 기록. */
|
||||
void FRAUD_DETECT(TPSVCINFO *p)
|
||||
{
|
||||
UBFH *b = (UBFH *)p->data;
|
||||
char card[24], bizdate[16], reason[128];
|
||||
long auth_id, amount, score = 0, fraud_id;
|
||||
EXEC SQL BEGIN DECLARE SECTION;
|
||||
long h_cnt;
|
||||
char h_card[24];
|
||||
EXEC SQL END DECLARE SECTION;
|
||||
|
||||
auth_id = getl(b, T_ID1);
|
||||
amount = getl(b, T_AMOUNT);
|
||||
gets_(b, T_KEY1, card, sizeof(card));
|
||||
gets_(b, T_BIZDATE, bizdate, sizeof(bizdate));
|
||||
strncpy(h_card, card, sizeof(h_card)-1); h_card[sizeof(h_card)-1] = 0;
|
||||
|
||||
/* 룰: 고액 + 당일 승인건수 과다 = 스코어 상승 */
|
||||
if (amount > 1000000) score += 50;
|
||||
EXEC SQL SELECT count(*) INTO :h_cnt FROM au_authorization WHERE card_no = :h_card;
|
||||
if (sqlca.sqlcode < 0) FAIL(b);
|
||||
if (h_cnt > 5) score += 40;
|
||||
score += (h_cnt * 2);
|
||||
|
||||
strcpy(reason, score >= 50 ? "HIGH_RISK" : "OK");
|
||||
setl(b, T_ARG1, score);
|
||||
if (score >= 50) {
|
||||
fraud_id = audb_insert_fraud(auth_id, card, score, reason, bizdate);
|
||||
if (fraud_id < 0) FAIL(b);
|
||||
setl(b, T_ID2, fraud_id);
|
||||
setl(b, T_RC, 1);
|
||||
} else {
|
||||
setl(b, T_RC, 0);
|
||||
}
|
||||
OK(b);
|
||||
}
|
||||
|
||||
/* 10. AUTH_RETRY - 거절 승인 재시도 (상태 D->A 재승인). */
|
||||
void AUTH_RETRY(TPSVCINFO *p)
|
||||
{
|
||||
UBFH *b = (UBFH *)p->data;
|
||||
char card[24];
|
||||
long auth_id = getl(b, T_ID1), amount = getl(b, T_AMOUNT), lim = 0, used = 0;
|
||||
gets_(b, T_KEY1, card, sizeof(card));
|
||||
if (audb_limit_get(card, &lim, &used) < 0) FAIL(b);
|
||||
if (used + amount > lim) { setl(b, T_RC, 1); FAIL(b); }
|
||||
if (audb_update_auth_status(auth_id, "A") < 0) FAIL(b);
|
||||
if (audb_limit_dec(card, amount) < 0) FAIL(b);
|
||||
setl(b, T_RC, 0);
|
||||
Bchg(b, T_STATUS, 0, "A", 0L);
|
||||
OK(b);
|
||||
}
|
||||
|
||||
/* 11. INSTALL_AUTH - 할부 승인: 개월 수만큼 분할 승인 레코드. */
|
||||
void INSTALL_AUTH(TPSVCINFO *p)
|
||||
{
|
||||
UBFH *b = (UBFH *)p->data;
|
||||
char card[24], merch[64], bizdate[16];
|
||||
long months, amount, i, auth_id;
|
||||
gets_(b, T_KEY1, card, sizeof(card));
|
||||
gets_(b, T_MERCHANT, merch, sizeof(merch));
|
||||
amount = getl(b, T_AMOUNT);
|
||||
months = getl(b, T_INSTALL_N); if (months < 1) months = 3;
|
||||
gets_(b, T_BIZDATE, bizdate, sizeof(bizdate));
|
||||
for (i = 1; i <= months; i++) {
|
||||
auth_id = audb_next_auth_id();
|
||||
if (auth_id < 0) FAIL(b);
|
||||
if (audb_insert_auth(auth_id, card, merch, amount / months, "INST00", "INSTALL", "A", bizdate) < 0) FAIL(b);
|
||||
}
|
||||
if (audb_limit_dec(card, amount) < 0) FAIL(b);
|
||||
setl(b, T_COUNT, months);
|
||||
OK(b);
|
||||
}
|
||||
|
||||
/* 12. AUTH_STATUS - 승인 상태 조회 (dbio). */
|
||||
void AUTH_STATUS(TPSVCINFO *p)
|
||||
{
|
||||
UBFH *b = (UBFH *)p->data;
|
||||
long auth_id = getl(b, T_ID1);
|
||||
char st[4];
|
||||
if (audb_get_auth_status(auth_id, st) < 0) FAIL(b);
|
||||
Bchg(b, T_STATUS, 0, st, 0L);
|
||||
OK(b);
|
||||
}
|
||||
|
||||
/* 13. AUTH_REVERSE - 승인 역전(reversal) + 한도 원복. */
|
||||
void AUTH_REVERSE(TPSVCINFO *p)
|
||||
{
|
||||
UBFH *b = (UBFH *)p->data;
|
||||
char card[24];
|
||||
long auth_id = getl(b, T_ID1), amount = getl(b, T_AMOUNT);
|
||||
gets_(b, T_KEY1, card, sizeof(card));
|
||||
if (audb_update_auth_status(auth_id, "R") < 0) FAIL(b);
|
||||
if (card[0] && audb_limit_dec(card, -amount) < 0) FAIL(b);
|
||||
Bchg(b, T_STATUS, 0, "R", 0L);
|
||||
OK(b);
|
||||
}
|
||||
|
||||
/* 14. AUTH_APPROVE - 승인 확정(pending->approved). */
|
||||
void AUTH_APPROVE(TPSVCINFO *p)
|
||||
{
|
||||
UBFH *b = (UBFH *)p->data;
|
||||
long auth_id = getl(b, T_ID1);
|
||||
if (audb_update_auth_status(auth_id, "A") < 0) FAIL(b);
|
||||
Bchg(b, T_STATUS, 0, "A", 0L);
|
||||
OK(b);
|
||||
}
|
||||
|
||||
/* 15. AUTH_DECLINE - 승인 거절 처리. */
|
||||
void AUTH_DECLINE(TPSVCINFO *p)
|
||||
{
|
||||
UBFH *b = (UBFH *)p->data;
|
||||
char reason[128];
|
||||
EXEC SQL BEGIN DECLARE SECTION;
|
||||
long h_auth;
|
||||
char h_reason[128];
|
||||
EXEC SQL END DECLARE SECTION;
|
||||
h_auth = getl(b, T_ID1);
|
||||
gets_(b, T_REASON, reason, sizeof(reason)); if (reason[0] == 0) strcpy(reason, "DECLINE");
|
||||
strncpy(h_reason, reason, sizeof(h_reason)-1); h_reason[sizeof(h_reason)-1] = 0;
|
||||
EXEC SQL UPDATE au_authorization SET status = 'D', decline_reason = :h_reason, updated_at = now()
|
||||
WHERE auth_id = :h_auth;
|
||||
if (sqlca.sqlcode < 0 || sqlca.sqlcode == 100) FAIL(b);
|
||||
Bchg(b, T_STATUS, 0, "D", 0L);
|
||||
OK(b);
|
||||
}
|
||||
|
||||
/* 16. CARD_VALIDATE - 카드번호 Luhn 검증 (common lib). */
|
||||
void CARD_VALIDATE(TPSVCINFO *p)
|
||||
{
|
||||
UBFH *b = (UBFH *)p->data;
|
||||
char card[24];
|
||||
gets_(b, T_KEY1, card, sizeof(card));
|
||||
setl(b, T_RC, acq_luhn_ok(card) ? 0 : 1);
|
||||
OK(b);
|
||||
}
|
||||
|
||||
/* 17. VELOCITY_CHK - 속도(velocity) 점검: 당일 카드 승인건수/합계. */
|
||||
void VELOCITY_CHK(TPSVCINFO *p)
|
||||
{
|
||||
UBFH *b = (UBFH *)p->data;
|
||||
char card[24], bizdate[16];
|
||||
EXEC SQL BEGIN DECLARE SECTION;
|
||||
long h_cnt, h_sum;
|
||||
char h_card[24], h_bizdate[16];
|
||||
EXEC SQL END DECLARE SECTION;
|
||||
gets_(b, T_KEY1, card, sizeof(card));
|
||||
gets_(b, T_BIZDATE, bizdate, sizeof(bizdate));
|
||||
strncpy(h_card, card, sizeof(h_card)-1); h_card[sizeof(h_card)-1] = 0;
|
||||
strncpy(h_bizdate, bizdate, sizeof(h_bizdate)-1); h_bizdate[sizeof(h_bizdate)-1] = 0;
|
||||
EXEC SQL SELECT count(*), coalesce(sum(amount),0) INTO :h_cnt, :h_sum
|
||||
FROM au_authorization WHERE card_no = :h_card AND biz_date = :h_bizdate;
|
||||
if (sqlca.sqlcode < 0) FAIL(b);
|
||||
setl(b, T_COUNT, h_cnt);
|
||||
setl(b, T_GROSS, h_sum);
|
||||
setl(b, T_RC, (h_cnt > 10) ? 1 : 0);
|
||||
OK(b);
|
||||
}
|
||||
|
||||
/* 18. BLACKLIST_CHK - 블랙리스트 카드 조회. */
|
||||
void BLACKLIST_CHK(TPSVCINFO *p)
|
||||
{
|
||||
UBFH *b = (UBFH *)p->data;
|
||||
char card[24];
|
||||
EXEC SQL BEGIN DECLARE SECTION;
|
||||
long h_cnt;
|
||||
char h_card[24];
|
||||
EXEC SQL END DECLARE SECTION;
|
||||
gets_(b, T_KEY1, card, sizeof(card));
|
||||
strncpy(h_card, card, sizeof(h_card)-1); h_card[sizeof(h_card)-1] = 0;
|
||||
EXEC SQL SELECT count(*) INTO :h_cnt FROM au_blacklist WHERE card_no = :h_card;
|
||||
if (sqlca.sqlcode < 0) FAIL(b);
|
||||
setl(b, T_RC, (h_cnt > 0) ? 1 : 0);
|
||||
OK(b);
|
||||
}
|
||||
|
||||
/* 19. AUTH_HOLD - 승인 보류. */
|
||||
void AUTH_HOLD(TPSVCINFO *p)
|
||||
{
|
||||
UBFH *b = (UBFH *)p->data;
|
||||
long auth_id = getl(b, T_ID1);
|
||||
if (audb_update_auth_status(auth_id, "H") < 0) FAIL(b);
|
||||
Bchg(b, T_STATUS, 0, "H", 0L);
|
||||
OK(b);
|
||||
}
|
||||
|
||||
/* 20. AUTH_RELEASE - 보류 해제. */
|
||||
void AUTH_RELEASE(TPSVCINFO *p)
|
||||
{
|
||||
UBFH *b = (UBFH *)p->data;
|
||||
long auth_id = getl(b, T_ID1);
|
||||
if (audb_update_auth_status(auth_id, "A") < 0) FAIL(b);
|
||||
Bchg(b, T_STATUS, 0, "A", 0L);
|
||||
OK(b);
|
||||
}
|
||||
|
||||
/* 21. AUTH_HISTORY - 카드 승인 이력 건수 조회. */
|
||||
void AUTH_HISTORY(TPSVCINFO *p)
|
||||
{
|
||||
UBFH *b = (UBFH *)p->data;
|
||||
char card[24];
|
||||
EXEC SQL BEGIN DECLARE SECTION;
|
||||
long h_cnt;
|
||||
char h_card[24];
|
||||
EXEC SQL END DECLARE SECTION;
|
||||
gets_(b, T_KEY1, card, sizeof(card));
|
||||
strncpy(h_card, card, sizeof(h_card)-1); h_card[sizeof(h_card)-1] = 0;
|
||||
EXEC SQL SELECT count(*) INTO :h_cnt FROM au_authorization WHERE card_no = :h_card;
|
||||
if (sqlca.sqlcode < 0) FAIL(b);
|
||||
setl(b, T_COUNT, h_cnt);
|
||||
OK(b);
|
||||
}
|
||||
|
||||
/* 22. LIMIT_SET - 카드 한도 설정/등록 (dbio upsert). */
|
||||
void LIMIT_SET(TPSVCINFO *p)
|
||||
{
|
||||
UBFH *b = (UBFH *)p->data;
|
||||
char card[24];
|
||||
long lim = getl(b, T_AMT1);
|
||||
gets_(b, T_KEY1, card, sizeof(card));
|
||||
if (lim <= 0) lim = 1000000;
|
||||
if (audb_limit_set(card, lim) < 0) FAIL(b);
|
||||
OK(b);
|
||||
}
|
||||
|
||||
/* 23. LIMIT_TEMP - 임시 한도 상향 (temp_limit 저장 + 당일 상향). */
|
||||
void LIMIT_TEMP(TPSVCINFO *p)
|
||||
{
|
||||
UBFH *b = (UBFH *)p->data;
|
||||
char card[24];
|
||||
EXEC SQL BEGIN DECLARE SECTION;
|
||||
long h_temp;
|
||||
char h_card[24];
|
||||
EXEC SQL END DECLARE SECTION;
|
||||
gets_(b, T_KEY1, card, sizeof(card));
|
||||
h_temp = getl(b, T_AMT1); if (h_temp <= 0) h_temp = 2000000;
|
||||
strncpy(h_card, card, sizeof(h_card)-1); h_card[sizeof(h_card)-1] = 0;
|
||||
EXEC SQL UPDATE card_limit
|
||||
SET temp_limit = :h_temp, daily_limit = daily_limit + :h_temp, updated_at = now()
|
||||
WHERE card_no = :h_card;
|
||||
if (sqlca.sqlcode < 0 || sqlca.sqlcode == 100) FAIL(b);
|
||||
OK(b);
|
||||
}
|
||||
|
||||
/* 24. AUTH_SUMMARY - 가맹점 당일 승인 집계 upsert (dbio). */
|
||||
void AUTH_SUMMARY(TPSVCINFO *p)
|
||||
{
|
||||
UBFH *b = (UBFH *)p->data;
|
||||
char merch[64], bizdate[16];
|
||||
EXEC SQL BEGIN DECLARE SECTION;
|
||||
long h_cnt, h_appr, h_dec, h_amt;
|
||||
char h_merch[64], h_bizdate[16];
|
||||
EXEC SQL END DECLARE SECTION;
|
||||
gets_(b, T_MERCHANT, merch, sizeof(merch));
|
||||
gets_(b, T_BIZDATE, bizdate, sizeof(bizdate));
|
||||
strncpy(h_merch, merch, sizeof(h_merch)-1); h_merch[sizeof(h_merch)-1] = 0;
|
||||
strncpy(h_bizdate, bizdate, sizeof(h_bizdate)-1); h_bizdate[sizeof(h_bizdate)-1] = 0;
|
||||
EXEC SQL SELECT count(*),
|
||||
count(*) FILTER (WHERE status = 'A'),
|
||||
count(*) FILTER (WHERE status = 'D'),
|
||||
coalesce(sum(amount),0)
|
||||
INTO :h_cnt, :h_appr, :h_dec, :h_amt
|
||||
FROM au_authorization WHERE merchant_id = :h_merch AND biz_date = :h_bizdate;
|
||||
if (sqlca.sqlcode < 0) FAIL(b);
|
||||
if (audb_upsert_auth_summary(merch, bizdate, h_cnt, h_appr, h_dec, h_amt) < 0) FAIL(b);
|
||||
setl(b, T_COUNT, h_cnt);
|
||||
setl(b, T_GROSS, h_amt);
|
||||
OK(b);
|
||||
}
|
||||
|
||||
/* 25. AUTH_ROUTE - 승인 라우팅(발급사 BIN 기준 라우팅 코드 산출). */
|
||||
void AUTH_ROUTE(TPSVCINFO *p)
|
||||
{
|
||||
UBFH *b = (UBFH *)p->data;
|
||||
char card[24], route[8];
|
||||
long bin;
|
||||
gets_(b, T_KEY1, card, sizeof(card));
|
||||
bin = atol(card) / 1000000000L; /* 앞 6자리 근사 BIN */
|
||||
if (card[0] == '4') strcpy(route, "VISA");
|
||||
else if (card[0] == '5') strcpy(route, "MC");
|
||||
else if (card[0] == '3') strcpy(route, "AMEX");
|
||||
else strcpy(route, "LOCAL");
|
||||
Bchg(b, T_STR1, 0, route, 0L);
|
||||
setl(b, T_ARG1, bin);
|
||||
OK(b);
|
||||
}
|
||||
|
||||
/* 26. DCC_QUOTE - 해외승인 DCC 환산 견적(통화 -> 원화). */
|
||||
void DCC_QUOTE(TPSVCINFO *p)
|
||||
{
|
||||
UBFH *b = (UBFH *)p->data;
|
||||
char ccy[8];
|
||||
long fx, rate = 1350, krw;
|
||||
fx = getl(b, T_FX_AMT);
|
||||
gets_(b, T_CCY, ccy, sizeof(ccy)); if (ccy[0] == 0) strcpy(ccy, "USD");
|
||||
krw = fx * rate / 100;
|
||||
setl(b, T_AMOUNT, krw);
|
||||
setl(b, T_ARG1, rate);
|
||||
OK(b);
|
||||
}
|
||||
|
||||
/* 27. AUTH_CAPTURE - 승인 매입확정(capture): 상태 A->M(매입대상). */
|
||||
void AUTH_CAPTURE(TPSVCINFO *p)
|
||||
{
|
||||
UBFH *b = (UBFH *)p->data;
|
||||
EXEC SQL BEGIN DECLARE SECTION;
|
||||
long h_auth;
|
||||
EXEC SQL END DECLARE SECTION;
|
||||
h_auth = getl(b, T_ID1);
|
||||
EXEC SQL UPDATE au_authorization SET status = 'M', captured_at = now(), updated_at = now()
|
||||
WHERE auth_id = :h_auth AND status = 'A';
|
||||
if (sqlca.sqlcode < 0) FAIL(b);
|
||||
if (sqlca.sqlcode == 100) { setl(b, T_RC, 1); FAIL(b); }
|
||||
setl(b, T_RC, 0);
|
||||
Bchg(b, T_STATUS, 0, "M", 0L);
|
||||
OK(b);
|
||||
}
|
||||
|
||||
/* 28. PREAUTH - 예비승인(pre-authorization) 기록. */
|
||||
void PREAUTH(TPSVCINFO *p)
|
||||
{
|
||||
UBFH *b = (UBFH *)p->data;
|
||||
char card[24], merch[64], bizdate[16];
|
||||
EXEC SQL BEGIN DECLARE SECTION;
|
||||
long h_id, h_amt;
|
||||
char h_card[24], h_merch[64], h_bizdate[16];
|
||||
EXEC SQL END DECLARE SECTION;
|
||||
gets_(b, T_KEY1, card, sizeof(card));
|
||||
gets_(b, T_MERCHANT, merch, sizeof(merch));
|
||||
gets_(b, T_BIZDATE, bizdate, sizeof(bizdate));
|
||||
h_amt = getl(b, T_AMOUNT);
|
||||
strncpy(h_card, card, sizeof(h_card)-1); h_card[sizeof(h_card)-1] = 0;
|
||||
strncpy(h_merch, merch, sizeof(h_merch)-1); h_merch[sizeof(h_merch)-1] = 0;
|
||||
strncpy(h_bizdate, bizdate, sizeof(h_bizdate)-1); h_bizdate[sizeof(h_bizdate)-1] = 0;
|
||||
EXEC SQL SELECT nextval('au_preauth_seq') INTO :h_id;
|
||||
if (sqlca.sqlcode < 0) FAIL(b);
|
||||
EXEC SQL INSERT INTO au_preauth (preauth_id, card_no, merchant_id, amount, status, biz_date)
|
||||
VALUES (:h_id, :h_card, :h_merch, :h_amt, 'P', :h_bizdate);
|
||||
if (sqlca.sqlcode < 0) FAIL(b);
|
||||
setl(b, T_ID2, h_id);
|
||||
OK(b);
|
||||
}
|
||||
|
||||
/* 29. PREAUTH_COMPLETE - 예비승인 확정: preauth 소진 + 본승인 생성. */
|
||||
void PREAUTH_COMPLETE(TPSVCINFO *p)
|
||||
{
|
||||
UBFH *b = (UBFH *)p->data;
|
||||
char bizdate[16];
|
||||
long auth_id;
|
||||
EXEC SQL BEGIN DECLARE SECTION;
|
||||
long h_pre, h_amt;
|
||||
char h_card[24], h_merch[64], h_bizdate[16];
|
||||
EXEC SQL END DECLARE SECTION;
|
||||
h_pre = getl(b, T_ID2);
|
||||
gets_(b, T_BIZDATE, bizdate, sizeof(bizdate));
|
||||
strncpy(h_bizdate, bizdate, sizeof(h_bizdate)-1); h_bizdate[sizeof(h_bizdate)-1] = 0;
|
||||
|
||||
EXEC SQL SELECT card_no, merchant_id, amount INTO :h_card, :h_merch, :h_amt
|
||||
FROM au_preauth WHERE preauth_id = :h_pre;
|
||||
if (sqlca.sqlcode < 0 || sqlca.sqlcode == 100) FAIL(b);
|
||||
EXEC SQL UPDATE au_preauth SET status = 'C' WHERE preauth_id = :h_pre;
|
||||
if (sqlca.sqlcode < 0) FAIL(b);
|
||||
auth_id = audb_next_auth_id();
|
||||
if (auth_id < 0) FAIL(b);
|
||||
if (audb_insert_auth(auth_id, h_card, h_merch, h_amt, "PRECMP", "PREAUTH", "A", bizdate) < 0) FAIL(b);
|
||||
setl(b, T_ID1, auth_id);
|
||||
OK(b);
|
||||
}
|
||||
|
||||
/* 30. AUTH_VOID - 당일 승인 무효화(void) + 한도 원복. */
|
||||
void AUTH_VOID(TPSVCINFO *p)
|
||||
{
|
||||
UBFH *b = (UBFH *)p->data;
|
||||
char card[24], bizdate[16];
|
||||
EXEC SQL BEGIN DECLARE SECTION;
|
||||
long h_id, h_auth, h_amt;
|
||||
char h_bizdate[16];
|
||||
EXEC SQL END DECLARE SECTION;
|
||||
h_auth = getl(b, T_ID1); h_amt = getl(b, T_AMOUNT);
|
||||
gets_(b, T_KEY1, card, sizeof(card));
|
||||
gets_(b, T_BIZDATE, bizdate, sizeof(bizdate));
|
||||
strncpy(h_bizdate, bizdate, sizeof(h_bizdate)-1); h_bizdate[sizeof(h_bizdate)-1] = 0;
|
||||
EXEC SQL SELECT nextval('au_cancel_seq') INTO :h_id;
|
||||
if (sqlca.sqlcode < 0) FAIL(b);
|
||||
EXEC SQL INSERT INTO au_cancel (cancel_id, auth_id, cancel_type, amount, reason, biz_date)
|
||||
VALUES (:h_id, :h_auth, 'VOID', :h_amt, 'VOID', :h_bizdate);
|
||||
if (sqlca.sqlcode < 0) FAIL(b);
|
||||
if (audb_update_auth_status(h_auth, "V") < 0) FAIL(b);
|
||||
if (card[0] && audb_limit_dec(card, -h_amt) < 0) FAIL(b);
|
||||
setl(b, T_CANCEL_ID, h_id);
|
||||
Bchg(b, T_STATUS, 0, "V", 0L);
|
||||
OK(b);
|
||||
}
|
||||
|
||||
/* vim: set ts=4 sw=4 et smartindent: */
|
||||
58
app/src/au/batch/au_fraud_scan_batch.pgc
Normal file
58
app/src/au/batch/au_fraud_scan_batch.pgc
Normal file
|
|
@ -0,0 +1,58 @@
|
|||
/*
|
||||
* au_fraud_scan_batch.pgc - au 부정거래 스캔 배치 (GROUP BY HAVING + XA).
|
||||
*
|
||||
* Groups the day's authorizations by card and, for cards whose승인건수/합계 that
|
||||
* exceed velocity thresholds (HAVING), inserts a fraud_log row via the dbio
|
||||
* layer - all under ONE global XA transaction. Usage: au_fraud_scan_batch [YYYY-MM-DD]
|
||||
*/
|
||||
#include <stdio.h>
|
||||
#include <stdlib.h>
|
||||
#include <string.h>
|
||||
#include <atmi.h>
|
||||
#include "au_dbio.h"
|
||||
|
||||
int main(int argc, char **argv)
|
||||
{
|
||||
const char *bizdate = (argc > 1) ? argv[1] : "2026-07-19";
|
||||
long rows = 0;
|
||||
|
||||
EXEC SQL BEGIN DECLARE SECTION;
|
||||
char h_bizdate[16];
|
||||
char h_card[24];
|
||||
long h_cnt, h_sum, h_score;
|
||||
EXEC SQL END DECLARE SECTION;
|
||||
|
||||
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 fsc CURSOR FOR
|
||||
SELECT card_no, count(*), coalesce(sum(amount),0)
|
||||
FROM au_authorization
|
||||
WHERE biz_date = :h_bizdate
|
||||
GROUP BY card_no
|
||||
HAVING count(*) > 5 OR coalesce(sum(amount),0) > 5000000
|
||||
ORDER BY card_no;
|
||||
EXEC SQL OPEN fsc;
|
||||
if (sqlca.sqlcode < 0) { fprintf(stderr, "OPEN FAIL [%d] %s\n", sqlca.sqlcode, sqlca.sqlerrm.sqlerrmc); tpabort(0); return 1; }
|
||||
|
||||
for (;;) {
|
||||
EXEC SQL FETCH fsc INTO :h_card, :h_cnt, :h_sum;
|
||||
if (sqlca.sqlcode == 100) break;
|
||||
if (sqlca.sqlcode < 0) { fprintf(stderr, "FETCH FAIL [%d] %s\n", sqlca.sqlcode, sqlca.sqlerrm.sqlerrmc); EXEC SQL CLOSE fsc; tpabort(0); return 1; }
|
||||
h_score = h_cnt * 10 + (h_sum > 5000000 ? 40 : 0);
|
||||
if (audb_insert_fraud(0, h_card, h_score, "VELOCITY_BATCH", bizdate) < 0) {
|
||||
EXEC SQL CLOSE fsc; tpabort(0); return 1;
|
||||
}
|
||||
rows++;
|
||||
}
|
||||
EXEC SQL CLOSE fsc;
|
||||
|
||||
if (tpcommit(0) < 0) { fprintf(stderr, "tpcommit FAIL: %s\n", tpstrerror(tperrno)); tpabort(0); return 1; }
|
||||
printf(">>> au_fraud_scan_batch COMMIT: bizdate=%s flagged_cards=%ld\n", bizdate, rows);
|
||||
|
||||
tpclose(); tpterm();
|
||||
return 0;
|
||||
}
|
||||
54
app/src/au/batch/au_settle_reflect_batch.pgc
Normal file
54
app/src/au/batch/au_settle_reflect_batch.pgc
Normal file
|
|
@ -0,0 +1,54 @@
|
|||
/*
|
||||
* au_settle_reflect_batch.pgc - au 승인 정산반영 배치 (커서 M->S + XA).
|
||||
*
|
||||
* ATMI client: tpinit/tpopen open the ECPG XA RM, tpbegin starts ONE global
|
||||
* transaction, an EXEC SQL cursor scans captured authorizations (status='M')
|
||||
* for a biz_date and flips each to settled (status='S') via the dbio layer,
|
||||
* then tpcommit drives XA 2PC. Usage: au_settle_reflect_batch [YYYY-MM-DD]
|
||||
*/
|
||||
#include <stdio.h>
|
||||
#include <stdlib.h>
|
||||
#include <string.h>
|
||||
#include <atmi.h>
|
||||
#include "au_dbio.h"
|
||||
|
||||
int main(int argc, char **argv)
|
||||
{
|
||||
const char *bizdate = (argc > 1) ? argv[1] : "2026-07-19";
|
||||
long rows = 0;
|
||||
|
||||
EXEC SQL BEGIN DECLARE SECTION;
|
||||
char h_bizdate[16];
|
||||
long h_auth, h_amt;
|
||||
EXEC SQL END DECLARE SECTION;
|
||||
|
||||
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 arc CURSOR FOR
|
||||
SELECT auth_id, amount FROM au_authorization
|
||||
WHERE biz_date = :h_bizdate AND status = 'M'
|
||||
ORDER BY auth_id;
|
||||
EXEC SQL OPEN arc;
|
||||
if (sqlca.sqlcode < 0) { fprintf(stderr, "OPEN FAIL [%d] %s\n", sqlca.sqlcode, sqlca.sqlerrm.sqlerrmc); tpabort(0); return 1; }
|
||||
|
||||
for (;;) {
|
||||
EXEC SQL FETCH arc INTO :h_auth, :h_amt;
|
||||
if (sqlca.sqlcode == 100) break;
|
||||
if (sqlca.sqlcode < 0) { fprintf(stderr, "FETCH FAIL [%d] %s\n", sqlca.sqlcode, sqlca.sqlerrm.sqlerrmc); EXEC SQL CLOSE arc; tpabort(0); return 1; }
|
||||
if (audb_update_auth_status(h_auth, "S") < 0) {
|
||||
EXEC SQL CLOSE arc; tpabort(0); return 1;
|
||||
}
|
||||
rows++;
|
||||
}
|
||||
EXEC SQL CLOSE arc;
|
||||
|
||||
if (tpcommit(0) < 0) { fprintf(stderr, "tpcommit FAIL: %s\n", tpstrerror(tperrno)); tpabort(0); return 1; }
|
||||
printf(">>> au_settle_reflect_batch COMMIT: bizdate=%s reflected(M->S)=%ld\n", bizdate, rows);
|
||||
|
||||
tpclose(); tpterm();
|
||||
return 0;
|
||||
}
|
||||
121
app/src/au/dbio/au_auth_dbio.pgc
Normal file
121
app/src/au/dbio/au_auth_dbio.pgc
Normal file
|
|
@ -0,0 +1,121 @@
|
|||
/*
|
||||
* au_auth_dbio.pgc - 승인(authorization)/부정거래 DB 접근 (libaudbio.a).
|
||||
* ECPG functions; run on the caller's XA branch. No EXEC SQL CONNECT.
|
||||
*/
|
||||
#include <string.h>
|
||||
#include <userlog.h>
|
||||
#include "au_dbio.h"
|
||||
|
||||
long audb_next_auth_id(void)
|
||||
{
|
||||
EXEC SQL BEGIN DECLARE SECTION;
|
||||
long h_id;
|
||||
EXEC SQL END DECLARE SECTION;
|
||||
|
||||
EXEC SQL SELECT nextval('au_auth_seq') INTO :h_id;
|
||||
if (sqlca.sqlcode < 0) {
|
||||
userlog("audb_next_auth_id FAIL [%d] %s", sqlca.sqlcode, sqlca.sqlerrm.sqlerrmc);
|
||||
return -1;
|
||||
}
|
||||
return h_id;
|
||||
}
|
||||
|
||||
int audb_insert_auth(long auth_id, const char *card, const char *merch, long amount,
|
||||
const char *appr_code, const char *auth_type, const char *status,
|
||||
const char *bizdate)
|
||||
{
|
||||
EXEC SQL BEGIN DECLARE SECTION;
|
||||
long h_id = auth_id, h_amount = amount;
|
||||
char h_card[24], h_merch[64], h_appr[12], h_type[12], h_status[8], h_bizdate[16];
|
||||
EXEC SQL END DECLARE SECTION;
|
||||
|
||||
strncpy(h_card, card, sizeof(h_card) - 1); h_card[sizeof(h_card)-1] = 0;
|
||||
strncpy(h_merch, merch, sizeof(h_merch) - 1); h_merch[sizeof(h_merch)-1] = 0;
|
||||
strncpy(h_appr, appr_code, sizeof(h_appr) - 1); h_appr[sizeof(h_appr)-1] = 0;
|
||||
strncpy(h_type, auth_type, sizeof(h_type) - 1); h_type[sizeof(h_type)-1] = 0;
|
||||
strncpy(h_status, status, sizeof(h_status) - 1); h_status[sizeof(h_status)-1] = 0;
|
||||
strncpy(h_bizdate, bizdate, sizeof(h_bizdate) - 1); h_bizdate[sizeof(h_bizdate)-1] = 0;
|
||||
|
||||
EXEC SQL INSERT INTO au_authorization
|
||||
(auth_id, card_no, merchant_id, amount, approval_code, auth_type, status, biz_date)
|
||||
VALUES (:h_id, :h_card, :h_merch, :h_amount, :h_appr, :h_type, :h_status, :h_bizdate);
|
||||
if (sqlca.sqlcode < 0) {
|
||||
userlog("audb_insert_auth FAIL [%d] %s", sqlca.sqlcode, sqlca.sqlerrm.sqlerrmc);
|
||||
return -1;
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
int audb_update_auth_status(long auth_id, const char *status)
|
||||
{
|
||||
EXEC SQL BEGIN DECLARE SECTION;
|
||||
long h_id = auth_id;
|
||||
char h_status[8];
|
||||
EXEC SQL END DECLARE SECTION;
|
||||
|
||||
strncpy(h_status, status, sizeof(h_status) - 1); h_status[sizeof(h_status)-1] = 0;
|
||||
|
||||
EXEC SQL UPDATE au_authorization SET status = :h_status, updated_at = now()
|
||||
WHERE auth_id = :h_id;
|
||||
if (sqlca.sqlcode < 0 || sqlca.sqlcode == 100) {
|
||||
userlog("audb_update_auth_status FAIL [%d] %s", sqlca.sqlcode, sqlca.sqlerrm.sqlerrmc);
|
||||
return -1;
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
int audb_get_auth_status(long auth_id, char *out)
|
||||
{
|
||||
EXEC SQL BEGIN DECLARE SECTION;
|
||||
long h_id = auth_id;
|
||||
char h_status[8];
|
||||
EXEC SQL END DECLARE SECTION;
|
||||
|
||||
EXEC SQL SELECT status INTO :h_status FROM au_authorization WHERE auth_id = :h_id;
|
||||
if (sqlca.sqlcode < 0 || sqlca.sqlcode == 100) {
|
||||
userlog("audb_get_auth_status FAIL [%d] %s", sqlca.sqlcode, sqlca.sqlerrm.sqlerrmc);
|
||||
return -1;
|
||||
}
|
||||
out[0] = h_status[0];
|
||||
out[1] = 0;
|
||||
return 0;
|
||||
}
|
||||
|
||||
int audb_update_auth_amount(long auth_id, long amount)
|
||||
{
|
||||
EXEC SQL BEGIN DECLARE SECTION;
|
||||
long h_id = auth_id, h_amount = amount;
|
||||
EXEC SQL END DECLARE SECTION;
|
||||
|
||||
EXEC SQL UPDATE au_authorization SET amount = :h_amount, updated_at = now()
|
||||
WHERE auth_id = :h_id;
|
||||
if (sqlca.sqlcode < 0 || sqlca.sqlcode == 100) {
|
||||
userlog("audb_update_auth_amount FAIL [%d] %s", sqlca.sqlcode, sqlca.sqlerrm.sqlerrmc);
|
||||
return -1;
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
long audb_insert_fraud(long auth_id, const char *card, long score, const char *reason,
|
||||
const char *bizdate)
|
||||
{
|
||||
EXEC SQL BEGIN DECLARE SECTION;
|
||||
long h_id, h_auth = auth_id, h_score = score;
|
||||
char h_card[24], h_reason[128], h_bizdate[16];
|
||||
EXEC SQL END DECLARE SECTION;
|
||||
|
||||
strncpy(h_card, card, sizeof(h_card) - 1); h_card[sizeof(h_card)-1] = 0;
|
||||
strncpy(h_reason, reason, sizeof(h_reason) - 1); h_reason[sizeof(h_reason)-1] = 0;
|
||||
strncpy(h_bizdate, bizdate, sizeof(h_bizdate) - 1); h_bizdate[sizeof(h_bizdate)-1] = 0;
|
||||
|
||||
EXEC SQL SELECT nextval('au_fraud_seq') INTO :h_id;
|
||||
if (sqlca.sqlcode < 0) return -1;
|
||||
|
||||
EXEC SQL INSERT INTO au_fraud_log (fraud_id, auth_id, card_no, score, reason, biz_date)
|
||||
VALUES (:h_id, :h_auth, :h_card, :h_score, :h_reason, :h_bizdate);
|
||||
if (sqlca.sqlcode < 0) {
|
||||
userlog("audb_insert_fraud FAIL [%d] %s", sqlca.sqlcode, sqlca.sqlerrm.sqlerrmc);
|
||||
return -1;
|
||||
}
|
||||
return h_id;
|
||||
}
|
||||
32
app/src/au/dbio/au_dbio.h
Normal file
32
app/src/au/dbio/au_dbio.h
Normal file
|
|
@ -0,0 +1,32 @@
|
|||
/*
|
||||
* au_dbio.h - au 모듈 (승인/한도) DB 접근 계층 (libaudbio.a).
|
||||
* ECPG (EXEC SQL) functions compiled into a static library and linked into
|
||||
* au_svr and the au batches. They run on the caller's XA branch/connection
|
||||
* (no EXEC SQL CONNECT); sqlca is the shared per-thread ECPG SQLCA.
|
||||
* Return convention: 0 = ok, -1 = SQL error (caller may also inspect sqlca).
|
||||
*/
|
||||
#ifndef AU_DBIO_H
|
||||
#define AU_DBIO_H
|
||||
|
||||
/* authorization */
|
||||
long audb_next_auth_id(void);
|
||||
int audb_insert_auth(long auth_id, const char *card, const char *merch, long amount,
|
||||
const char *appr_code, const char *auth_type, const char *status,
|
||||
const char *bizdate);
|
||||
int audb_update_auth_status(long auth_id, const char *status);
|
||||
int audb_get_auth_status(long auth_id, char *out /* >= 2 bytes */);
|
||||
int audb_update_auth_amount(long auth_id, long amount);
|
||||
|
||||
/* fraud */
|
||||
long audb_insert_fraud(long auth_id, const char *card, long score, const char *reason,
|
||||
const char *bizdate);
|
||||
|
||||
/* card limit */
|
||||
int audb_limit_get(const char *card, long *limit_out, long *used_out);
|
||||
int audb_limit_dec(const char *card, long amount);
|
||||
int audb_limit_reset(const char *card);
|
||||
int audb_limit_set(const char *card, long limit);
|
||||
int audb_upsert_auth_summary(const char *merch, const char *bizdate, long cnt,
|
||||
long approved, long declined, long amount);
|
||||
|
||||
#endif /* AU_DBIO_H */
|
||||
108
app/src/au/dbio/au_limit_dbio.pgc
Normal file
108
app/src/au/dbio/au_limit_dbio.pgc
Normal file
|
|
@ -0,0 +1,108 @@
|
|||
/*
|
||||
* au_limit_dbio.pgc - 카드 한도(card_limit) + 승인 일집계 DB 접근 (libaudbio.a).
|
||||
*/
|
||||
#include <string.h>
|
||||
#include <userlog.h>
|
||||
#include "au_dbio.h"
|
||||
|
||||
int audb_limit_get(const char *card, long *limit_out, long *used_out)
|
||||
{
|
||||
EXEC SQL BEGIN DECLARE SECTION;
|
||||
long h_limit, h_used;
|
||||
char h_card[24];
|
||||
EXEC SQL END DECLARE SECTION;
|
||||
|
||||
strncpy(h_card, card, sizeof(h_card) - 1); h_card[sizeof(h_card)-1] = 0;
|
||||
|
||||
EXEC SQL SELECT daily_limit, used_amount INTO :h_limit, :h_used
|
||||
FROM card_limit WHERE card_no = :h_card;
|
||||
if (sqlca.sqlcode < 0 || sqlca.sqlcode == 100) {
|
||||
userlog("audb_limit_get FAIL [%d] %s", sqlca.sqlcode, sqlca.sqlerrm.sqlerrmc);
|
||||
return -1;
|
||||
}
|
||||
*limit_out = h_limit;
|
||||
*used_out = h_used;
|
||||
return 0;
|
||||
}
|
||||
|
||||
int audb_limit_dec(const char *card, long amount)
|
||||
{
|
||||
EXEC SQL BEGIN DECLARE SECTION;
|
||||
long h_amount = amount;
|
||||
char h_card[24];
|
||||
EXEC SQL END DECLARE SECTION;
|
||||
|
||||
strncpy(h_card, card, sizeof(h_card) - 1); h_card[sizeof(h_card)-1] = 0;
|
||||
|
||||
EXEC SQL UPDATE card_limit SET used_amount = used_amount + :h_amount, updated_at = now()
|
||||
WHERE card_no = :h_card;
|
||||
if (sqlca.sqlcode < 0 || sqlca.sqlcode == 100) {
|
||||
userlog("audb_limit_dec FAIL [%d] %s", sqlca.sqlcode, sqlca.sqlerrm.sqlerrmc);
|
||||
return -1;
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
int audb_limit_reset(const char *card)
|
||||
{
|
||||
EXEC SQL BEGIN DECLARE SECTION;
|
||||
char h_card[24];
|
||||
EXEC SQL END DECLARE SECTION;
|
||||
|
||||
strncpy(h_card, card, sizeof(h_card) - 1); h_card[sizeof(h_card)-1] = 0;
|
||||
|
||||
EXEC SQL UPDATE card_limit SET used_amount = 0, updated_at = now()
|
||||
WHERE card_no = :h_card;
|
||||
if (sqlca.sqlcode < 0 || sqlca.sqlcode == 100) {
|
||||
userlog("audb_limit_reset FAIL [%d] %s", sqlca.sqlcode, sqlca.sqlerrm.sqlerrmc);
|
||||
return -1;
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
int audb_limit_set(const char *card, long limit)
|
||||
{
|
||||
EXEC SQL BEGIN DECLARE SECTION;
|
||||
long h_limit = limit;
|
||||
char h_card[24];
|
||||
EXEC SQL END DECLARE SECTION;
|
||||
|
||||
strncpy(h_card, card, sizeof(h_card) - 1); h_card[sizeof(h_card)-1] = 0;
|
||||
|
||||
EXEC SQL INSERT INTO card_limit (card_no, daily_limit, used_amount)
|
||||
VALUES (:h_card, :h_limit, 0)
|
||||
ON CONFLICT (card_no) DO UPDATE
|
||||
SET daily_limit = EXCLUDED.daily_limit, updated_at = now();
|
||||
if (sqlca.sqlcode < 0) {
|
||||
userlog("audb_limit_set FAIL [%d] %s", sqlca.sqlcode, sqlca.sqlerrm.sqlerrmc);
|
||||
return -1;
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
int audb_upsert_auth_summary(const char *merch, const char *bizdate, long cnt,
|
||||
long approved, long declined, long amount)
|
||||
{
|
||||
EXEC SQL BEGIN DECLARE SECTION;
|
||||
long h_cnt = cnt, h_appr = approved, h_dec = declined, h_amt = amount;
|
||||
char h_merch[64], h_bizdate[16];
|
||||
EXEC SQL END DECLARE SECTION;
|
||||
|
||||
strncpy(h_merch, merch, sizeof(h_merch) - 1); h_merch[sizeof(h_merch)-1] = 0;
|
||||
strncpy(h_bizdate, bizdate, sizeof(h_bizdate) - 1); h_bizdate[sizeof(h_bizdate)-1] = 0;
|
||||
|
||||
EXEC SQL INSERT INTO au_summary
|
||||
(merchant_id, biz_date, auth_count, approve_count, decline_count, auth_amount)
|
||||
VALUES (:h_merch, :h_bizdate, :h_cnt, :h_appr, :h_dec, :h_amt)
|
||||
ON CONFLICT (merchant_id, biz_date) DO UPDATE
|
||||
SET auth_count = au_summary.auth_count + EXCLUDED.auth_count,
|
||||
approve_count = au_summary.approve_count + EXCLUDED.approve_count,
|
||||
decline_count = au_summary.decline_count + EXCLUDED.decline_count,
|
||||
auth_amount = au_summary.auth_amount + EXCLUDED.auth_amount,
|
||||
updated_at = now();
|
||||
if (sqlca.sqlcode < 0) {
|
||||
userlog("audb_upsert_auth_summary FAIL [%d] %s", sqlca.sqlcode, sqlca.sqlerrm.sqlerrmc);
|
||||
return -1;
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
71
app/src/cl/batch/cl_daily_batch.pgc
Normal file
71
app/src/cl/batch/cl_daily_batch.pgc
Normal file
|
|
@ -0,0 +1,71 @@
|
|||
/*
|
||||
* cl_daily_batch.pgc - cl 일 마감 배치 (미마감 점검 + 커서 M->S + XA).
|
||||
*
|
||||
* ATMI client: tpinit/tpopen open the ECPG XA RM, tpbegin starts ONE global
|
||||
* transaction. 먼저 당일 대사완료(M) 미마감 매입 건수를 점검하고, 커서로 각 건을
|
||||
* 정산완료(S)로 마감 전환한 뒤, 당일 마감이력(close_log, DAILY)을 집계와 함께
|
||||
* 기록한다. tpcommit 이 XA 2PC 를 구동. Usage: cl_daily_batch [YYYY-MM-DD]
|
||||
*/
|
||||
#include <stdio.h>
|
||||
#include <stdlib.h>
|
||||
#include <string.h>
|
||||
#include <atmi.h>
|
||||
#include "cl_dbio.h"
|
||||
|
||||
int main(int argc, char **argv)
|
||||
{
|
||||
const char *bizdate = (argc > 1) ? argv[1] : "2026-07-19";
|
||||
long closed = 0, cid;
|
||||
|
||||
EXEC SQL BEGIN DECLARE SECTION;
|
||||
char h_bizdate[16];
|
||||
long h_pid, h_unclosed, h_cnt, h_amt;
|
||||
EXEC SQL END DECLARE SECTION;
|
||||
|
||||
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) 미마감(대사완료 M) 점검 */
|
||||
EXEC SQL SELECT count(*) INTO :h_unclosed FROM purchase
|
||||
WHERE biz_date = :h_bizdate AND status = 'M';
|
||||
if (sqlca.sqlcode < 0) { fprintf(stderr, "COUNT FAIL [%d] %s\n", sqlca.sqlcode, sqlca.sqlerrm.sqlerrmc); tpabort(0); return 1; }
|
||||
printf(">>> cl_daily_batch 미마감(M) 점검: bizdate=%s 대상=%ld건\n", bizdate, h_unclosed);
|
||||
|
||||
/* 2) 커서로 M -> S 마감 전환 */
|
||||
EXEC SQL DECLARE cdc CURSOR FOR
|
||||
SELECT purchase_id FROM purchase
|
||||
WHERE biz_date = :h_bizdate AND status = 'M'
|
||||
ORDER BY purchase_id;
|
||||
EXEC SQL OPEN cdc;
|
||||
if (sqlca.sqlcode < 0) { fprintf(stderr, "OPEN FAIL [%d] %s\n", sqlca.sqlcode, sqlca.sqlerrm.sqlerrmc); tpabort(0); return 1; }
|
||||
|
||||
for (;;) {
|
||||
EXEC SQL FETCH cdc INTO :h_pid;
|
||||
if (sqlca.sqlcode == 100) break;
|
||||
if (sqlca.sqlcode < 0) { fprintf(stderr, "FETCH FAIL [%d] %s\n", sqlca.sqlcode, sqlca.sqlerrm.sqlerrmc); EXEC SQL CLOSE cdc; tpabort(0); return 1; }
|
||||
EXEC SQL UPDATE purchase SET status = 'S', updated_at = now() WHERE purchase_id = :h_pid;
|
||||
if (sqlca.sqlcode < 0) { EXEC SQL CLOSE cdc; tpabort(0); return 1; }
|
||||
closed++;
|
||||
}
|
||||
EXEC SQL CLOSE cdc;
|
||||
|
||||
/* 3) 당일 집계 + 마감이력(DAILY) 기록 (via dbio) */
|
||||
EXEC SQL SELECT count(*), coalesce(sum(amount),0) INTO :h_cnt, :h_amt
|
||||
FROM purchase WHERE biz_date = :h_bizdate;
|
||||
if (sqlca.sqlcode < 0) { tpabort(0); return 1; }
|
||||
cid = cldb_next_close_id();
|
||||
if (cid < 0) { tpabort(0); return 1; }
|
||||
if (cldb_insert_close(cid, "DAILY", bizdate, bizdate, "C", h_cnt, h_amt, "일마감(배치)") < 0) {
|
||||
tpabort(0); return 1;
|
||||
}
|
||||
|
||||
if (tpcommit(0) < 0) { fprintf(stderr, "tpcommit FAIL: %s\n", tpstrerror(tperrno)); tpabort(0); return 1; }
|
||||
printf(">>> cl_daily_batch COMMIT: bizdate=%s 마감전환=%ld cid=%ld 총건수=%ld\n",
|
||||
bizdate, closed, cid, h_cnt);
|
||||
|
||||
tpclose(); tpterm();
|
||||
return 0;
|
||||
}
|
||||
66
app/src/cl/batch/cl_snapshot_batch.pgc
Normal file
66
app/src/cl/batch/cl_snapshot_batch.pgc
Normal file
|
|
@ -0,0 +1,66 @@
|
|||
/*
|
||||
* cl_snapshot_batch.pgc - cl 마감 스냅샷 배치 (커서 + XA).
|
||||
*
|
||||
* 당일 마감(close_log, DAILY)에 대해 merchant_summary 를 가맹점별로 커서 순회하여
|
||||
* cl_snapshot 에 고정 복사(스냅샷)한다. 마감 시점의 가맹점별 집계를 불변 기록으로
|
||||
* 남긴다. 모두 ONE global XA transaction 하에서 수행.
|
||||
* Usage: cl_snapshot_batch [YYYY-MM-DD]
|
||||
*/
|
||||
#include <stdio.h>
|
||||
#include <stdlib.h>
|
||||
#include <string.h>
|
||||
#include <atmi.h>
|
||||
#include "cl_dbio.h"
|
||||
|
||||
int main(int argc, char **argv)
|
||||
{
|
||||
const char *bizdate = (argc > 1) ? argv[1] : "2026-07-19";
|
||||
long rows = 0, sid;
|
||||
|
||||
EXEC SQL BEGIN DECLARE SECTION;
|
||||
char h_bizdate[16], h_merch[64];
|
||||
long h_cid, h_gross, h_fee, h_net, h_cnt;
|
||||
EXEC SQL END DECLARE SECTION;
|
||||
|
||||
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; }
|
||||
|
||||
/* 당일 최종 DAILY 마감 id 조회 (없으면 스냅샷 생략) */
|
||||
EXEC SQL SELECT coalesce(max(close_id), 0) INTO :h_cid FROM cl_close_log
|
||||
WHERE close_type = 'DAILY' AND biz_date = :h_bizdate AND status <> 'X';
|
||||
if (sqlca.sqlcode < 0) { fprintf(stderr, "CID FAIL [%d] %s\n", sqlca.sqlcode, sqlca.sqlerrm.sqlerrmc); tpabort(0); return 1; }
|
||||
if (h_cid == 0) {
|
||||
printf(">>> cl_snapshot_batch: bizdate=%s DAILY 마감 없음 -> 스냅샷 생략\n", bizdate);
|
||||
tpabort(0); tpclose(); tpterm(); return 0;
|
||||
}
|
||||
|
||||
EXEC SQL DECLARE csc CURSOR FOR
|
||||
SELECT merchant_id, gross_amount, fee_amount, net_amount, txn_count
|
||||
FROM merchant_summary WHERE biz_date = :h_bizdate
|
||||
ORDER BY merchant_id;
|
||||
EXEC SQL OPEN csc;
|
||||
if (sqlca.sqlcode < 0) { fprintf(stderr, "OPEN FAIL [%d] %s\n", sqlca.sqlcode, sqlca.sqlerrm.sqlerrmc); tpabort(0); return 1; }
|
||||
|
||||
for (;;) {
|
||||
EXEC SQL FETCH csc INTO :h_merch, :h_gross, :h_fee, :h_net, :h_cnt;
|
||||
if (sqlca.sqlcode == 100) break;
|
||||
if (sqlca.sqlcode < 0) { fprintf(stderr, "FETCH FAIL [%d] %s\n", sqlca.sqlcode, sqlca.sqlerrm.sqlerrmc); EXEC SQL CLOSE csc; tpabort(0); return 1; }
|
||||
sid = cldb_next_snapshot_id();
|
||||
if (sid < 0) { EXEC SQL CLOSE csc; tpabort(0); return 1; }
|
||||
if (cldb_insert_snapshot(sid, h_cid, bizdate, bizdate, h_merch,
|
||||
h_gross, h_fee, h_net, h_cnt) < 0) {
|
||||
EXEC SQL CLOSE csc; tpabort(0); return 1;
|
||||
}
|
||||
rows++;
|
||||
}
|
||||
EXEC SQL CLOSE csc;
|
||||
|
||||
if (tpcommit(0) < 0) { fprintf(stderr, "tpcommit FAIL: %s\n", tpstrerror(tperrno)); tpabort(0); return 1; }
|
||||
printf(">>> cl_snapshot_batch COMMIT: bizdate=%s cid=%ld 스냅샷=%ld건\n", bizdate, h_cid, rows);
|
||||
|
||||
tpclose(); tpterm();
|
||||
return 0;
|
||||
}
|
||||
650
app/src/cl/cl_svr.pgc
Normal file
650
app/src/cl/cl_svr.pgc
Normal file
|
|
@ -0,0 +1,650 @@
|
|||
/*
|
||||
* cl_svr.pgc - cl (마감/closing) MODULE SERVER.
|
||||
*
|
||||
* ONE binary that tpadvertise()s ALL ~30 online cl services (the real Tuxedo
|
||||
* "one module server, many services" pattern). Every service is a distinct 마감
|
||||
* operation with genuine EXEC SQL (directly, or via the linked libcldbio.a dbio
|
||||
* layer) and common helpers from libacqcommon.a.
|
||||
*
|
||||
* 마감 도메인: 일/월/분기/연 마감 이력(close_log), 가맹점별 마감 스냅샷(snapshot),
|
||||
* 기간 잠금(period_lock). 마감 상태 O=개시 -> C=마감완료 -> A=승인 -> L=잠금,
|
||||
* 취소 X / 재마감 R.
|
||||
*
|
||||
* XA: the connection is opened once by tpopen() (ECPG XA switch libndrxxaecpg.so);
|
||||
* there is NO EXEC SQL CONNECT. Each tpcall runs on this process's XA branch.
|
||||
*/
|
||||
#include <stdio.h>
|
||||
#include <stdlib.h>
|
||||
#include <string.h>
|
||||
#include <atmi.h>
|
||||
#include <ubf.h>
|
||||
#include <userlog.h>
|
||||
#include <ndebug.h>
|
||||
#include "acq.fd.h"
|
||||
#include "cl_dbio.h"
|
||||
#include "acq_common.h"
|
||||
|
||||
/* ----- small UBF helpers ------------------------------------------------- */
|
||||
static long getl(UBFH *b, BFLDID f) { long v = 0; Bget(b, f, 0, (char *)&v, 0L); return v; }
|
||||
static void gets_(UBFH *b, BFLDID f, char *out, int cap)
|
||||
{ BFLDLEN l = cap; out[0] = 0; Bget(b, f, 0, out, &l); }
|
||||
static void setl(UBFH *b, BFLDID f, long v) { Bchg(b, f, 0, (char *)&v, 0L); }
|
||||
|
||||
#define FAIL(b) do { tpreturn(TPFAIL, 0, (char *)(b), 0L, 0L); return; } while (0)
|
||||
#define OK(b) do { tpreturn(TPSUCCESS, 0, (char *)(b), 0L, 0L); return; } while (0)
|
||||
|
||||
/* ----- service prototypes ------------------------------------------------ */
|
||||
void CL_DAILY(TPSVCINFO *p); void CL_MONTHLY(TPSVCINFO *p);
|
||||
void CL_QUARTER(TPSVCINFO *p); void CL_SNAPSHOT(TPSVCINFO *p);
|
||||
void CL_RECLOSE(TPSVCINFO *p); void CL_VERIFY(TPSVCINFO *p);
|
||||
void CL_CANCEL(TPSVCINFO *p); void CL_STATS(TPSVCINFO *p);
|
||||
void CL_BALVERIFY(TPSVCINFO *p); void CL_SEQ(TPSVCINFO *p);
|
||||
void CL_REPORT(TPSVCINFO *p); void CL_ALERT(TPSVCINFO *p);
|
||||
void CL_UNCLOSED(TPSVCINFO *p); void CL_APPROVE(TPSVCINFO *p);
|
||||
void CL_LOCK(TPSVCINFO *p); void CL_UNLOCK(TPSVCINFO *p);
|
||||
void CL_AGGREGATE(TPSVCINFO *p); void CL_DIFFCHK(TPSVCINFO *p);
|
||||
void CL_SUMMARY(TPSVCINFO *p); void CL_STATUS(TPSVCINFO *p);
|
||||
void CL_ROLLBACK(TPSVCINFO *p); void CL_CONFIRM(TPSVCINFO *p);
|
||||
void CL_PERIOD(TPSVCINFO *p); void CL_YEAREND(TPSVCINFO *p);
|
||||
void CL_MIDCLOSE(TPSVCINFO *p); void CL_RECON(TPSVCINFO *p);
|
||||
void CL_ARCHIVE(TPSVCINFO *p); void CL_PURGE(TPSVCINFO *p);
|
||||
void CL_RESTORE(TPSVCINFO *p); void CL_INQUIRY(TPSVCINFO *p);
|
||||
|
||||
/* ----- advertise table (this is what "scales to 30" cleanly) ------------- */
|
||||
static struct { const char *name; void (*fn)(TPSVCINFO *); } SVCS[] = {
|
||||
{"CL_DAILY", CL_DAILY}, {"CL_MONTHLY", CL_MONTHLY},
|
||||
{"CL_QUARTER", CL_QUARTER}, {"CL_SNAPSHOT", CL_SNAPSHOT},
|
||||
{"CL_RECLOSE", CL_RECLOSE}, {"CL_VERIFY", CL_VERIFY},
|
||||
{"CL_CANCEL", CL_CANCEL}, {"CL_STATS", CL_STATS},
|
||||
{"CL_BALVERIFY", CL_BALVERIFY}, {"CL_SEQ", CL_SEQ},
|
||||
{"CL_REPORT", CL_REPORT}, {"CL_ALERT", CL_ALERT},
|
||||
{"CL_UNCLOSED", CL_UNCLOSED}, {"CL_APPROVE", CL_APPROVE},
|
||||
{"CL_LOCK", CL_LOCK}, {"CL_UNLOCK", CL_UNLOCK},
|
||||
{"CL_AGGREGATE", CL_AGGREGATE}, {"CL_DIFFCHK", CL_DIFFCHK},
|
||||
{"CL_SUMMARY", CL_SUMMARY}, {"CL_STATUS", CL_STATUS},
|
||||
{"CL_ROLLBACK", CL_ROLLBACK}, {"CL_CONFIRM", CL_CONFIRM},
|
||||
{"CL_PERIOD", CL_PERIOD}, {"CL_YEAREND", CL_YEAREND},
|
||||
{"CL_MIDCLOSE", CL_MIDCLOSE}, {"CL_RECON", CL_RECON},
|
||||
{"CL_ARCHIVE", CL_ARCHIVE}, {"CL_PURGE", CL_PURGE},
|
||||
{"CL_RESTORE", CL_RESTORE}, {"CL_INQUIRY", CL_INQUIRY},
|
||||
{NULL, NULL}
|
||||
};
|
||||
|
||||
int tpsvrinit(int argc, char **argv)
|
||||
{
|
||||
int i;
|
||||
if (tpopen() < 0) {
|
||||
userlog("cl_svr: tpopen FAIL: %s", tpstrerror(tperrno));
|
||||
return -1;
|
||||
}
|
||||
for (i = 0; SVCS[i].name != NULL; i++) {
|
||||
if (tpadvertise((char *)SVCS[i].name, SVCS[i].fn) < 0) {
|
||||
userlog("cl_svr: tpadvertise(%s) FAIL: %s", SVCS[i].name, tpstrerror(tperrno));
|
||||
return -1;
|
||||
}
|
||||
}
|
||||
userlog("cl_svr: %d cl 서비스 광고 완료, RM 개방", i);
|
||||
return 0;
|
||||
}
|
||||
|
||||
void tpsvrdone(void)
|
||||
{
|
||||
tpclose();
|
||||
userlog("cl_svr: tpsvrdone");
|
||||
}
|
||||
|
||||
/* 공통: 매입 집계 후 마감 이력 생성 헬퍼 (일/월/분기/연 공용). */
|
||||
static int cl_open_close(UBFH *b, const char *ctype, const char *period,
|
||||
const char *bizdate, long *cid_out)
|
||||
{
|
||||
EXEC SQL BEGIN DECLARE SECTION;
|
||||
long h_cnt, h_amt, h_cid;
|
||||
char h_bizdate[16], h_type[16], h_period[16];
|
||||
EXEC SQL END DECLARE SECTION;
|
||||
|
||||
strncpy(h_bizdate, bizdate, sizeof(h_bizdate)-1); h_bizdate[sizeof(h_bizdate)-1] = 0;
|
||||
strncpy(h_type, ctype, sizeof(h_type)-1); h_type[sizeof(h_type)-1] = 0;
|
||||
|
||||
if (strcmp(ctype, "DAILY") == 0) {
|
||||
EXEC SQL SELECT count(*), coalesce(sum(amount), 0) INTO :h_cnt, :h_amt
|
||||
FROM purchase WHERE biz_date = :h_bizdate;
|
||||
} else {
|
||||
strncpy(h_period, period, sizeof(h_period)-1); h_period[sizeof(h_period)-1] = 0;
|
||||
EXEC SQL SELECT count(*), coalesce(sum(amount), 0) INTO :h_cnt, :h_amt
|
||||
FROM purchase WHERE to_char(biz_date, 'YYYY-MM') LIKE :h_period || '%';
|
||||
}
|
||||
if (sqlca.sqlcode < 0) return -1;
|
||||
|
||||
h_cid = cldb_next_close_id();
|
||||
if (h_cid < 0) return -1;
|
||||
if (cldb_insert_close(h_cid, ctype, period, bizdate, "C", h_cnt, h_amt, "마감") < 0) return -1;
|
||||
setl(b, T_COUNT, h_cnt);
|
||||
setl(b, T_GROSS, h_amt);
|
||||
*cid_out = h_cid;
|
||||
return 0;
|
||||
}
|
||||
|
||||
/* ========================================================================= */
|
||||
/* 1. CL_DAILY - 일 마감: 당일 매입 집계 후 마감이력(DAILY) 생성. */
|
||||
/* ========================================================================= */
|
||||
void CL_DAILY(TPSVCINFO *p)
|
||||
{
|
||||
UBFH *b = (UBFH *)p->data;
|
||||
char bizdate[16];
|
||||
long cid = 0;
|
||||
gets_(b, T_BIZDATE, bizdate, sizeof(bizdate));
|
||||
if (cl_open_close(b, "DAILY", bizdate, bizdate, &cid) < 0) FAIL(b);
|
||||
setl(b, T_ID1, cid);
|
||||
Bchg(b, T_STATUS, 0, "C", 0L);
|
||||
userlog("CL_DAILY cid=%ld bizdate=%s 일마감 완료", cid, bizdate);
|
||||
OK(b);
|
||||
}
|
||||
|
||||
/* 2. CL_MONTHLY - 월 마감: 해당 월(YYYY-MM) 매입 집계 후 마감이력(MONTHLY). */
|
||||
void CL_MONTHLY(TPSVCINFO *p)
|
||||
{
|
||||
UBFH *b = (UBFH *)p->data;
|
||||
char bizdate[16], period[16];
|
||||
long cid = 0;
|
||||
gets_(b, T_BIZDATE, bizdate, sizeof(bizdate));
|
||||
gets_(b, T_STR1, period, sizeof(period));
|
||||
if (period[0] == 0) { strncpy(period, bizdate, 7); period[7] = 0; }
|
||||
if (cl_open_close(b, "MONTHLY", period, bizdate, &cid) < 0) FAIL(b);
|
||||
setl(b, T_ID1, cid);
|
||||
Bchg(b, T_STATUS, 0, "C", 0L);
|
||||
OK(b);
|
||||
}
|
||||
|
||||
/* 3. CL_QUARTER - 분기 마감: 분기 프리픽스(YYYY-MM LIKE)로 집계. */
|
||||
void CL_QUARTER(TPSVCINFO *p)
|
||||
{
|
||||
UBFH *b = (UBFH *)p->data;
|
||||
char bizdate[16], period[16];
|
||||
long cid = 0;
|
||||
gets_(b, T_BIZDATE, bizdate, sizeof(bizdate));
|
||||
gets_(b, T_STR1, period, sizeof(period));
|
||||
if (period[0] == 0) { strncpy(period, bizdate, 4); period[4] = 0; }
|
||||
if (cl_open_close(b, "QUARTER", period, bizdate, &cid) < 0) FAIL(b);
|
||||
setl(b, T_ID1, cid);
|
||||
OK(b);
|
||||
}
|
||||
|
||||
/* 4. CL_SNAPSHOT - 마감 스냅샷: merchant_summary 를 close_id 로 고정 복사. */
|
||||
void CL_SNAPSHOT(TPSVCINFO *p)
|
||||
{
|
||||
UBFH *b = (UBFH *)p->data;
|
||||
char bizdate[16], period[16];
|
||||
EXEC SQL BEGIN DECLARE SECTION;
|
||||
long h_cid, h_cnt;
|
||||
char h_bizdate[16], h_period[16];
|
||||
EXEC SQL END DECLARE SECTION;
|
||||
|
||||
h_cid = getl(b, T_ID1);
|
||||
gets_(b, T_BIZDATE, bizdate, sizeof(bizdate));
|
||||
gets_(b, T_STR1, period, sizeof(period)); if (period[0] == 0) strcpy(period, bizdate);
|
||||
strncpy(h_bizdate, bizdate, sizeof(h_bizdate)-1); h_bizdate[sizeof(h_bizdate)-1] = 0;
|
||||
strncpy(h_period, period, sizeof(h_period)-1); h_period[sizeof(h_period)-1] = 0;
|
||||
|
||||
EXEC SQL INSERT INTO cl_snapshot
|
||||
(snapshot_id, close_id, period_key, biz_date, merchant_id,
|
||||
gross_amount, fee_amount, net_amount, txn_count)
|
||||
SELECT nextval('cl_snapshot_seq'), :h_cid, :h_period, :h_bizdate,
|
||||
ms.merchant_id, ms.gross_amount, ms.fee_amount, ms.net_amount, ms.txn_count
|
||||
FROM merchant_summary ms WHERE ms.biz_date = :h_bizdate;
|
||||
if (sqlca.sqlcode < 0) { userlog("CL_SNAPSHOT FAIL [%d] %s", sqlca.sqlcode, sqlca.sqlerrm.sqlerrmc); FAIL(b); }
|
||||
h_cnt = sqlca.sqlerrd[2];
|
||||
setl(b, T_COUNT, h_cnt);
|
||||
userlog("CL_SNAPSHOT cid=%ld 가맹점 %ld건 스냅샷", h_cid, h_cnt);
|
||||
OK(b);
|
||||
}
|
||||
|
||||
/* 5. CL_RECLOSE - 재마감: 상태 R 로 표시하고 총계 재집계. */
|
||||
void CL_RECLOSE(TPSVCINFO *p)
|
||||
{
|
||||
UBFH *b = (UBFH *)p->data;
|
||||
char bizdate[16];
|
||||
EXEC SQL BEGIN DECLARE SECTION;
|
||||
long h_cid, h_cnt, h_amt;
|
||||
char h_bizdate[16];
|
||||
EXEC SQL END DECLARE SECTION;
|
||||
h_cid = getl(b, T_ID1);
|
||||
gets_(b, T_BIZDATE, bizdate, sizeof(bizdate));
|
||||
strncpy(h_bizdate, bizdate, sizeof(h_bizdate)-1); h_bizdate[sizeof(h_bizdate)-1] = 0;
|
||||
EXEC SQL SELECT count(*), coalesce(sum(amount),0) INTO :h_cnt, :h_amt
|
||||
FROM purchase WHERE biz_date = :h_bizdate;
|
||||
if (sqlca.sqlcode < 0) FAIL(b);
|
||||
if (cldb_update_close_totals(h_cid, h_cnt, h_amt) < 0) FAIL(b);
|
||||
if (cldb_update_close_status(h_cid, "R") < 0) FAIL(b);
|
||||
setl(b, T_COUNT, h_cnt);
|
||||
Bchg(b, T_STATUS, 0, "R", 0L);
|
||||
OK(b);
|
||||
}
|
||||
|
||||
/* 6. CL_VERIFY - 마감 검증: close_log 총건수 vs 실제 매입건수 비교. */
|
||||
void CL_VERIFY(TPSVCINFO *p)
|
||||
{
|
||||
UBFH *b = (UBFH *)p->data;
|
||||
char bizdate[16];
|
||||
EXEC SQL BEGIN DECLARE SECTION;
|
||||
long h_cid, h_stored, h_actual;
|
||||
char h_bizdate[16];
|
||||
EXEC SQL END DECLARE SECTION;
|
||||
h_cid = getl(b, T_ID1);
|
||||
gets_(b, T_BIZDATE, bizdate, sizeof(bizdate));
|
||||
strncpy(h_bizdate, bizdate, sizeof(h_bizdate)-1); h_bizdate[sizeof(h_bizdate)-1] = 0;
|
||||
EXEC SQL SELECT txn_count INTO :h_stored FROM cl_close_log WHERE close_id = :h_cid;
|
||||
if (sqlca.sqlcode < 0 || sqlca.sqlcode == 100) FAIL(b);
|
||||
EXEC SQL SELECT count(*) INTO :h_actual FROM purchase WHERE biz_date = :h_bizdate;
|
||||
if (sqlca.sqlcode < 0) FAIL(b);
|
||||
setl(b, T_COUNT, h_actual);
|
||||
setl(b, T_RC, (h_stored == h_actual) ? 0 : 1);
|
||||
OK(b);
|
||||
}
|
||||
|
||||
/* 7. CL_CANCEL - 마감 취소: 상태 X. */
|
||||
void CL_CANCEL(TPSVCINFO *p)
|
||||
{
|
||||
UBFH *b = (UBFH *)p->data;
|
||||
long cid = getl(b, T_ID1);
|
||||
if (cldb_update_close_status(cid, "X") < 0) FAIL(b);
|
||||
Bchg(b, T_STATUS, 0, "X", 0L);
|
||||
OK(b);
|
||||
}
|
||||
|
||||
/* 8. CL_STATS - 마감 통계: close_type 별 마감 건수/총액. */
|
||||
void CL_STATS(TPSVCINFO *p)
|
||||
{
|
||||
UBFH *b = (UBFH *)p->data;
|
||||
char ctype[16];
|
||||
EXEC SQL BEGIN DECLARE SECTION;
|
||||
long h_cnt, h_amt;
|
||||
char h_type[16];
|
||||
EXEC SQL END DECLARE SECTION;
|
||||
gets_(b, T_STR2, ctype, sizeof(ctype)); if (ctype[0] == 0) strcpy(ctype, "DAILY");
|
||||
strncpy(h_type, ctype, sizeof(h_type)-1); h_type[sizeof(h_type)-1] = 0;
|
||||
EXEC SQL SELECT count(*), coalesce(sum(total_amount),0) INTO :h_cnt, :h_amt
|
||||
FROM cl_close_log WHERE close_type = :h_type AND status <> 'X';
|
||||
if (sqlca.sqlcode < 0) FAIL(b);
|
||||
setl(b, T_COUNT, h_cnt);
|
||||
setl(b, T_GROSS, h_amt);
|
||||
OK(b);
|
||||
}
|
||||
|
||||
/* 9. CL_BALVERIFY - 잔액 검증: 스냅샷 net 합 vs close_log total 비교. */
|
||||
void CL_BALVERIFY(TPSVCINFO *p)
|
||||
{
|
||||
UBFH *b = (UBFH *)p->data;
|
||||
EXEC SQL BEGIN DECLARE SECTION;
|
||||
long h_cid, h_snap_net, h_total;
|
||||
EXEC SQL END DECLARE SECTION;
|
||||
h_cid = getl(b, T_ID1);
|
||||
EXEC SQL SELECT coalesce(sum(net_amount),0) INTO :h_snap_net
|
||||
FROM cl_snapshot WHERE close_id = :h_cid;
|
||||
if (sqlca.sqlcode < 0) FAIL(b);
|
||||
EXEC SQL SELECT total_amount INTO :h_total FROM cl_close_log WHERE close_id = :h_cid;
|
||||
if (sqlca.sqlcode < 0 || sqlca.sqlcode == 100) FAIL(b);
|
||||
setl(b, T_AMT2, h_snap_net);
|
||||
setl(b, T_AMT3, h_total);
|
||||
setl(b, T_DELTA, h_total - h_snap_net);
|
||||
OK(b);
|
||||
}
|
||||
|
||||
/* 10. CL_SEQ - 마감 순번 발번(dbio upsert). */
|
||||
void CL_SEQ(TPSVCINFO *p)
|
||||
{
|
||||
UBFH *b = (UBFH *)p->data;
|
||||
char ctype[16];
|
||||
long seq;
|
||||
gets_(b, T_STR2, ctype, sizeof(ctype)); if (ctype[0] == 0) strcpy(ctype, "DAILY");
|
||||
seq = cldb_bump_seq(ctype);
|
||||
if (seq < 0) FAIL(b);
|
||||
setl(b, T_ID2, seq);
|
||||
OK(b);
|
||||
}
|
||||
|
||||
/* 11. CL_REPORT - 마감 리포트: 특정 마감의 스냅샷 건수/총매출. */
|
||||
void CL_REPORT(TPSVCINFO *p)
|
||||
{
|
||||
UBFH *b = (UBFH *)p->data;
|
||||
EXEC SQL BEGIN DECLARE SECTION;
|
||||
long h_cid, h_cnt, h_gross;
|
||||
EXEC SQL END DECLARE SECTION;
|
||||
h_cid = getl(b, T_ID1);
|
||||
EXEC SQL SELECT count(*), coalesce(sum(gross_amount),0) INTO :h_cnt, :h_gross
|
||||
FROM cl_snapshot WHERE close_id = :h_cid;
|
||||
if (sqlca.sqlcode < 0) FAIL(b);
|
||||
setl(b, T_COUNT, h_cnt);
|
||||
setl(b, T_GROSS, h_gross);
|
||||
OK(b);
|
||||
}
|
||||
|
||||
/* 12. CL_ALERT - 미마감 경보: 당일 대사완료(M) 미전기 매입 건수. */
|
||||
void CL_ALERT(TPSVCINFO *p)
|
||||
{
|
||||
UBFH *b = (UBFH *)p->data;
|
||||
char bizdate[16];
|
||||
EXEC SQL BEGIN DECLARE SECTION;
|
||||
long h_cnt;
|
||||
char h_bizdate[16];
|
||||
EXEC SQL END DECLARE SECTION;
|
||||
gets_(b, T_BIZDATE, bizdate, sizeof(bizdate));
|
||||
strncpy(h_bizdate, bizdate, sizeof(h_bizdate)-1); h_bizdate[sizeof(h_bizdate)-1] = 0;
|
||||
EXEC SQL SELECT count(*) INTO :h_cnt FROM purchase
|
||||
WHERE biz_date = :h_bizdate AND status = 'M';
|
||||
if (sqlca.sqlcode < 0) FAIL(b);
|
||||
setl(b, T_COUNT, h_cnt);
|
||||
setl(b, T_RC, (h_cnt > 0) ? 1 : 0);
|
||||
if (h_cnt > 0) userlog("CL_ALERT %s 미마감 %ld건 감지", bizdate, h_cnt);
|
||||
OK(b);
|
||||
}
|
||||
|
||||
/* 13. CL_UNCLOSED - 미마감일 점검: close_log 없는 매입 영업일 건수. */
|
||||
void CL_UNCLOSED(TPSVCINFO *p)
|
||||
{
|
||||
UBFH *b = (UBFH *)p->data;
|
||||
EXEC SQL BEGIN DECLARE SECTION;
|
||||
long h_cnt;
|
||||
EXEC SQL END DECLARE SECTION;
|
||||
EXEC SQL SELECT count(DISTINCT p.biz_date) INTO :h_cnt
|
||||
FROM purchase p
|
||||
WHERE NOT EXISTS (SELECT 1 FROM cl_close_log c
|
||||
WHERE c.close_type = 'DAILY' AND c.biz_date = p.biz_date
|
||||
AND c.status <> 'X');
|
||||
if (sqlca.sqlcode < 0) FAIL(b);
|
||||
setl(b, T_COUNT, h_cnt);
|
||||
OK(b);
|
||||
}
|
||||
|
||||
/* 14. CL_APPROVE - 마감 승인: 상태 A. */
|
||||
void CL_APPROVE(TPSVCINFO *p)
|
||||
{
|
||||
UBFH *b = (UBFH *)p->data;
|
||||
long cid = getl(b, T_ID1);
|
||||
if (cldb_update_close_status(cid, "A") < 0) FAIL(b);
|
||||
Bchg(b, T_STATUS, 0, "A", 0L);
|
||||
OK(b);
|
||||
}
|
||||
|
||||
/* 15. CL_LOCK - 기간 잠금: cl_period_lock upsert(locked=true) + 마감 L. */
|
||||
void CL_LOCK(TPSVCINFO *p)
|
||||
{
|
||||
UBFH *b = (UBFH *)p->data;
|
||||
char period[16];
|
||||
EXEC SQL BEGIN DECLARE SECTION;
|
||||
long h_cid;
|
||||
char h_period[16];
|
||||
EXEC SQL END DECLARE SECTION;
|
||||
h_cid = getl(b, T_ID1);
|
||||
gets_(b, T_STR1, period, sizeof(period)); if (period[0] == 0) gets_(b, T_BIZDATE, period, sizeof(period));
|
||||
strncpy(h_period, period, sizeof(h_period)-1); h_period[sizeof(h_period)-1] = 0;
|
||||
EXEC SQL INSERT INTO cl_period_lock (period_key, locked) VALUES (:h_period, true)
|
||||
ON CONFLICT (period_key) DO UPDATE SET locked = true, locked_at = now();
|
||||
if (sqlca.sqlcode < 0) FAIL(b);
|
||||
if (h_cid > 0 && cldb_update_close_status(h_cid, "L") < 0) FAIL(b);
|
||||
Bchg(b, T_STATUS, 0, "L", 0L);
|
||||
OK(b);
|
||||
}
|
||||
|
||||
/* 16. CL_UNLOCK - 기간 잠금 해제. */
|
||||
void CL_UNLOCK(TPSVCINFO *p)
|
||||
{
|
||||
UBFH *b = (UBFH *)p->data;
|
||||
char period[16];
|
||||
EXEC SQL BEGIN DECLARE SECTION;
|
||||
char h_period[16];
|
||||
EXEC SQL END DECLARE SECTION;
|
||||
gets_(b, T_STR1, period, sizeof(period)); if (period[0] == 0) gets_(b, T_BIZDATE, period, sizeof(period));
|
||||
strncpy(h_period, period, sizeof(h_period)-1); h_period[sizeof(h_period)-1] = 0;
|
||||
EXEC SQL UPDATE cl_period_lock SET locked = false, locked_at = now()
|
||||
WHERE period_key = :h_period;
|
||||
if (sqlca.sqlcode < 0) FAIL(b);
|
||||
setl(b, T_COUNT, sqlca.sqlerrd[2]);
|
||||
OK(b);
|
||||
}
|
||||
|
||||
/* 17. CL_AGGREGATE - 마감 총계 재집계 후 close_log 반영. */
|
||||
void CL_AGGREGATE(TPSVCINFO *p)
|
||||
{
|
||||
UBFH *b = (UBFH *)p->data;
|
||||
char bizdate[16];
|
||||
EXEC SQL BEGIN DECLARE SECTION;
|
||||
long h_cid, h_cnt, h_amt;
|
||||
char h_bizdate[16];
|
||||
EXEC SQL END DECLARE SECTION;
|
||||
h_cid = getl(b, T_ID1);
|
||||
gets_(b, T_BIZDATE, bizdate, sizeof(bizdate));
|
||||
strncpy(h_bizdate, bizdate, sizeof(h_bizdate)-1); h_bizdate[sizeof(h_bizdate)-1] = 0;
|
||||
EXEC SQL SELECT count(*), coalesce(sum(amount),0) INTO :h_cnt, :h_amt
|
||||
FROM purchase WHERE biz_date = :h_bizdate;
|
||||
if (sqlca.sqlcode < 0) FAIL(b);
|
||||
if (cldb_update_close_totals(h_cid, h_cnt, h_amt) < 0) FAIL(b);
|
||||
setl(b, T_COUNT, h_cnt);
|
||||
setl(b, T_GROSS, h_amt);
|
||||
OK(b);
|
||||
}
|
||||
|
||||
/* 18. CL_DIFFCHK - 차액 점검: close_log total vs 스냅샷 gross 합. */
|
||||
void CL_DIFFCHK(TPSVCINFO *p)
|
||||
{
|
||||
UBFH *b = (UBFH *)p->data;
|
||||
EXEC SQL BEGIN DECLARE SECTION;
|
||||
long h_cid, h_total, h_snap;
|
||||
EXEC SQL END DECLARE SECTION;
|
||||
h_cid = getl(b, T_ID1);
|
||||
EXEC SQL SELECT total_amount INTO :h_total FROM cl_close_log WHERE close_id = :h_cid;
|
||||
if (sqlca.sqlcode < 0 || sqlca.sqlcode == 100) FAIL(b);
|
||||
EXEC SQL SELECT coalesce(sum(gross_amount),0) INTO :h_snap
|
||||
FROM cl_snapshot WHERE close_id = :h_cid;
|
||||
if (sqlca.sqlcode < 0) FAIL(b);
|
||||
setl(b, T_DELTA, h_total - h_snap);
|
||||
setl(b, T_RC, (h_total == h_snap) ? 0 : 1);
|
||||
OK(b);
|
||||
}
|
||||
|
||||
/* 19. CL_SUMMARY - 마감 요약: 스냅샷 gross/fee/net 합계 반환. */
|
||||
void CL_SUMMARY(TPSVCINFO *p)
|
||||
{
|
||||
UBFH *b = (UBFH *)p->data;
|
||||
EXEC SQL BEGIN DECLARE SECTION;
|
||||
long h_cid, h_gross, h_fee, h_net;
|
||||
EXEC SQL END DECLARE SECTION;
|
||||
h_cid = getl(b, T_ID1);
|
||||
EXEC SQL SELECT coalesce(sum(gross_amount),0), coalesce(sum(fee_amount),0),
|
||||
coalesce(sum(net_amount),0)
|
||||
INTO :h_gross, :h_fee, :h_net FROM cl_snapshot WHERE close_id = :h_cid;
|
||||
if (sqlca.sqlcode < 0) FAIL(b);
|
||||
setl(b, T_GROSS, h_gross);
|
||||
setl(b, T_FEE, h_fee);
|
||||
setl(b, T_NET, h_net);
|
||||
OK(b);
|
||||
}
|
||||
|
||||
/* 20. CL_STATUS - 마감 상태 조회(dbio getter). */
|
||||
void CL_STATUS(TPSVCINFO *p)
|
||||
{
|
||||
UBFH *b = (UBFH *)p->data;
|
||||
long cid = getl(b, T_ID1);
|
||||
char st[4];
|
||||
if (cldb_get_close_status(cid, st) < 0) FAIL(b);
|
||||
Bchg(b, T_STATUS, 0, st, 0L);
|
||||
OK(b);
|
||||
}
|
||||
|
||||
/* 21. CL_ROLLBACK - 마감 롤백: 스냅샷 삭제 + 상태 O 로 복귀. */
|
||||
void CL_ROLLBACK(TPSVCINFO *p)
|
||||
{
|
||||
UBFH *b = (UBFH *)p->data;
|
||||
EXEC SQL BEGIN DECLARE SECTION;
|
||||
long h_cid;
|
||||
EXEC SQL END DECLARE SECTION;
|
||||
h_cid = getl(b, T_ID1);
|
||||
EXEC SQL DELETE FROM cl_snapshot WHERE close_id = :h_cid;
|
||||
if (sqlca.sqlcode < 0) FAIL(b);
|
||||
setl(b, T_COUNT, sqlca.sqlerrd[2]);
|
||||
if (cldb_update_close_status(h_cid, "O") < 0) FAIL(b);
|
||||
Bchg(b, T_STATUS, 0, "O", 0L);
|
||||
OK(b);
|
||||
}
|
||||
|
||||
/* 22. CL_CONFIRM - 마감 확정: 상태 C. */
|
||||
void CL_CONFIRM(TPSVCINFO *p)
|
||||
{
|
||||
UBFH *b = (UBFH *)p->data;
|
||||
long cid = getl(b, T_ID1);
|
||||
if (cldb_update_close_status(cid, "C") < 0) FAIL(b);
|
||||
Bchg(b, T_STATUS, 0, "C", 0L);
|
||||
OK(b);
|
||||
}
|
||||
|
||||
/* 23. CL_PERIOD - 기간 마감 개시(open): close_log 를 O 상태로 등록. */
|
||||
void CL_PERIOD(TPSVCINFO *p)
|
||||
{
|
||||
UBFH *b = (UBFH *)p->data;
|
||||
char bizdate[16], period[16], ctype[16];
|
||||
long cid;
|
||||
gets_(b, T_BIZDATE, bizdate, sizeof(bizdate));
|
||||
gets_(b, T_STR1, period, sizeof(period)); if (period[0] == 0) strcpy(period, bizdate);
|
||||
gets_(b, T_STR2, ctype, sizeof(ctype)); if (ctype[0] == 0) strcpy(ctype, "DAILY");
|
||||
cid = cldb_next_close_id();
|
||||
if (cid < 0) FAIL(b);
|
||||
if (cldb_insert_close(cid, ctype, period, bizdate, "O", 0, 0, "기간개시") < 0) FAIL(b);
|
||||
setl(b, T_ID1, cid);
|
||||
Bchg(b, T_STATUS, 0, "O", 0L);
|
||||
OK(b);
|
||||
}
|
||||
|
||||
/* 24. CL_YEAREND - 연말 마감: 연도(YYYY) 프리픽스로 집계. */
|
||||
void CL_YEAREND(TPSVCINFO *p)
|
||||
{
|
||||
UBFH *b = (UBFH *)p->data;
|
||||
char bizdate[16], period[16];
|
||||
long cid = 0;
|
||||
gets_(b, T_BIZDATE, bizdate, sizeof(bizdate));
|
||||
strncpy(period, bizdate, 4); period[4] = 0;
|
||||
if (cl_open_close(b, "YEAREND", period, bizdate, &cid) < 0) FAIL(b);
|
||||
setl(b, T_ID1, cid);
|
||||
Bchg(b, T_STATUS, 0, "C", 0L);
|
||||
userlog("CL_YEAREND cid=%ld 연도=%s 연마감 완료", cid, period);
|
||||
OK(b);
|
||||
}
|
||||
|
||||
/* 25. CL_MIDCLOSE - 중간 마감: 당일 중간 스냅 마감이력(MID) 생성. */
|
||||
void CL_MIDCLOSE(TPSVCINFO *p)
|
||||
{
|
||||
UBFH *b = (UBFH *)p->data;
|
||||
char bizdate[16];
|
||||
EXEC SQL BEGIN DECLARE SECTION;
|
||||
long h_cnt, h_amt, h_cid;
|
||||
char h_bizdate[16];
|
||||
EXEC SQL END DECLARE SECTION;
|
||||
gets_(b, T_BIZDATE, bizdate, sizeof(bizdate));
|
||||
strncpy(h_bizdate, bizdate, sizeof(h_bizdate)-1); h_bizdate[sizeof(h_bizdate)-1] = 0;
|
||||
EXEC SQL SELECT count(*), coalesce(sum(amount),0) INTO :h_cnt, :h_amt
|
||||
FROM purchase WHERE biz_date = :h_bizdate AND status IN ('A', 'M');
|
||||
if (sqlca.sqlcode < 0) FAIL(b);
|
||||
h_cid = cldb_next_close_id();
|
||||
if (h_cid < 0) FAIL(b);
|
||||
if (cldb_insert_close(h_cid, "MID", bizdate, bizdate, "O", h_cnt, h_amt, "중간마감") < 0) FAIL(b);
|
||||
setl(b, T_ID1, h_cid);
|
||||
setl(b, T_COUNT, h_cnt);
|
||||
OK(b);
|
||||
}
|
||||
|
||||
/* 26. CL_RECON - 마감 대사: 실제 매입건수 vs close_log txn_count 대사. */
|
||||
void CL_RECON(TPSVCINFO *p)
|
||||
{
|
||||
UBFH *b = (UBFH *)p->data;
|
||||
char bizdate[16];
|
||||
EXEC SQL BEGIN DECLARE SECTION;
|
||||
long h_cid, h_stored, h_actual, h_asum, h_tsum;
|
||||
char h_bizdate[16];
|
||||
EXEC SQL END DECLARE SECTION;
|
||||
h_cid = getl(b, T_ID1);
|
||||
gets_(b, T_BIZDATE, bizdate, sizeof(bizdate));
|
||||
strncpy(h_bizdate, bizdate, sizeof(h_bizdate)-1); h_bizdate[sizeof(h_bizdate)-1] = 0;
|
||||
EXEC SQL SELECT txn_count, total_amount INTO :h_stored, :h_tsum
|
||||
FROM cl_close_log WHERE close_id = :h_cid;
|
||||
if (sqlca.sqlcode < 0 || sqlca.sqlcode == 100) FAIL(b);
|
||||
EXEC SQL SELECT count(*), coalesce(sum(amount),0) INTO :h_actual, :h_asum
|
||||
FROM purchase WHERE biz_date = :h_bizdate;
|
||||
if (sqlca.sqlcode < 0) FAIL(b);
|
||||
setl(b, T_COUNT, h_actual);
|
||||
setl(b, T_DELTA, h_tsum - h_asum);
|
||||
setl(b, T_RC, (h_stored == h_actual && h_tsum == h_asum) ? 0 : 1);
|
||||
OK(b);
|
||||
}
|
||||
|
||||
/* 27. CL_ARCHIVE - 마감 아카이브: 승인/완료 마감을 보관 상태로(memo 표시). */
|
||||
void CL_ARCHIVE(TPSVCINFO *p)
|
||||
{
|
||||
UBFH *b = (UBFH *)p->data;
|
||||
EXEC SQL BEGIN DECLARE SECTION;
|
||||
long h_cid;
|
||||
EXEC SQL END DECLARE SECTION;
|
||||
h_cid = getl(b, T_ID1);
|
||||
EXEC SQL UPDATE cl_close_log
|
||||
SET memo = 'ARCHIVED', updated_at = now()
|
||||
WHERE close_id = :h_cid AND status IN ('C', 'A', 'L');
|
||||
if (sqlca.sqlcode < 0 || sqlca.sqlcode == 100) FAIL(b);
|
||||
setl(b, T_COUNT, sqlca.sqlerrd[2]);
|
||||
OK(b);
|
||||
}
|
||||
|
||||
/* 28. CL_PURGE - 취소 마감 정리: 오래된 X 상태 마감과 그 스냅샷 삭제. */
|
||||
void CL_PURGE(TPSVCINFO *p)
|
||||
{
|
||||
UBFH *b = (UBFH *)p->data;
|
||||
char bizdate[16];
|
||||
EXEC SQL BEGIN DECLARE SECTION;
|
||||
char h_bizdate[16];
|
||||
long h_cnt;
|
||||
EXEC SQL END DECLARE SECTION;
|
||||
gets_(b, T_BIZDATE, bizdate, sizeof(bizdate));
|
||||
strncpy(h_bizdate, bizdate, sizeof(h_bizdate)-1); h_bizdate[sizeof(h_bizdate)-1] = 0;
|
||||
EXEC SQL DELETE FROM cl_snapshot WHERE close_id IN
|
||||
(SELECT close_id FROM cl_close_log WHERE status = 'X' AND biz_date < :h_bizdate);
|
||||
if (sqlca.sqlcode < 0) FAIL(b);
|
||||
EXEC SQL DELETE FROM cl_close_log WHERE status = 'X' AND biz_date < :h_bizdate;
|
||||
if (sqlca.sqlcode < 0) FAIL(b);
|
||||
h_cnt = sqlca.sqlerrd[2];
|
||||
setl(b, T_COUNT, h_cnt);
|
||||
userlog("CL_PURGE %s 이전 취소마감 %ld건 삭제", bizdate, h_cnt);
|
||||
OK(b);
|
||||
}
|
||||
|
||||
/* 29. CL_RESTORE - 마감 복원: 취소(X) 마감을 개시(O) 상태로 되돌림. */
|
||||
void CL_RESTORE(TPSVCINFO *p)
|
||||
{
|
||||
UBFH *b = (UBFH *)p->data;
|
||||
EXEC SQL BEGIN DECLARE SECTION;
|
||||
long h_cid;
|
||||
EXEC SQL END DECLARE SECTION;
|
||||
h_cid = getl(b, T_ID1);
|
||||
EXEC SQL UPDATE cl_close_log SET status = 'O', memo = 'RESTORED', updated_at = now()
|
||||
WHERE close_id = :h_cid AND status = 'X';
|
||||
if (sqlca.sqlcode < 0 || sqlca.sqlcode == 100) FAIL(b);
|
||||
Bchg(b, T_STATUS, 0, "O", 0L);
|
||||
OK(b);
|
||||
}
|
||||
|
||||
/* 30. CL_INQUIRY - 마감 조회: close_type/기간/상태/총건수 반환. */
|
||||
void CL_INQUIRY(TPSVCINFO *p)
|
||||
{
|
||||
UBFH *b = (UBFH *)p->data;
|
||||
EXEC SQL BEGIN DECLARE SECTION;
|
||||
long h_cid, h_cnt, h_amt;
|
||||
char h_type[16], h_status[8];
|
||||
EXEC SQL END DECLARE SECTION;
|
||||
h_cid = getl(b, T_ID1);
|
||||
EXEC SQL SELECT close_type, status, txn_count, total_amount
|
||||
INTO :h_type, :h_status, :h_cnt, :h_amt
|
||||
FROM cl_close_log WHERE close_id = :h_cid;
|
||||
if (sqlca.sqlcode < 0 || sqlca.sqlcode == 100) FAIL(b);
|
||||
Bchg(b, T_STR2, 0, h_type, 0L);
|
||||
Bchg(b, T_STATUS, 0, h_status, 0L);
|
||||
setl(b, T_COUNT, h_cnt);
|
||||
setl(b, T_GROSS, h_amt);
|
||||
OK(b);
|
||||
}
|
||||
|
||||
/* vim: set ts=4 sw=4 et smartindent: */
|
||||
120
app/src/cl/dbio/cl_close_dbio.pgc
Normal file
120
app/src/cl/dbio/cl_close_dbio.pgc
Normal file
|
|
@ -0,0 +1,120 @@
|
|||
/*
|
||||
* cl_close_dbio.pgc - 마감이력(close_log) + 순번관리 DB 접근 (part of libcldbio.a).
|
||||
* ECPG functions; run on the caller's XA branch. No EXEC SQL CONNECT.
|
||||
*/
|
||||
#include <string.h>
|
||||
#include <userlog.h>
|
||||
#include "cl_dbio.h"
|
||||
|
||||
long cldb_next_close_id(void)
|
||||
{
|
||||
EXEC SQL BEGIN DECLARE SECTION;
|
||||
long h_id;
|
||||
EXEC SQL END DECLARE SECTION;
|
||||
|
||||
EXEC SQL SELECT nextval('cl_close_seq') INTO :h_id;
|
||||
if (sqlca.sqlcode < 0) {
|
||||
userlog("cldb_next_close_id FAIL [%d] %s", sqlca.sqlcode, sqlca.sqlerrm.sqlerrmc);
|
||||
return -1;
|
||||
}
|
||||
return h_id;
|
||||
}
|
||||
|
||||
int cldb_insert_close(long cid, const char *ctype, const char *period,
|
||||
const char *bizdate, const char *status,
|
||||
long txn_count, long total_amount, const char *memo)
|
||||
{
|
||||
EXEC SQL BEGIN DECLARE SECTION;
|
||||
long h_cid = cid, h_cnt = txn_count, h_amt = total_amount;
|
||||
char h_type[16], h_period[16], h_bizdate[16], h_status[8], h_memo[128];
|
||||
EXEC SQL END DECLARE SECTION;
|
||||
|
||||
strncpy(h_type, ctype, sizeof(h_type) - 1); h_type[sizeof(h_type)-1] = 0;
|
||||
strncpy(h_period, period, sizeof(h_period) - 1); h_period[sizeof(h_period)-1] = 0;
|
||||
strncpy(h_bizdate, bizdate, sizeof(h_bizdate)-1); h_bizdate[sizeof(h_bizdate)-1] = 0;
|
||||
strncpy(h_status, status, sizeof(h_status) - 1); h_status[sizeof(h_status)-1] = 0;
|
||||
strncpy(h_memo, memo, sizeof(h_memo) - 1); h_memo[sizeof(h_memo)-1] = 0;
|
||||
|
||||
EXEC SQL INSERT INTO cl_close_log
|
||||
(close_id, close_type, period_key, biz_date, status, txn_count, total_amount, memo)
|
||||
VALUES (:h_cid, :h_type, :h_period, :h_bizdate, :h_status, :h_cnt, :h_amt, :h_memo);
|
||||
if (sqlca.sqlcode < 0) {
|
||||
userlog("cldb_insert_close FAIL [%d] %s", sqlca.sqlcode, sqlca.sqlerrm.sqlerrmc);
|
||||
return -1;
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
int cldb_update_close_status(long cid, const char *status)
|
||||
{
|
||||
EXEC SQL BEGIN DECLARE SECTION;
|
||||
long h_cid = cid;
|
||||
char h_status[8];
|
||||
EXEC SQL END DECLARE SECTION;
|
||||
|
||||
strncpy(h_status, status, sizeof(h_status) - 1); h_status[sizeof(h_status)-1] = 0;
|
||||
|
||||
EXEC SQL UPDATE cl_close_log SET status = :h_status, updated_at = now()
|
||||
WHERE close_id = :h_cid;
|
||||
if (sqlca.sqlcode < 0 || sqlca.sqlcode == 100) {
|
||||
userlog("cldb_update_close_status FAIL [%d] %s", sqlca.sqlcode, sqlca.sqlerrm.sqlerrmc);
|
||||
return -1;
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
int cldb_get_close_status(long cid, char *out)
|
||||
{
|
||||
EXEC SQL BEGIN DECLARE SECTION;
|
||||
long h_cid = cid;
|
||||
char h_status[8];
|
||||
EXEC SQL END DECLARE SECTION;
|
||||
|
||||
EXEC SQL SELECT status INTO :h_status FROM cl_close_log WHERE close_id = :h_cid;
|
||||
if (sqlca.sqlcode < 0 || sqlca.sqlcode == 100) {
|
||||
userlog("cldb_get_close_status FAIL [%d] %s", sqlca.sqlcode, sqlca.sqlerrm.sqlerrmc);
|
||||
return -1;
|
||||
}
|
||||
out[0] = h_status[0];
|
||||
out[1] = 0;
|
||||
return 0;
|
||||
}
|
||||
|
||||
int cldb_update_close_totals(long cid, long txn_count, long total_amount)
|
||||
{
|
||||
EXEC SQL BEGIN DECLARE SECTION;
|
||||
long h_cid = cid, h_cnt = txn_count, h_amt = total_amount;
|
||||
EXEC SQL END DECLARE SECTION;
|
||||
|
||||
EXEC SQL UPDATE cl_close_log
|
||||
SET txn_count = :h_cnt, total_amount = :h_amt, updated_at = now()
|
||||
WHERE close_id = :h_cid;
|
||||
if (sqlca.sqlcode < 0 || sqlca.sqlcode == 100) {
|
||||
userlog("cldb_update_close_totals FAIL [%d] %s", sqlca.sqlcode, sqlca.sqlerrm.sqlerrmc);
|
||||
return -1;
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
/* close_type 별 마지막 순번을 1 증가시켜 반환 (upsert). */
|
||||
long cldb_bump_seq(const char *ctype)
|
||||
{
|
||||
EXEC SQL BEGIN DECLARE SECTION;
|
||||
long h_seq;
|
||||
char h_type[16];
|
||||
EXEC SQL END DECLARE SECTION;
|
||||
|
||||
strncpy(h_type, ctype, sizeof(h_type) - 1); h_type[sizeof(h_type)-1] = 0;
|
||||
|
||||
EXEC SQL INSERT INTO cl_seq_reg (close_type, last_seq)
|
||||
VALUES (:h_type, 1)
|
||||
ON CONFLICT (close_type) DO UPDATE
|
||||
SET last_seq = cl_seq_reg.last_seq + 1, updated_at = now();
|
||||
if (sqlca.sqlcode < 0) {
|
||||
userlog("cldb_bump_seq FAIL [%d] %s", sqlca.sqlcode, sqlca.sqlerrm.sqlerrmc);
|
||||
return -1;
|
||||
}
|
||||
EXEC SQL SELECT last_seq INTO :h_seq FROM cl_seq_reg WHERE close_type = :h_type;
|
||||
if (sqlca.sqlcode < 0 || sqlca.sqlcode == 100) return -1;
|
||||
return h_seq;
|
||||
}
|
||||
28
app/src/cl/dbio/cl_dbio.h
Normal file
28
app/src/cl/dbio/cl_dbio.h
Normal file
|
|
@ -0,0 +1,28 @@
|
|||
/*
|
||||
* cl_dbio.h - cl 모듈 (마감/closing) DB 접근 계층 (libcldbio.a).
|
||||
* ECPG (EXEC SQL) functions compiled into a static library and linked into
|
||||
* cl_svr and the cl batches. They run on the caller's XA branch/connection
|
||||
* (no EXEC SQL CONNECT); sqlca is the shared per-thread ECPG SQLCA.
|
||||
* Return convention: 0 = ok, -1 = SQL error (caller may also inspect sqlca).
|
||||
*/
|
||||
#ifndef CL_DBIO_H
|
||||
#define CL_DBIO_H
|
||||
|
||||
/* 마감 이력 (close_log) */
|
||||
long cldb_next_close_id(void);
|
||||
int cldb_insert_close(long cid, const char *ctype, const char *period,
|
||||
const char *bizdate, const char *status,
|
||||
long txn_count, long total_amount, const char *memo);
|
||||
int cldb_update_close_status(long cid, const char *status);
|
||||
int cldb_get_close_status(long cid, char *out /* >= 2 bytes */);
|
||||
int cldb_update_close_totals(long cid, long txn_count, long total_amount);
|
||||
|
||||
/* 마감 스냅샷 (snapshot) */
|
||||
long cldb_next_snapshot_id(void);
|
||||
int cldb_insert_snapshot(long sid, long cid, const char *period, const char *bizdate,
|
||||
const char *merch, long gross, long fee, long net, long cnt);
|
||||
|
||||
/* 마감 순번 관리 (seq registry) */
|
||||
long cldb_bump_seq(const char *ctype);
|
||||
|
||||
#endif /* CL_DBIO_H */
|
||||
45
app/src/cl/dbio/cl_snapshot_dbio.pgc
Normal file
45
app/src/cl/dbio/cl_snapshot_dbio.pgc
Normal file
|
|
@ -0,0 +1,45 @@
|
|||
/*
|
||||
* cl_snapshot_dbio.pgc - 마감 스냅샷(snapshot) DB 접근 (part of libcldbio.a).
|
||||
* ECPG functions; run on the caller's XA branch. No EXEC SQL CONNECT.
|
||||
*/
|
||||
#include <string.h>
|
||||
#include <userlog.h>
|
||||
#include "cl_dbio.h"
|
||||
|
||||
long cldb_next_snapshot_id(void)
|
||||
{
|
||||
EXEC SQL BEGIN DECLARE SECTION;
|
||||
long h_id;
|
||||
EXEC SQL END DECLARE SECTION;
|
||||
|
||||
EXEC SQL SELECT nextval('cl_snapshot_seq') INTO :h_id;
|
||||
if (sqlca.sqlcode < 0) {
|
||||
userlog("cldb_next_snapshot_id FAIL [%d] %s", sqlca.sqlcode, sqlca.sqlerrm.sqlerrmc);
|
||||
return -1;
|
||||
}
|
||||
return h_id;
|
||||
}
|
||||
|
||||
int cldb_insert_snapshot(long sid, long cid, const char *period, const char *bizdate,
|
||||
const char *merch, long gross, long fee, long net, long cnt)
|
||||
{
|
||||
EXEC SQL BEGIN DECLARE SECTION;
|
||||
long h_sid = sid, h_cid = cid, h_gross = gross, h_fee = fee, h_net = net, h_cnt = cnt;
|
||||
char h_period[16], h_bizdate[16], h_merch[64];
|
||||
EXEC SQL END DECLARE SECTION;
|
||||
|
||||
strncpy(h_period, period, sizeof(h_period) - 1); h_period[sizeof(h_period)-1] = 0;
|
||||
strncpy(h_bizdate, bizdate, sizeof(h_bizdate)-1); h_bizdate[sizeof(h_bizdate)-1] = 0;
|
||||
strncpy(h_merch, merch, sizeof(h_merch) - 1); h_merch[sizeof(h_merch)-1] = 0;
|
||||
|
||||
EXEC SQL INSERT INTO cl_snapshot
|
||||
(snapshot_id, close_id, period_key, biz_date, merchant_id,
|
||||
gross_amount, fee_amount, net_amount, txn_count)
|
||||
VALUES (:h_sid, :h_cid, :h_period, :h_bizdate, :h_merch,
|
||||
:h_gross, :h_fee, :h_net, :h_cnt);
|
||||
if (sqlca.sqlcode < 0) {
|
||||
userlog("cldb_insert_snapshot FAIL [%d] %s", sqlca.sqlcode, sqlca.sqlerrm.sqlerrmc);
|
||||
return -1;
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
45
app/src/cm/batch/cm_bizday_roll_batch.pgc
Normal file
45
app/src/cm/batch/cm_bizday_roll_batch.pgc
Normal file
|
|
@ -0,0 +1,45 @@
|
|||
/*
|
||||
* cm_bizday_roll_batch.pgc - cm 영업일 롤 배치 (XA).
|
||||
*
|
||||
* ATMI client: tpinit/tpopen open the ECPG XA RM, tpbegin starts ONE global
|
||||
* transaction. Reads cm_param BIZ_DATE, advances it to the next 영업일 (주말=common
|
||||
* util로 skip, 휴일=cm_holiday로 skip), writes it back via the dbio layer, then
|
||||
* tpcommit drives XA 2PC. Usage: cm_bizday_roll_batch
|
||||
*/
|
||||
#include <stdio.h>
|
||||
#include <stdlib.h>
|
||||
#include <string.h>
|
||||
#include <atmi.h>
|
||||
#include "cm_dbio.h"
|
||||
#include "acq_common.h"
|
||||
|
||||
int main(int argc, char **argv)
|
||||
{
|
||||
char cur[16], nxt[16];
|
||||
int hol, guard = 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; }
|
||||
|
||||
if (cmdb_get_param("BIZ_DATE", cur) < 0) {
|
||||
fprintf(stderr, "BIZ_DATE 조회 실패\n"); tpabort(0); return 1;
|
||||
}
|
||||
|
||||
/* 다음 영업일: 주말 + 휴일 모두 건너뜀 */
|
||||
do {
|
||||
if (acq_next_bizday(cur, nxt) < 0) { fprintf(stderr, "next_bizday 파싱 실패: %s\n", cur); tpabort(0); return 1; }
|
||||
strncpy(cur, nxt, sizeof(cur)-1); cur[sizeof(cur)-1] = 0;
|
||||
hol = cmdb_is_holiday(nxt);
|
||||
if (hol < 0) { tpabort(0); return 1; }
|
||||
if (++guard > 30) break;
|
||||
} while (hol == 1);
|
||||
|
||||
if (cmdb_set_param("BIZ_DATE", nxt) < 0) { tpabort(0); return 1; }
|
||||
|
||||
if (tpcommit(0) < 0) { fprintf(stderr, "tpcommit FAIL: %s\n", tpstrerror(tperrno)); tpabort(0); return 1; }
|
||||
printf(">>> cm_bizday_roll_batch COMMIT: BIZ_DATE -> %s\n", nxt);
|
||||
|
||||
tpclose(); tpterm();
|
||||
return 0;
|
||||
}
|
||||
58
app/src/cm/batch/cm_purge_batch.pgc
Normal file
58
app/src/cm/batch/cm_purge_batch.pgc
Normal file
|
|
@ -0,0 +1,58 @@
|
|||
/*
|
||||
* cm_purge_batch.pgc - cm 공통로그 보존기간 경과분 삭제 배치 (XA).
|
||||
*
|
||||
* ATMI client: opens the ECPG XA RM, begins ONE global transaction, deletes
|
||||
* cm_log rows older than cm_param LOG_KEEP_DAYS (기본 90일) from a cutoff biz_date,
|
||||
* then tpcommit drives XA 2PC. Usage: cm_purge_batch [YYYY-MM-DD(기준일)]
|
||||
*/
|
||||
#include <stdio.h>
|
||||
#include <stdlib.h>
|
||||
#include <string.h>
|
||||
#include <atmi.h>
|
||||
#include "cm_dbio.h"
|
||||
|
||||
int main(int argc, char **argv)
|
||||
{
|
||||
char keep_s[128];
|
||||
long keep_days = 90;
|
||||
|
||||
EXEC SQL BEGIN DECLARE SECTION;
|
||||
char h_asof[16];
|
||||
long h_keep, h_deleted;
|
||||
EXEC SQL END DECLARE SECTION;
|
||||
|
||||
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; }
|
||||
|
||||
/* 보존일수는 파라미터에서 (없으면 90일 기본) */
|
||||
if (cmdb_get_param("LOG_KEEP_DAYS", keep_s) == 0) {
|
||||
long v = atol(keep_s);
|
||||
if (v > 0) keep_days = v;
|
||||
}
|
||||
h_keep = keep_days;
|
||||
|
||||
/* 기준일: 인자 우선, 없으면 cm_param BIZ_DATE */
|
||||
if (argc > 1) {
|
||||
strncpy(h_asof, argv[1], sizeof(h_asof)-1); h_asof[sizeof(h_asof)-1] = 0;
|
||||
} else if (cmdb_get_param("BIZ_DATE", keep_s) == 0) {
|
||||
strncpy(h_asof, keep_s, sizeof(h_asof)-1); h_asof[sizeof(h_asof)-1] = 0;
|
||||
} else {
|
||||
strcpy(h_asof, "2026-07-19");
|
||||
}
|
||||
|
||||
EXEC SQL DELETE FROM cm_log
|
||||
WHERE biz_date < (CAST(:h_asof AS date) - (:h_keep || ' days')::interval);
|
||||
if (sqlca.sqlcode < 0) {
|
||||
fprintf(stderr, "DELETE FAIL [%d] %s\n", sqlca.sqlcode, sqlca.sqlerrm.sqlerrmc);
|
||||
tpabort(0); return 1;
|
||||
}
|
||||
h_deleted = sqlca.sqlerrd[2];
|
||||
|
||||
if (tpcommit(0) < 0) { fprintf(stderr, "tpcommit FAIL: %s\n", tpstrerror(tperrno)); tpabort(0); return 1; }
|
||||
printf(">>> cm_purge_batch COMMIT: asof=%s keep_days=%ld deleted=%ld\n",
|
||||
h_asof, keep_days, h_deleted);
|
||||
|
||||
tpclose(); tpterm();
|
||||
return 0;
|
||||
}
|
||||
554
app/src/cm/cm_svr.pgc
Normal file
554
app/src/cm/cm_svr.pgc
Normal file
|
|
@ -0,0 +1,554 @@
|
|||
/*
|
||||
* cm_svr.pgc - cm (공통 / common) MODULE SERVER.
|
||||
*
|
||||
* ONE binary that tpadvertise()s ALL ~30 online cm services (the real Tuxedo
|
||||
* "one module server, many services" pattern). Services are the shared utilities
|
||||
* every other module leans on: 공통코드/파라미터 조회 (EXEC SQL against cm_code /
|
||||
* cm_param), 영업일/휴일 계산, 금액 반올림·분할·포맷, 환율 환산, Luhn/CRC/체크디지트,
|
||||
* 이자·일할·네팅 계산, 채번, 공통 로그. Compute-only services just Bget->compute->
|
||||
* Bchg with no SQL; DB-backed ones use the linked libcmdbio.a layer or直접 EXEC SQL.
|
||||
*
|
||||
* XA: the connection is opened once by tpopen() (ECPG XA switch libndrxxaecpg.so);
|
||||
* there is NO EXEC SQL CONNECT. Each tpcall runs on this process's XA branch.
|
||||
*/
|
||||
#include <stdio.h>
|
||||
#include <stdlib.h>
|
||||
#include <string.h>
|
||||
#include <atmi.h>
|
||||
#include <ubf.h>
|
||||
#include <userlog.h>
|
||||
#include <ndebug.h>
|
||||
#include "acq.fd.h"
|
||||
#include "cm_dbio.h"
|
||||
#include "acq_common.h"
|
||||
|
||||
/* ----- small UBF helpers ------------------------------------------------- */
|
||||
static long getl(UBFH *b, BFLDID f) { long v = 0; Bget(b, f, 0, (char *)&v, 0L); return v; }
|
||||
static void gets_(UBFH *b, BFLDID f, char *out, int cap)
|
||||
{ BFLDLEN l = cap; out[0] = 0; Bget(b, f, 0, out, &l); }
|
||||
static void setl(UBFH *b, BFLDID f, long v) { Bchg(b, f, 0, (char *)&v, 0L); }
|
||||
static void sets_(UBFH *b, BFLDID f, const char *s) { Bchg(b, f, 0, (char *)s, 0L); }
|
||||
|
||||
#define FAIL(b) do { tpreturn(TPFAIL, 0, (char *)(b), 0L, 0L); return; } while (0)
|
||||
#define OK(b) do { tpreturn(TPSUCCESS, 0, (char *)(b), 0L, 0L); return; } while (0)
|
||||
|
||||
/* ----- module-local pure-compute date helpers ---------------------------- */
|
||||
static int cm_is_leap(int y) { return (y % 4 == 0 && y % 100 != 0) || (y % 400 == 0); }
|
||||
static int cm_mdays(int y, int m)
|
||||
{
|
||||
static const int d[] = {31,28,31,30,31,30,31,31,30,31,30,31};
|
||||
if (m < 1 || m > 12) return 30;
|
||||
if (m == 2 && cm_is_leap(y)) return 29;
|
||||
return d[m - 1];
|
||||
}
|
||||
/* Sakamoto: 0=Sun..6=Sat */
|
||||
static int cm_dow(int y, int m, int d)
|
||||
{
|
||||
static int t[] = {0,3,2,5,0,3,5,1,4,6,2,4};
|
||||
if (m < 3) y -= 1;
|
||||
return (y + y/4 - y/100 + y/400 + t[m-1] + d) % 7;
|
||||
}
|
||||
/* CRC-16/CCITT-FALSE over a string. */
|
||||
static unsigned int cm_crc16(const char *s)
|
||||
{
|
||||
unsigned int crc = 0xFFFF; int i, j;
|
||||
for (i = 0; s[i]; i++) {
|
||||
crc ^= (unsigned int)(unsigned char)s[i] << 8;
|
||||
for (j = 0; j < 8; j++)
|
||||
crc = (crc & 0x8000) ? ((crc << 1) ^ 0x1021) & 0xFFFF : (crc << 1) & 0xFFFF;
|
||||
}
|
||||
return crc & 0xFFFF;
|
||||
}
|
||||
|
||||
/* ----- service prototypes ------------------------------------------------ */
|
||||
void CM_CODE(TPSVCINFO *p); void CM_CODENAME(TPSVCINFO *p);
|
||||
void CM_BIZDAY(TPSVCINFO *p); void CM_NEXTBIZ(TPSVCINFO *p);
|
||||
void CM_ADDDAYS(TPSVCINFO *p); void CM_WEEKDAY(TPSVCINFO *p);
|
||||
void CM_MONTHEND(TPSVCINFO *p); void CM_AMT_ROUND(TPSVCINFO *p);
|
||||
void CM_AMT_SPLIT(TPSVCINFO *p); void CM_SEQ(TPSVCINFO *p);
|
||||
void CM_FX(TPSVCINFO *p); void CM_FX_CONV(TPSVCINFO *p);
|
||||
void CM_LUHN(TPSVCINFO *p); void CM_CRC(TPSVCINFO *p);
|
||||
void CM_MASK(TPSVCINFO *p); void CM_FMT_DATE(TPSVCINFO *p);
|
||||
void CM_FMT_AMT(TPSVCINFO *p); void CM_PARAM(TPSVCINFO *p);
|
||||
void CM_PARAM_SET(TPSVCINFO *p); void CM_CACHE_LOAD(TPSVCINFO *p);
|
||||
void CM_CACHE_INV(TPSVCINFO *p); void CM_ERRMSG(TPSVCINFO *p);
|
||||
void CM_LOG(TPSVCINFO *p); void CM_HOLIDAY_CHK(TPSVCINFO *p);
|
||||
void CM_CHECKDIGIT(TPSVCINFO *p); void CM_INTEREST(TPSVCINFO *p);
|
||||
void CM_PRORATE(TPSVCINFO *p); void CM_NETTING_CALC(TPSVCINFO *p);
|
||||
void CM_STATUS(TPSVCINFO *p); void CM_PING(TPSVCINFO *p);
|
||||
|
||||
/* ----- advertise table --------------------------------------------------- */
|
||||
static struct { const char *name; void (*fn)(TPSVCINFO *); } SVCS[] = {
|
||||
{"CM_CODE", CM_CODE}, {"CM_CODENAME", CM_CODENAME},
|
||||
{"CM_BIZDAY", CM_BIZDAY}, {"CM_NEXTBIZ", CM_NEXTBIZ},
|
||||
{"CM_ADDDAYS", CM_ADDDAYS}, {"CM_WEEKDAY", CM_WEEKDAY},
|
||||
{"CM_MONTHEND", CM_MONTHEND}, {"CM_AMT_ROUND", CM_AMT_ROUND},
|
||||
{"CM_AMT_SPLIT", CM_AMT_SPLIT}, {"CM_SEQ", CM_SEQ},
|
||||
{"CM_FX", CM_FX}, {"CM_FX_CONV", CM_FX_CONV},
|
||||
{"CM_LUHN", CM_LUHN}, {"CM_CRC", CM_CRC},
|
||||
{"CM_MASK", CM_MASK}, {"CM_FMT_DATE", CM_FMT_DATE},
|
||||
{"CM_FMT_AMT", CM_FMT_AMT}, {"CM_PARAM", CM_PARAM},
|
||||
{"CM_PARAM_SET", CM_PARAM_SET}, {"CM_CACHE_LOAD", CM_CACHE_LOAD},
|
||||
{"CM_CACHE_INV", CM_CACHE_INV}, {"CM_ERRMSG", CM_ERRMSG},
|
||||
{"CM_LOG", CM_LOG}, {"CM_HOLIDAY_CHK", CM_HOLIDAY_CHK},
|
||||
{"CM_CHECKDIGIT", CM_CHECKDIGIT},{"CM_INTEREST", CM_INTEREST},
|
||||
{"CM_PRORATE", CM_PRORATE}, {"CM_NETTING_CALC", CM_NETTING_CALC},
|
||||
{"CM_STATUS", CM_STATUS}, {"CM_PING", CM_PING},
|
||||
{NULL, NULL}
|
||||
};
|
||||
|
||||
int tpsvrinit(int argc, char **argv)
|
||||
{
|
||||
int i;
|
||||
if (tpopen() < 0) {
|
||||
userlog("cm_svr: tpopen FAIL: %s", tpstrerror(tperrno));
|
||||
return -1;
|
||||
}
|
||||
for (i = 0; SVCS[i].name != NULL; i++) {
|
||||
if (tpadvertise((char *)SVCS[i].name, SVCS[i].fn) < 0) {
|
||||
userlog("cm_svr: tpadvertise(%s) FAIL: %s", SVCS[i].name, tpstrerror(tperrno));
|
||||
return -1;
|
||||
}
|
||||
}
|
||||
userlog("cm_svr: %d cm 공통 서비스 광고 완료, RM 오픈", i);
|
||||
return 0;
|
||||
}
|
||||
|
||||
void tpsvrdone(void)
|
||||
{
|
||||
tpclose();
|
||||
userlog("cm_svr: tpsvrdone");
|
||||
}
|
||||
|
||||
/* ========================================================================= */
|
||||
/* 1. CM_CODE - 공통코드 존재 확인 (EXEC SQL via dbio). */
|
||||
/* ========================================================================= */
|
||||
void CM_CODE(TPSVCINFO *p)
|
||||
{
|
||||
UBFH *b = (UBFH *)p->data;
|
||||
char grp[32], code[32];
|
||||
int ex;
|
||||
gets_(b, T_STR1, grp, sizeof(grp)); if (grp[0] == 0) strcpy(grp, "CCY");
|
||||
gets_(b, T_STR2, code, sizeof(code));
|
||||
ex = cmdb_code_exists(grp, code);
|
||||
if (ex < 0) FAIL(b);
|
||||
setl(b, T_RC, ex);
|
||||
OK(b);
|
||||
}
|
||||
|
||||
/* 2. CM_CODENAME - 공통코드 명 조회 (EXEC SQL). */
|
||||
void CM_CODENAME(TPSVCINFO *p)
|
||||
{
|
||||
UBFH *b = (UBFH *)p->data;
|
||||
char grp[32], code[32], name[128];
|
||||
gets_(b, T_STR1, grp, sizeof(grp)); if (grp[0] == 0) strcpy(grp, "CCY");
|
||||
gets_(b, T_STR2, code, sizeof(code));
|
||||
if (cmdb_get_code_name(grp, code, name) < 0) {
|
||||
userlog("CM_CODENAME 미존재 grp=%s code=%s", grp, code);
|
||||
FAIL(b);
|
||||
}
|
||||
sets_(b, T_STR3, name);
|
||||
OK(b);
|
||||
}
|
||||
|
||||
/* 3. CM_BIZDAY - 현재 영업일 조회 (cm_param BIZ_DATE). */
|
||||
void CM_BIZDAY(TPSVCINFO *p)
|
||||
{
|
||||
UBFH *b = (UBFH *)p->data;
|
||||
char val[128];
|
||||
if (cmdb_get_param("BIZ_DATE", val) < 0) FAIL(b);
|
||||
sets_(b, T_BIZDATE, val);
|
||||
OK(b);
|
||||
}
|
||||
|
||||
/* 4. CM_NEXTBIZ - 다음 영업일 (주말 skip=common util + 휴일 skip=cm_holiday). */
|
||||
void CM_NEXTBIZ(TPSVCINFO *p)
|
||||
{
|
||||
UBFH *b = (UBFH *)p->data;
|
||||
char in[16], cur[16], nxt[16];
|
||||
int hol, guard = 0;
|
||||
gets_(b, T_STR1, in, sizeof(in));
|
||||
if (in[0] == 0) { if (cmdb_get_param("BIZ_DATE", in) < 0) strcpy(in, "2026-07-19"); }
|
||||
strncpy(cur, in, sizeof(cur)-1); cur[sizeof(cur)-1] = 0;
|
||||
do {
|
||||
if (acq_next_bizday(cur, nxt) < 0) FAIL(b); /* 주말 건너뜀 */
|
||||
strncpy(cur, nxt, sizeof(cur)-1); cur[sizeof(cur)-1] = 0;
|
||||
hol = cmdb_is_holiday(nxt);
|
||||
if (hol < 0) FAIL(b);
|
||||
if (++guard > 30) break;
|
||||
} while (hol == 1); /* 휴일이면 다시 +1영업일 */
|
||||
sets_(b, T_STR3, nxt);
|
||||
OK(b);
|
||||
}
|
||||
|
||||
/* 5. CM_ADDDAYS - 달력일 N일 가산 (순수 계산). */
|
||||
void CM_ADDDAYS(TPSVCINFO *p)
|
||||
{
|
||||
UBFH *b = (UBFH *)p->data;
|
||||
char in[16], out[16];
|
||||
long n, i;
|
||||
int y, m, d;
|
||||
gets_(b, T_STR1, in, sizeof(in));
|
||||
n = getl(b, T_ARG1);
|
||||
if (sscanf(in, "%d-%d-%d", &y, &m, &d) != 3) FAIL(b);
|
||||
for (i = 0; i < n; i++) {
|
||||
d++;
|
||||
if (d > cm_mdays(y, m)) { d = 1; if (++m > 12) { m = 1; y++; } }
|
||||
}
|
||||
sprintf(out, "%04d-%02d-%02d", y, m, d);
|
||||
sets_(b, T_STR3, out);
|
||||
OK(b);
|
||||
}
|
||||
|
||||
/* 6. CM_WEEKDAY - 요일 계산 (순수 계산): 0=일..6=토. */
|
||||
void CM_WEEKDAY(TPSVCINFO *p)
|
||||
{
|
||||
UBFH *b = (UBFH *)p->data;
|
||||
static const char *KOR[] = {"일","월","화","수","목","금","토"};
|
||||
char in[16];
|
||||
int y, m, d, w;
|
||||
gets_(b, T_STR1, in, sizeof(in));
|
||||
if (sscanf(in, "%d-%d-%d", &y, &m, &d) != 3) FAIL(b);
|
||||
w = cm_dow(y, m, d);
|
||||
setl(b, T_ARG1, w);
|
||||
sets_(b, T_STR3, KOR[w]);
|
||||
setl(b, T_RC, (w == 0 || w == 6) ? 1 : 0); /* 1=주말 */
|
||||
OK(b);
|
||||
}
|
||||
|
||||
/* 7. CM_MONTHEND - 월말일 계산 (순수 계산). */
|
||||
void CM_MONTHEND(TPSVCINFO *p)
|
||||
{
|
||||
UBFH *b = (UBFH *)p->data;
|
||||
char in[16], out[16];
|
||||
int y, m, d, last;
|
||||
gets_(b, T_STR1, in, sizeof(in));
|
||||
if (sscanf(in, "%d-%d-%d", &y, &m, &d) != 3) FAIL(b);
|
||||
last = cm_mdays(y, m);
|
||||
sprintf(out, "%04d-%02d-%02d", y, m, last);
|
||||
sets_(b, T_STR3, out);
|
||||
setl(b, T_ARG1, last);
|
||||
OK(b);
|
||||
}
|
||||
|
||||
/* 8. CM_AMT_ROUND - 금액 반올림 (순수 계산): 단위(T_ARG1)로 반올림. */
|
||||
void CM_AMT_ROUND(TPSVCINFO *p)
|
||||
{
|
||||
UBFH *b = (UBFH *)p->data;
|
||||
long amt = getl(b, T_AMOUNT), unit = getl(b, T_ARG1), r;
|
||||
if (unit <= 0) unit = 1;
|
||||
r = ((amt + unit / 2) / unit) * unit;
|
||||
setl(b, T_AMT1, r);
|
||||
OK(b);
|
||||
}
|
||||
|
||||
/* 9. CM_AMT_SPLIT - 금액 균등 분할 (순수 계산): n등분 + 잔액. */
|
||||
void CM_AMT_SPLIT(TPSVCINFO *p)
|
||||
{
|
||||
UBFH *b = (UBFH *)p->data;
|
||||
long amt = getl(b, T_AMOUNT), n = getl(b, T_ARG1), each, rem;
|
||||
if (n < 1) n = 1;
|
||||
each = amt / n;
|
||||
rem = amt - each * n; /* 첫 회차에 잔액 가산 */
|
||||
setl(b, T_AMT1, each);
|
||||
setl(b, T_AMT2, rem);
|
||||
setl(b, T_COUNT, n);
|
||||
OK(b);
|
||||
}
|
||||
|
||||
/* 10. CM_SEQ - 공통 채번 (cm_seq 이름있는 카운터). */
|
||||
void CM_SEQ(TPSVCINFO *p)
|
||||
{
|
||||
UBFH *b = (UBFH *)p->data;
|
||||
char name[32];
|
||||
long v;
|
||||
gets_(b, T_KEY1, name, sizeof(name)); if (name[0] == 0) strcpy(name, "BATCH");
|
||||
v = cmdb_next_seq(name);
|
||||
if (v < 0) FAIL(b);
|
||||
setl(b, T_ARG1, v);
|
||||
OK(b);
|
||||
}
|
||||
|
||||
/* 11. CM_FX - 환율 조회 (cm_fx_rate). */
|
||||
void CM_FX(TPSVCINFO *p)
|
||||
{
|
||||
UBFH *b = (UBFH *)p->data;
|
||||
char ccy[8], bizdate[16];
|
||||
long rate;
|
||||
gets_(b, T_CCY, ccy, sizeof(ccy)); if (ccy[0] == 0) strcpy(ccy, "USD");
|
||||
gets_(b, T_BIZDATE, bizdate, sizeof(bizdate));
|
||||
if (bizdate[0] == 0) { if (cmdb_get_param("BIZ_DATE", bizdate) < 0) strcpy(bizdate, "2026-07-19"); }
|
||||
if (cmdb_get_fx_rate(ccy, bizdate, &rate) < 0) { userlog("CM_FX 환율 없음 ccy=%s", ccy); FAIL(b); }
|
||||
setl(b, T_ARG1, rate);
|
||||
OK(b);
|
||||
}
|
||||
|
||||
/* 12. CM_FX_CONV - 환율 환산 (cm_fx_rate + compute): 외화 -> 원화. */
|
||||
void CM_FX_CONV(TPSVCINFO *p)
|
||||
{
|
||||
UBFH *b = (UBFH *)p->data;
|
||||
char ccy[8], bizdate[16];
|
||||
long rate, fx, krw;
|
||||
gets_(b, T_CCY, ccy, sizeof(ccy)); if (ccy[0] == 0) strcpy(ccy, "USD");
|
||||
gets_(b, T_BIZDATE, bizdate, sizeof(bizdate));
|
||||
if (bizdate[0] == 0) { if (cmdb_get_param("BIZ_DATE", bizdate) < 0) strcpy(bizdate, "2026-07-19"); }
|
||||
fx = getl(b, T_FX_AMT);
|
||||
if (cmdb_get_fx_rate(ccy, bizdate, &rate) < 0) FAIL(b);
|
||||
krw = fx * rate / 100; /* rate_bps = KRW*100 per unit */
|
||||
setl(b, T_AMOUNT, krw);
|
||||
setl(b, T_ARG1, rate);
|
||||
OK(b);
|
||||
}
|
||||
|
||||
/* 13. CM_LUHN - 카드번호 Luhn 검증 (순수 계산, common util). */
|
||||
void CM_LUHN(TPSVCINFO *p)
|
||||
{
|
||||
UBFH *b = (UBFH *)p->data;
|
||||
char pan[32];
|
||||
gets_(b, T_STR5, pan, sizeof(pan));
|
||||
setl(b, T_RC, acq_luhn_ok(pan) ? 0 : 1); /* 0=유효 */
|
||||
OK(b);
|
||||
}
|
||||
|
||||
/* 14. CM_CRC - CRC16 계산 (순수 계산). */
|
||||
void CM_CRC(TPSVCINFO *p)
|
||||
{
|
||||
UBFH *b = (UBFH *)p->data;
|
||||
char in[256], out[8];
|
||||
unsigned int crc;
|
||||
gets_(b, T_STR1, in, sizeof(in));
|
||||
crc = cm_crc16(in);
|
||||
sprintf(out, "%04X", crc);
|
||||
sets_(b, T_STR4, out);
|
||||
setl(b, T_ARG1, (long)crc);
|
||||
OK(b);
|
||||
}
|
||||
|
||||
/* 15. CM_MASK - 카드번호 마스킹 (순수 계산): 앞6 뒤4 노출. */
|
||||
void CM_MASK(TPSVCINFO *p)
|
||||
{
|
||||
UBFH *b = (UBFH *)p->data;
|
||||
char pan[32], out[32];
|
||||
int n, i;
|
||||
gets_(b, T_STR5, pan, sizeof(pan));
|
||||
n = (int)strlen(pan);
|
||||
for (i = 0; i < n && i < (int)sizeof(out) - 1; i++)
|
||||
out[i] = (i < 6 || i >= n - 4) ? pan[i] : '*';
|
||||
out[(n < (int)sizeof(out)-1) ? n : (int)sizeof(out)-1] = 0;
|
||||
sets_(b, T_STR4, out);
|
||||
OK(b);
|
||||
}
|
||||
|
||||
/* 16. CM_FMT_DATE - 날짜 포맷 변환 (순수 계산): YYYYMMDD -> YYYY-MM-DD. */
|
||||
void CM_FMT_DATE(TPSVCINFO *p)
|
||||
{
|
||||
UBFH *b = (UBFH *)p->data;
|
||||
char in[16], out[16];
|
||||
int y, m, d;
|
||||
gets_(b, T_STR1, in, sizeof(in));
|
||||
if (strlen(in) == 8 && sscanf(in, "%4d%2d%2d", &y, &m, &d) == 3)
|
||||
sprintf(out, "%04d-%02d-%02d", y, m, d);
|
||||
else if (sscanf(in, "%d-%d-%d", &y, &m, &d) == 3)
|
||||
sprintf(out, "%04d%02d%02d", y, m, d); /* 역변환 */
|
||||
else FAIL(b);
|
||||
sets_(b, T_STR3, out);
|
||||
OK(b);
|
||||
}
|
||||
|
||||
/* 17. CM_FMT_AMT - 금액 천단위 콤마 포맷 (순수 계산). */
|
||||
void CM_FMT_AMT(TPSVCINFO *p)
|
||||
{
|
||||
UBFH *b = (UBFH *)p->data;
|
||||
char raw[32], out[48];
|
||||
long amt = getl(b, T_AMOUNT);
|
||||
int n, i, j, k, neg = 0;
|
||||
if (amt < 0) { neg = 1; amt = -amt; }
|
||||
sprintf(raw, "%ld", amt);
|
||||
n = (int)strlen(raw);
|
||||
j = 0;
|
||||
if (neg) out[j++] = '-';
|
||||
for (i = 0; i < n; i++) {
|
||||
out[j++] = raw[i];
|
||||
k = n - i - 1;
|
||||
if (k > 0 && k % 3 == 0) out[j++] = ',';
|
||||
}
|
||||
out[j] = 0;
|
||||
sets_(b, T_STR4, out);
|
||||
OK(b);
|
||||
}
|
||||
|
||||
/* 18. CM_PARAM - 공통 파라미터 조회. */
|
||||
void CM_PARAM(TPSVCINFO *p)
|
||||
{
|
||||
UBFH *b = (UBFH *)p->data;
|
||||
char key[64], val[128];
|
||||
gets_(b, T_STR1, key, sizeof(key));
|
||||
if (key[0] == 0) FAIL(b);
|
||||
if (cmdb_get_param(key, val) < 0) { userlog("CM_PARAM 미존재 key=%s", key); FAIL(b); }
|
||||
sets_(b, T_STR3, val);
|
||||
OK(b);
|
||||
}
|
||||
|
||||
/* 19. CM_PARAM_SET - 공통 파라미터 설정 (owner-writes, upsert). */
|
||||
void CM_PARAM_SET(TPSVCINFO *p)
|
||||
{
|
||||
UBFH *b = (UBFH *)p->data;
|
||||
char key[64], val[128];
|
||||
gets_(b, T_STR1, key, sizeof(key));
|
||||
gets_(b, T_STR3, val, sizeof(val));
|
||||
if (key[0] == 0) FAIL(b);
|
||||
if (cmdb_set_param(key, val) < 0) FAIL(b);
|
||||
userlog("CM_PARAM_SET %s=%s", key, val);
|
||||
OK(b);
|
||||
}
|
||||
|
||||
/* 20. CM_CACHE_LOAD - 코드 그룹 캐시 로드 (건수 조회로 로딩 시뮬레이션). */
|
||||
void CM_CACHE_LOAD(TPSVCINFO *p)
|
||||
{
|
||||
UBFH *b = (UBFH *)p->data;
|
||||
char grp[32];
|
||||
long cnt;
|
||||
gets_(b, T_STR1, grp, sizeof(grp)); if (grp[0] == 0) strcpy(grp, "CCY");
|
||||
cnt = cmdb_count_codes(grp);
|
||||
if (cnt < 0) FAIL(b);
|
||||
setl(b, T_COUNT, cnt);
|
||||
userlog("CM_CACHE_LOAD grp=%s codes=%ld", grp, cnt);
|
||||
OK(b);
|
||||
}
|
||||
|
||||
/* 21. CM_CACHE_INV - 캐시 무효화 (순수 처리: 버전 증가 표시). */
|
||||
void CM_CACHE_INV(TPSVCINFO *p)
|
||||
{
|
||||
UBFH *b = (UBFH *)p->data;
|
||||
char grp[32];
|
||||
gets_(b, T_STR1, grp, sizeof(grp)); if (grp[0] == 0) strcpy(grp, "ALL");
|
||||
setl(b, T_RC, 0);
|
||||
sets_(b, T_MSG, "캐시 무효화 완료");
|
||||
userlog("CM_CACHE_INV grp=%s", grp);
|
||||
OK(b);
|
||||
}
|
||||
|
||||
/* 22. CM_ERRMSG - 오류코드 -> 메시지 매핑 (cm_code 그룹 STATUS/ERR 조회). */
|
||||
void CM_ERRMSG(TPSVCINFO *p)
|
||||
{
|
||||
UBFH *b = (UBFH *)p->data;
|
||||
char grp[32], code[32], name[128];
|
||||
gets_(b, T_STR1, grp, sizeof(grp)); if (grp[0] == 0) strcpy(grp, "STATUS");
|
||||
gets_(b, T_STR2, code, sizeof(code));
|
||||
if (cmdb_get_code_name(grp, code, name) < 0)
|
||||
strcpy(name, "알 수 없는 코드"); /* 미등록 시 기본 메시지 */
|
||||
sets_(b, T_MSG, name);
|
||||
OK(b);
|
||||
}
|
||||
|
||||
/* 23. CM_LOG - 공통 로그 기록 (cm_log). */
|
||||
void CM_LOG(TPSVCINFO *p)
|
||||
{
|
||||
UBFH *b = (UBFH *)p->data;
|
||||
char svc[64], msg[256], bizdate[16];
|
||||
long id;
|
||||
gets_(b, T_SVCNAME, svc, sizeof(svc)); if (svc[0] == 0) strcpy(svc, "CM");
|
||||
gets_(b, T_MSG, msg, sizeof(msg));
|
||||
gets_(b, T_BIZDATE, bizdate, sizeof(bizdate));
|
||||
if (bizdate[0] == 0) { if (cmdb_get_param("BIZ_DATE", bizdate) < 0) strcpy(bizdate, "2026-07-19"); }
|
||||
id = cmdb_log(svc, "I", msg, bizdate);
|
||||
if (id < 0) FAIL(b);
|
||||
setl(b, T_ID1, id);
|
||||
OK(b);
|
||||
}
|
||||
|
||||
/* 24. CM_HOLIDAY_CHK - 휴일 여부 확인 (cm_holiday). */
|
||||
void CM_HOLIDAY_CHK(TPSVCINFO *p)
|
||||
{
|
||||
UBFH *b = (UBFH *)p->data;
|
||||
char date[16];
|
||||
int h;
|
||||
gets_(b, T_STR1, date, sizeof(date));
|
||||
if (date[0] == 0) FAIL(b);
|
||||
h = cmdb_is_holiday(date);
|
||||
if (h < 0) FAIL(b);
|
||||
setl(b, T_RC, h); /* 1=휴일, 0=평일 */
|
||||
OK(b);
|
||||
}
|
||||
|
||||
/* 25. CM_CHECKDIGIT - Luhn 체크디지트 생성 (순수 계산). */
|
||||
void CM_CHECKDIGIT(TPSVCINFO *p)
|
||||
{
|
||||
UBFH *b = (UBFH *)p->data;
|
||||
char base[32], cand[34];
|
||||
int d, found = -1, n;
|
||||
gets_(b, T_STR2, base, sizeof(base));
|
||||
n = (int)strlen(base);
|
||||
if (n == 0 || n >= (int)sizeof(cand) - 1) FAIL(b);
|
||||
for (d = 0; d <= 9; d++) {
|
||||
strcpy(cand, base);
|
||||
cand[n] = (char)('0' + d);
|
||||
cand[n + 1] = 0;
|
||||
if (acq_luhn_ok(cand)) { found = d; break; }
|
||||
}
|
||||
if (found < 0) FAIL(b);
|
||||
setl(b, T_ARG1, found);
|
||||
sets_(b, T_STR4, cand); /* 체크디지트 포함 완성번호 */
|
||||
OK(b);
|
||||
}
|
||||
|
||||
/* 26. CM_INTEREST - 단리 이자 계산 (순수 계산): 원금*연이율(bps)*일수/(10000*365). */
|
||||
void CM_INTEREST(TPSVCINFO *p)
|
||||
{
|
||||
UBFH *b = (UBFH *)p->data;
|
||||
long principal = getl(b, T_AMOUNT), rate_bps = getl(b, T_ARG1), days = getl(b, T_ARG2), interest;
|
||||
if (principal < 0 || rate_bps < 0 || days < 0) FAIL(b);
|
||||
interest = principal * rate_bps / 10000 * days / 365;
|
||||
setl(b, T_AMT1, interest);
|
||||
OK(b);
|
||||
}
|
||||
|
||||
/* 27. CM_PRORATE - 일할 계산 (순수 계산): 금액*사용일/총일. */
|
||||
void CM_PRORATE(TPSVCINFO *p)
|
||||
{
|
||||
UBFH *b = (UBFH *)p->data;
|
||||
long amt = getl(b, T_AMOUNT), used = getl(b, T_ARG1), total = getl(b, T_ARG2), pro;
|
||||
if (total <= 0) FAIL(b);
|
||||
if (used < 0) used = 0;
|
||||
if (used > total) used = total;
|
||||
pro = amt * used / total;
|
||||
setl(b, T_AMT1, pro);
|
||||
OK(b);
|
||||
}
|
||||
|
||||
/* 28. CM_NETTING_CALC - 네팅 계산 (순수 계산): 차변-대변. */
|
||||
void CM_NETTING_CALC(TPSVCINFO *p)
|
||||
{
|
||||
UBFH *b = (UBFH *)p->data;
|
||||
long debit = getl(b, T_AMT1), credit = getl(b, T_AMT2), net;
|
||||
net = debit - credit;
|
||||
setl(b, T_NET, net);
|
||||
setl(b, T_RC, net > 0 ? 1 : (net < 0 ? -1 : 0)); /* 1=수취, -1=지급 */
|
||||
OK(b);
|
||||
}
|
||||
|
||||
/* 29. CM_STATUS - 모듈 상태: 파라미터 건수 조회로 DB 연결 확인. */
|
||||
void CM_STATUS(TPSVCINFO *p)
|
||||
{
|
||||
UBFH *b = (UBFH *)p->data;
|
||||
EXEC SQL BEGIN DECLARE SECTION;
|
||||
long h_cnt;
|
||||
EXEC SQL END DECLARE SECTION;
|
||||
EXEC SQL SELECT count(*) INTO :h_cnt FROM cm_param;
|
||||
if (sqlca.sqlcode < 0) FAIL(b);
|
||||
setl(b, T_COUNT, h_cnt);
|
||||
setl(b, T_RC, 0);
|
||||
sets_(b, T_STATUS, "UP");
|
||||
OK(b);
|
||||
}
|
||||
|
||||
/* 30. CM_PING - 핑 (순수 처리). */
|
||||
void CM_PING(TPSVCINFO *p)
|
||||
{
|
||||
UBFH *b = (UBFH *)p->data;
|
||||
sets_(b, T_MSG, "PONG");
|
||||
setl(b, T_RC, 0);
|
||||
OK(b);
|
||||
}
|
||||
|
||||
/* vim: set ts=4 sw=4 et smartindent: */
|
||||
126
app/src/cm/dbio/cm_code_dbio.pgc
Normal file
126
app/src/cm/dbio/cm_code_dbio.pgc
Normal file
|
|
@ -0,0 +1,126 @@
|
|||
/*
|
||||
* cm_code_dbio.pgc - 공통코드/환율/휴일 DB 접근 (part of libcmdbio.a).
|
||||
* ECPG functions; run on the caller's XA branch. No EXEC SQL CONNECT.
|
||||
*/
|
||||
#include <string.h>
|
||||
#include <userlog.h>
|
||||
#include "cm_dbio.h"
|
||||
|
||||
int cmdb_get_code_name(const char *grp, const char *code, char *out)
|
||||
{
|
||||
EXEC SQL BEGIN DECLARE SECTION;
|
||||
char h_grp[32], h_code[32], h_name[128];
|
||||
EXEC SQL END DECLARE SECTION;
|
||||
|
||||
strncpy(h_grp, grp, sizeof(h_grp)-1); h_grp[sizeof(h_grp)-1] = 0;
|
||||
strncpy(h_code, code, sizeof(h_code)-1); h_code[sizeof(h_code)-1] = 0;
|
||||
|
||||
EXEC SQL SELECT code_name INTO :h_name FROM cm_code
|
||||
WHERE code_grp = :h_grp AND code = :h_code AND use_yn = 'Y';
|
||||
if (sqlca.sqlcode == 100) { out[0] = 0; return -1; }
|
||||
if (sqlca.sqlcode < 0) {
|
||||
userlog("cmdb_get_code_name FAIL [%d] %s", sqlca.sqlcode, sqlca.sqlerrm.sqlerrmc);
|
||||
return -1;
|
||||
}
|
||||
strncpy(out, h_name, 127); out[127] = 0;
|
||||
return 0;
|
||||
}
|
||||
|
||||
int cmdb_code_exists(const char *grp, const char *code)
|
||||
{
|
||||
EXEC SQL BEGIN DECLARE SECTION;
|
||||
long h_cnt;
|
||||
char h_grp[32], h_code[32];
|
||||
EXEC SQL END DECLARE SECTION;
|
||||
|
||||
strncpy(h_grp, grp, sizeof(h_grp)-1); h_grp[sizeof(h_grp)-1] = 0;
|
||||
strncpy(h_code, code, sizeof(h_code)-1); h_code[sizeof(h_code)-1] = 0;
|
||||
|
||||
EXEC SQL SELECT count(*) INTO :h_cnt FROM cm_code
|
||||
WHERE code_grp = :h_grp AND code = :h_code;
|
||||
if (sqlca.sqlcode < 0) {
|
||||
userlog("cmdb_code_exists FAIL [%d] %s", sqlca.sqlcode, sqlca.sqlerrm.sqlerrmc);
|
||||
return -1;
|
||||
}
|
||||
return h_cnt > 0 ? 1 : 0;
|
||||
}
|
||||
|
||||
int cmdb_insert_code(const char *grp, const char *code, const char *name, long sort)
|
||||
{
|
||||
EXEC SQL BEGIN DECLARE SECTION;
|
||||
long h_sort = sort;
|
||||
char h_grp[32], h_code[32], h_name[128];
|
||||
EXEC SQL END DECLARE SECTION;
|
||||
|
||||
strncpy(h_grp, grp, sizeof(h_grp)-1); h_grp[sizeof(h_grp)-1] = 0;
|
||||
strncpy(h_code, code, sizeof(h_code)-1); h_code[sizeof(h_code)-1] = 0;
|
||||
strncpy(h_name, name, sizeof(h_name)-1); h_name[sizeof(h_name)-1] = 0;
|
||||
|
||||
EXEC SQL INSERT INTO cm_code (code_grp, code, code_name, sort_ord)
|
||||
VALUES (:h_grp, :h_code, :h_name, :h_sort)
|
||||
ON CONFLICT (code_grp, code) DO UPDATE
|
||||
SET code_name = EXCLUDED.code_name, sort_ord = EXCLUDED.sort_ord, updated_at = now();
|
||||
if (sqlca.sqlcode < 0) {
|
||||
userlog("cmdb_insert_code FAIL [%d] %s", sqlca.sqlcode, sqlca.sqlerrm.sqlerrmc);
|
||||
return -1;
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
long cmdb_count_codes(const char *grp)
|
||||
{
|
||||
EXEC SQL BEGIN DECLARE SECTION;
|
||||
long h_cnt;
|
||||
char h_grp[32];
|
||||
EXEC SQL END DECLARE SECTION;
|
||||
|
||||
strncpy(h_grp, grp, sizeof(h_grp)-1); h_grp[sizeof(h_grp)-1] = 0;
|
||||
|
||||
EXEC SQL SELECT count(*) INTO :h_cnt FROM cm_code
|
||||
WHERE code_grp = :h_grp AND use_yn = 'Y';
|
||||
if (sqlca.sqlcode < 0) {
|
||||
userlog("cmdb_count_codes FAIL [%d] %s", sqlca.sqlcode, sqlca.sqlerrm.sqlerrmc);
|
||||
return -1;
|
||||
}
|
||||
return h_cnt;
|
||||
}
|
||||
|
||||
int cmdb_get_fx_rate(const char *ccy, const char *base_date, long *rate_out)
|
||||
{
|
||||
EXEC SQL BEGIN DECLARE SECTION;
|
||||
long h_rate;
|
||||
char h_ccy[8], h_date[16];
|
||||
EXEC SQL END DECLARE SECTION;
|
||||
|
||||
strncpy(h_ccy, ccy, sizeof(h_ccy)-1); h_ccy[sizeof(h_ccy)-1] = 0;
|
||||
strncpy(h_date, base_date, sizeof(h_date)-1); h_date[sizeof(h_date)-1] = 0;
|
||||
|
||||
/* 요청일 이하 가장 최근 기준일 환율 */
|
||||
EXEC SQL SELECT rate_bps INTO :h_rate FROM cm_fx_rate
|
||||
WHERE ccy = :h_ccy AND base_date <= :h_date
|
||||
ORDER BY base_date DESC LIMIT 1;
|
||||
if (sqlca.sqlcode == 100) return -1;
|
||||
if (sqlca.sqlcode < 0) {
|
||||
userlog("cmdb_get_fx_rate FAIL [%d] %s", sqlca.sqlcode, sqlca.sqlerrm.sqlerrmc);
|
||||
return -1;
|
||||
}
|
||||
*rate_out = h_rate;
|
||||
return 0;
|
||||
}
|
||||
|
||||
int cmdb_is_holiday(const char *date)
|
||||
{
|
||||
EXEC SQL BEGIN DECLARE SECTION;
|
||||
long h_cnt;
|
||||
char h_date[16];
|
||||
EXEC SQL END DECLARE SECTION;
|
||||
|
||||
strncpy(h_date, date, sizeof(h_date)-1); h_date[sizeof(h_date)-1] = 0;
|
||||
|
||||
EXEC SQL SELECT count(*) INTO :h_cnt FROM cm_holiday WHERE holi_date = :h_date;
|
||||
if (sqlca.sqlcode < 0) {
|
||||
userlog("cmdb_is_holiday FAIL [%d] %s", sqlca.sqlcode, sqlca.sqlerrm.sqlerrmc);
|
||||
return -1;
|
||||
}
|
||||
return h_cnt > 0 ? 1 : 0;
|
||||
}
|
||||
33
app/src/cm/dbio/cm_dbio.h
Normal file
33
app/src/cm/dbio/cm_dbio.h
Normal file
|
|
@ -0,0 +1,33 @@
|
|||
/*
|
||||
* cm_dbio.h - cm 모듈 (공통) DB 접근 계층 (libcmdbio.a).
|
||||
* ECPG (EXEC SQL) functions compiled into a static library and linked into
|
||||
* cm_svr and the cm batches. They run on the caller's XA branch/connection
|
||||
* (no EXEC SQL CONNECT); sqlca is the shared per-thread ECPG SQLCA.
|
||||
* Return convention: 0 = ok, -1 = SQL error / not found (caller inspects sqlca).
|
||||
*/
|
||||
#ifndef CM_DBIO_H
|
||||
#define CM_DBIO_H
|
||||
|
||||
/* 공통코드 (cm_code) */
|
||||
int cmdb_get_code_name(const char *grp, const char *code, char *out /* >= 128 */);
|
||||
int cmdb_code_exists(const char *grp, const char *code); /* 1=존재, 0=없음, -1=오류 */
|
||||
int cmdb_insert_code(const char *grp, const char *code, const char *name, long sort);
|
||||
long cmdb_count_codes(const char *grp);
|
||||
|
||||
/* 공통 파라미터 (cm_param) */
|
||||
int cmdb_get_param(const char *key, char *out /* >= 128 */);
|
||||
int cmdb_set_param(const char *key, const char *val);
|
||||
|
||||
/* 채번 (cm_seq) - 이름있는 카운터 +1 -> 새 값 */
|
||||
long cmdb_next_seq(const char *name);
|
||||
|
||||
/* 환율 (cm_fx_rate) - 통화의 최신 기준일 rate_bps 조회 */
|
||||
int cmdb_get_fx_rate(const char *ccy, const char *base_date, long *rate_out);
|
||||
|
||||
/* 휴일 (cm_holiday) - 1=휴일, 0=평일, -1=오류 */
|
||||
int cmdb_is_holiday(const char *date);
|
||||
|
||||
/* 공통 로그 (cm_log) */
|
||||
long cmdb_log(const char *svc, const char *level, const char *msg, const char *bizdate);
|
||||
|
||||
#endif /* CM_DBIO_H */
|
||||
93
app/src/cm/dbio/cm_param_dbio.pgc
Normal file
93
app/src/cm/dbio/cm_param_dbio.pgc
Normal file
|
|
@ -0,0 +1,93 @@
|
|||
/*
|
||||
* cm_param_dbio.pgc - 공통 파라미터/채번/공통로그 DB 접근 (part of libcmdbio.a).
|
||||
*/
|
||||
#include <string.h>
|
||||
#include <userlog.h>
|
||||
#include "cm_dbio.h"
|
||||
|
||||
int cmdb_get_param(const char *key, char *out)
|
||||
{
|
||||
EXEC SQL BEGIN DECLARE SECTION;
|
||||
char h_key[64], h_val[128];
|
||||
EXEC SQL END DECLARE SECTION;
|
||||
|
||||
strncpy(h_key, key, sizeof(h_key)-1); h_key[sizeof(h_key)-1] = 0;
|
||||
|
||||
EXEC SQL SELECT param_val INTO :h_val FROM cm_param WHERE param_key = :h_key;
|
||||
if (sqlca.sqlcode == 100) { out[0] = 0; return -1; }
|
||||
if (sqlca.sqlcode < 0) {
|
||||
userlog("cmdb_get_param FAIL [%d] %s", sqlca.sqlcode, sqlca.sqlerrm.sqlerrmc);
|
||||
return -1;
|
||||
}
|
||||
strncpy(out, h_val, 127); out[127] = 0;
|
||||
return 0;
|
||||
}
|
||||
|
||||
int cmdb_set_param(const char *key, const char *val)
|
||||
{
|
||||
EXEC SQL BEGIN DECLARE SECTION;
|
||||
char h_key[64], h_val[128];
|
||||
EXEC SQL END DECLARE SECTION;
|
||||
|
||||
strncpy(h_key, key, sizeof(h_key)-1); h_key[sizeof(h_key)-1] = 0;
|
||||
strncpy(h_val, val, sizeof(h_val)-1); h_val[sizeof(h_val)-1] = 0;
|
||||
|
||||
EXEC SQL INSERT INTO cm_param (param_key, param_val) VALUES (:h_key, :h_val)
|
||||
ON CONFLICT (param_key) DO UPDATE
|
||||
SET param_val = EXCLUDED.param_val, updated_at = now();
|
||||
if (sqlca.sqlcode < 0) {
|
||||
userlog("cmdb_set_param FAIL [%d] %s", sqlca.sqlcode, sqlca.sqlerrm.sqlerrmc);
|
||||
return -1;
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
/* cm_seq 이름있는 카운터를 원자적으로 +1 하고 새 값을 돌려준다. */
|
||||
long cmdb_next_seq(const char *name)
|
||||
{
|
||||
EXEC SQL BEGIN DECLARE SECTION;
|
||||
long h_val;
|
||||
char h_name[32];
|
||||
EXEC SQL END DECLARE SECTION;
|
||||
|
||||
strncpy(h_name, name, sizeof(h_name)-1); h_name[sizeof(h_name)-1] = 0;
|
||||
|
||||
EXEC SQL UPDATE cm_seq SET seq_val = seq_val + 1, updated_at = now()
|
||||
WHERE seq_name = :h_name;
|
||||
if (sqlca.sqlcode < 0) {
|
||||
userlog("cmdb_next_seq UPDATE FAIL [%d] %s", sqlca.sqlcode, sqlca.sqlerrm.sqlerrmc);
|
||||
return -1;
|
||||
}
|
||||
if (sqlca.sqlcode == 100) { /* 최초 사용: 행 생성 */
|
||||
EXEC SQL INSERT INTO cm_seq (seq_name, seq_val) VALUES (:h_name, 1);
|
||||
if (sqlca.sqlcode < 0) return -1;
|
||||
return 1;
|
||||
}
|
||||
EXEC SQL SELECT seq_val INTO :h_val FROM cm_seq WHERE seq_name = :h_name;
|
||||
if (sqlca.sqlcode < 0 || sqlca.sqlcode == 100) return -1;
|
||||
return h_val;
|
||||
}
|
||||
|
||||
long cmdb_log(const char *svc, const char *level, const char *msg, const char *bizdate)
|
||||
{
|
||||
EXEC SQL BEGIN DECLARE SECTION;
|
||||
long h_id;
|
||||
char h_svc[64], h_level[4], h_msg[256], h_bizdate[16];
|
||||
EXEC SQL END DECLARE SECTION;
|
||||
|
||||
strncpy(h_svc, svc, sizeof(h_svc)-1); h_svc[sizeof(h_svc)-1] = 0;
|
||||
strncpy(h_level, level, sizeof(h_level)-1); h_level[sizeof(h_level)-1] = 0;
|
||||
strncpy(h_msg, msg, sizeof(h_msg)-1); h_msg[sizeof(h_msg)-1] = 0;
|
||||
strncpy(h_bizdate, bizdate, sizeof(h_bizdate)-1); h_bizdate[sizeof(h_bizdate)-1] = 0;
|
||||
|
||||
EXEC SQL SELECT nextval('cm_log_seq') INTO :h_id;
|
||||
if (sqlca.sqlcode < 0) return -1;
|
||||
|
||||
EXEC SQL INSERT INTO cm_log (log_id, svc_name, log_level, msg, biz_date)
|
||||
VALUES (:h_id, :h_svc, :h_level, :h_msg, :h_bizdate);
|
||||
if (sqlca.sqlcode < 0) {
|
||||
userlog("cmdb_log FAIL [%d] %s", sqlca.sqlcode, sqlca.sqlerrm.sqlerrmc);
|
||||
return -1;
|
||||
}
|
||||
return h_id;
|
||||
}
|
||||
65
app/src/lg/batch/lg_post_batch.pgc
Normal file
65
app/src/lg/batch/lg_post_batch.pgc
Normal file
|
|
@ -0,0 +1,65 @@
|
|||
/*
|
||||
* lg_post_batch.pgc - lg 매입 원장 전기 배치 (커서 + XA).
|
||||
*
|
||||
* ATMI client: tpinit/tpopen open the ECPG XA RM, tpbegin starts ONE global
|
||||
* transaction, an EXEC SQL cursor walks 대사완료(M) 상태 매입건을 훑어 각 건에
|
||||
* 대해 매입 전기 전표(ACQ)를 복식부기로 생성한다:
|
||||
* 차변 RECV = amount, 대변 MERCH_PAY = net, 대변 FEE_INC = fee.
|
||||
* 전기 후 purchase.status M -> 'P'(posted) 로 표시, tpcommit 이 XA 2PC 를 구동.
|
||||
* Usage: lg_post_batch [YYYY-MM-DD]
|
||||
*/
|
||||
#include <stdio.h>
|
||||
#include <stdlib.h>
|
||||
#include <string.h>
|
||||
#include <atmi.h>
|
||||
#include "lg_dbio.h"
|
||||
|
||||
int main(int argc, char **argv)
|
||||
{
|
||||
const char *bizdate = (argc > 1) ? argv[1] : "2026-07-19";
|
||||
long rows = 0, vid;
|
||||
|
||||
EXEC SQL BEGIN DECLARE SECTION;
|
||||
char h_bizdate[16], h_merch[64];
|
||||
long h_pid, h_amount, h_fee, h_net;
|
||||
EXEC SQL END DECLARE SECTION;
|
||||
|
||||
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 lpc CURSOR FOR
|
||||
SELECT p.purchase_id, p.merchant_id, p.amount, p.fee, p.net
|
||||
FROM purchase p
|
||||
WHERE p.biz_date = :h_bizdate AND p.status = 'M'
|
||||
ORDER BY p.purchase_id;
|
||||
EXEC SQL OPEN lpc;
|
||||
if (sqlca.sqlcode < 0) { fprintf(stderr, "OPEN FAIL [%d] %s\n", sqlca.sqlcode, sqlca.sqlerrm.sqlerrmc); tpabort(0); return 1; }
|
||||
|
||||
for (;;) {
|
||||
EXEC SQL FETCH lpc INTO :h_pid, :h_merch, :h_amount, :h_fee, :h_net;
|
||||
if (sqlca.sqlcode == 100) break;
|
||||
if (sqlca.sqlcode < 0) { fprintf(stderr, "FETCH FAIL [%d] %s\n", sqlca.sqlcode, sqlca.sqlerrm.sqlerrmc); EXEC SQL CLOSE lpc; tpabort(0); return 1; }
|
||||
|
||||
vid = lgdb_next_voucher_id();
|
||||
if (vid < 0) { EXEC SQL CLOSE lpc; tpabort(0); return 1; }
|
||||
if (lgdb_insert_voucher(vid, "ACQ", h_pid, "P", bizdate, "매입전기(배치)") < 0 ||
|
||||
lgdb_insert_line(vid, "RECV", "D", h_amount, h_merch, bizdate, "posted") < 0 ||
|
||||
lgdb_insert_line(vid, "MERCH_PAY", "C", h_net, h_merch, bizdate, "posted") < 0 ||
|
||||
lgdb_insert_line(vid, "FEE_INC", "C", h_fee, h_merch, bizdate, "posted") < 0) {
|
||||
EXEC SQL CLOSE lpc; tpabort(0); return 1;
|
||||
}
|
||||
EXEC SQL UPDATE purchase SET status = 'P', updated_at = now() WHERE purchase_id = :h_pid;
|
||||
if (sqlca.sqlcode < 0) { EXEC SQL CLOSE lpc; tpabort(0); return 1; }
|
||||
rows++;
|
||||
}
|
||||
EXEC SQL CLOSE lpc;
|
||||
|
||||
if (tpcommit(0) < 0) { fprintf(stderr, "tpcommit FAIL: %s\n", tpstrerror(tperrno)); tpabort(0); return 1; }
|
||||
printf(">>> lg_post_batch COMMIT: bizdate=%s 전기전표=%ld\n", bizdate, rows);
|
||||
|
||||
tpclose(); tpterm();
|
||||
return 0;
|
||||
}
|
||||
63
app/src/lg/batch/lg_trialbal_batch.pgc
Normal file
63
app/src/lg/batch/lg_trialbal_batch.pgc
Normal file
|
|
@ -0,0 +1,63 @@
|
|||
/*
|
||||
* lg_trialbal_batch.pgc - lg 시산표(trial balance) 배치 (커서 + XA).
|
||||
*
|
||||
* 당일 원장 라인을 계정과목(acct)별로 GROUP BY 하여 차변/대변 합계를 산출하고
|
||||
* lg_trial_bal 에 upsert 한다. 전체 대차 차액(diff) 을 누적해 마지막에 출력하여
|
||||
* 복식부기 균형 여부를 검증한다. 모두 ONE global XA transaction 하에서 수행.
|
||||
* Usage: lg_trialbal_batch [YYYY-MM-DD]
|
||||
*/
|
||||
#include <stdio.h>
|
||||
#include <stdlib.h>
|
||||
#include <string.h>
|
||||
#include <atmi.h>
|
||||
#include "lg_dbio.h"
|
||||
|
||||
int main(int argc, char **argv)
|
||||
{
|
||||
const char *bizdate = (argc > 1) ? argv[1] : "2026-07-19";
|
||||
long rows = 0, net_diff = 0;
|
||||
|
||||
EXEC SQL BEGIN DECLARE SECTION;
|
||||
char h_bizdate[16], h_acct[32];
|
||||
long h_dr, h_cr, h_diff;
|
||||
EXEC SQL END DECLARE SECTION;
|
||||
|
||||
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 ltc CURSOR FOR
|
||||
SELECT acct,
|
||||
coalesce(sum(CASE WHEN dc = 'D' THEN amount ELSE 0 END), 0),
|
||||
coalesce(sum(CASE WHEN dc = 'C' THEN amount ELSE 0 END), 0)
|
||||
FROM lg_ledger_line WHERE biz_date = :h_bizdate
|
||||
GROUP BY acct ORDER BY acct;
|
||||
EXEC SQL OPEN ltc;
|
||||
if (sqlca.sqlcode < 0) { fprintf(stderr, "OPEN FAIL [%d] %s\n", sqlca.sqlcode, sqlca.sqlerrm.sqlerrmc); tpabort(0); return 1; }
|
||||
|
||||
for (;;) {
|
||||
EXEC SQL FETCH ltc INTO :h_acct, :h_dr, :h_cr;
|
||||
if (sqlca.sqlcode == 100) break;
|
||||
if (sqlca.sqlcode < 0) { fprintf(stderr, "FETCH FAIL [%d] %s\n", sqlca.sqlcode, sqlca.sqlerrm.sqlerrmc); EXEC SQL CLOSE ltc; tpabort(0); return 1; }
|
||||
h_diff = h_dr - h_cr;
|
||||
|
||||
EXEC SQL INSERT INTO lg_trial_bal (acct, biz_date, debit_total, credit_total, diff)
|
||||
VALUES (:h_acct, :h_bizdate, :h_dr, :h_cr, :h_diff)
|
||||
ON CONFLICT (acct, biz_date) DO UPDATE
|
||||
SET debit_total = EXCLUDED.debit_total, credit_total = EXCLUDED.credit_total,
|
||||
diff = EXCLUDED.diff, updated_at = now();
|
||||
if (sqlca.sqlcode < 0) { fprintf(stderr, "UPSERT FAIL [%d] %s\n", sqlca.sqlcode, sqlca.sqlerrm.sqlerrmc); EXEC SQL CLOSE ltc; tpabort(0); return 1; }
|
||||
net_diff += h_diff;
|
||||
rows++;
|
||||
}
|
||||
EXEC SQL CLOSE ltc;
|
||||
|
||||
if (tpcommit(0) < 0) { fprintf(stderr, "tpcommit FAIL: %s\n", tpstrerror(tperrno)); tpabort(0); return 1; }
|
||||
printf(">>> lg_trialbal_batch COMMIT: bizdate=%s 계정=%ld 총대차차액=%ld %s\n",
|
||||
bizdate, rows, net_diff, (net_diff == 0) ? "(균형)" : "(불균형!)");
|
||||
|
||||
tpclose(); tpterm();
|
||||
return 0;
|
||||
}
|
||||
110
app/src/lg/dbio/lg_balance_dbio.pgc
Normal file
110
app/src/lg/dbio/lg_balance_dbio.pgc
Normal file
|
|
@ -0,0 +1,110 @@
|
|||
/*
|
||||
* lg_balance_dbio.pgc - 계정/가맹점 잔액 재계산 및 upsert (part of liblgdbio.a).
|
||||
* ECPG functions; run on the caller's XA branch. No EXEC SQL CONNECT.
|
||||
* 복식부기 원칙: balance = 차변(D) 합 - 대변(C) 합.
|
||||
*/
|
||||
#include <string.h>
|
||||
#include <userlog.h>
|
||||
#include "lg_dbio.h"
|
||||
|
||||
/* 계정과목 x 영업일 잔액을 원장 라인에서 재집계하여 upsert. */
|
||||
int lgdb_recalc_acct_bal(const char *acct, const char *bizdate)
|
||||
{
|
||||
EXEC SQL BEGIN DECLARE SECTION;
|
||||
char h_acct[32], h_bizdate[16];
|
||||
EXEC SQL END DECLARE SECTION;
|
||||
|
||||
strncpy(h_acct, acct, sizeof(h_acct) - 1); h_acct[sizeof(h_acct)-1] = 0;
|
||||
strncpy(h_bizdate, bizdate, sizeof(h_bizdate)-1); h_bizdate[sizeof(h_bizdate)-1] = 0;
|
||||
|
||||
EXEC SQL INSERT INTO lg_account_bal (acct, biz_date, debit_total, credit_total, balance)
|
||||
SELECT :h_acct, :h_bizdate,
|
||||
coalesce(sum(CASE WHEN dc = 'D' THEN amount ELSE 0 END), 0),
|
||||
coalesce(sum(CASE WHEN dc = 'C' THEN amount ELSE 0 END), 0),
|
||||
coalesce(sum(CASE WHEN dc = 'D' THEN amount ELSE -amount END), 0)
|
||||
FROM lg_ledger_line
|
||||
WHERE acct = :h_acct AND biz_date = :h_bizdate
|
||||
ON CONFLICT (acct, biz_date) DO UPDATE
|
||||
SET debit_total = EXCLUDED.debit_total,
|
||||
credit_total = EXCLUDED.credit_total,
|
||||
balance = EXCLUDED.balance,
|
||||
updated_at = now();
|
||||
if (sqlca.sqlcode < 0) {
|
||||
userlog("lgdb_recalc_acct_bal FAIL [%d] %s", sqlca.sqlcode, sqlca.sqlerrm.sqlerrmc);
|
||||
return -1;
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
int lgdb_get_acct_bal(const char *acct, const char *bizdate, long *bal_out)
|
||||
{
|
||||
EXEC SQL BEGIN DECLARE SECTION;
|
||||
long h_bal;
|
||||
char h_acct[32], h_bizdate[16];
|
||||
EXEC SQL END DECLARE SECTION;
|
||||
|
||||
strncpy(h_acct, acct, sizeof(h_acct) - 1); h_acct[sizeof(h_acct)-1] = 0;
|
||||
strncpy(h_bizdate, bizdate, sizeof(h_bizdate)-1); h_bizdate[sizeof(h_bizdate)-1] = 0;
|
||||
|
||||
EXEC SQL SELECT balance INTO :h_bal FROM lg_account_bal
|
||||
WHERE acct = :h_acct AND biz_date = :h_bizdate;
|
||||
if (sqlca.sqlcode < 0 || sqlca.sqlcode == 100) {
|
||||
userlog("lgdb_get_acct_bal FAIL [%d] %s", sqlca.sqlcode, sqlca.sqlerrm.sqlerrmc);
|
||||
return -1;
|
||||
}
|
||||
*bal_out = h_bal;
|
||||
return 0;
|
||||
}
|
||||
|
||||
int lgdb_upsert_acct_bal(const char *acct, const char *bizdate, long debit, long credit)
|
||||
{
|
||||
EXEC SQL BEGIN DECLARE SECTION;
|
||||
long h_debit = debit, h_credit = credit, h_bal = debit - credit;
|
||||
char h_acct[32], h_bizdate[16];
|
||||
EXEC SQL END DECLARE SECTION;
|
||||
|
||||
strncpy(h_acct, acct, sizeof(h_acct) - 1); h_acct[sizeof(h_acct)-1] = 0;
|
||||
strncpy(h_bizdate, bizdate, sizeof(h_bizdate)-1); h_bizdate[sizeof(h_bizdate)-1] = 0;
|
||||
|
||||
EXEC SQL INSERT INTO lg_account_bal (acct, biz_date, debit_total, credit_total, balance)
|
||||
VALUES (:h_acct, :h_bizdate, :h_debit, :h_credit, :h_bal)
|
||||
ON CONFLICT (acct, biz_date) DO UPDATE
|
||||
SET debit_total = lg_account_bal.debit_total + EXCLUDED.debit_total,
|
||||
credit_total = lg_account_bal.credit_total + EXCLUDED.credit_total,
|
||||
balance = lg_account_bal.balance + EXCLUDED.balance,
|
||||
updated_at = now();
|
||||
if (sqlca.sqlcode < 0) {
|
||||
userlog("lgdb_upsert_acct_bal FAIL [%d] %s", sqlca.sqlcode, sqlca.sqlerrm.sqlerrmc);
|
||||
return -1;
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
/* 가맹점 x 영업일 잔액을 원장 라인에서 재집계하여 upsert. */
|
||||
int lgdb_upsert_merch_bal(const char *merch, const char *bizdate)
|
||||
{
|
||||
EXEC SQL BEGIN DECLARE SECTION;
|
||||
char h_merch[64], h_bizdate[16];
|
||||
EXEC SQL END DECLARE SECTION;
|
||||
|
||||
strncpy(h_merch, merch, sizeof(h_merch) - 1); h_merch[sizeof(h_merch)-1] = 0;
|
||||
strncpy(h_bizdate, bizdate, sizeof(h_bizdate)-1); h_bizdate[sizeof(h_bizdate)-1] = 0;
|
||||
|
||||
EXEC SQL INSERT INTO lg_merch_bal (merchant_id, biz_date, debit_total, credit_total, balance)
|
||||
SELECT :h_merch, :h_bizdate,
|
||||
coalesce(sum(CASE WHEN dc = 'D' THEN amount ELSE 0 END), 0),
|
||||
coalesce(sum(CASE WHEN dc = 'C' THEN amount ELSE 0 END), 0),
|
||||
coalesce(sum(CASE WHEN dc = 'D' THEN amount ELSE -amount END), 0)
|
||||
FROM lg_ledger_line
|
||||
WHERE merchant_id = :h_merch AND biz_date = :h_bizdate
|
||||
ON CONFLICT (merchant_id, biz_date) DO UPDATE
|
||||
SET debit_total = EXCLUDED.debit_total,
|
||||
credit_total = EXCLUDED.credit_total,
|
||||
balance = EXCLUDED.balance,
|
||||
updated_at = now();
|
||||
if (sqlca.sqlcode < 0) {
|
||||
userlog("lgdb_upsert_merch_bal FAIL [%d] %s", sqlca.sqlcode, sqlca.sqlerrm.sqlerrmc);
|
||||
return -1;
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
29
app/src/lg/dbio/lg_dbio.h
Normal file
29
app/src/lg/dbio/lg_dbio.h
Normal file
|
|
@ -0,0 +1,29 @@
|
|||
/*
|
||||
* lg_dbio.h - lg 모듈 (원장/복식부기) DB 접근 계층 (liblgdbio.a).
|
||||
* ECPG (EXEC SQL) functions compiled into a static library and linked into
|
||||
* lg_svr and the lg batches. They run on the caller's XA branch/connection
|
||||
* (no EXEC SQL CONNECT); sqlca is the shared per-thread ECPG SQLCA.
|
||||
* Return convention: 0 = ok, -1 = SQL error (caller may also inspect sqlca).
|
||||
*/
|
||||
#ifndef LG_DBIO_H
|
||||
#define LG_DBIO_H
|
||||
|
||||
/* 전표 (voucher) */
|
||||
long lgdb_next_voucher_id(void);
|
||||
long lgdb_next_line_id(void);
|
||||
int lgdb_insert_voucher(long vid, const char *vtype, long ref_id,
|
||||
const char *status, const char *bizdate, const char *memo);
|
||||
int lgdb_update_voucher_status(long vid, const char *status);
|
||||
int lgdb_get_voucher_status(long vid, char *out /* >= 2 bytes */);
|
||||
|
||||
/* 원장 라인 (ledger_line) - 차변(D)/대변(C) 분개 */
|
||||
int lgdb_insert_line(long vid, const char *acct, const char *dc, long amount,
|
||||
const char *merch, const char *bizdate, const char *status);
|
||||
|
||||
/* 계정/가맹점 잔액 (balance) */
|
||||
int lgdb_recalc_acct_bal(const char *acct, const char *bizdate);
|
||||
int lgdb_get_acct_bal(const char *acct, const char *bizdate, long *bal_out);
|
||||
int lgdb_upsert_acct_bal(const char *acct, const char *bizdate, long debit, long credit);
|
||||
int lgdb_upsert_merch_bal(const char *merch, const char *bizdate);
|
||||
|
||||
#endif /* LG_DBIO_H */
|
||||
119
app/src/lg/dbio/lg_ledger_dbio.pgc
Normal file
119
app/src/lg/dbio/lg_ledger_dbio.pgc
Normal file
|
|
@ -0,0 +1,119 @@
|
|||
/*
|
||||
* lg_ledger_dbio.pgc - 전표/원장라인 DB 접근 (part of liblgdbio.a).
|
||||
* ECPG functions; run on the caller's XA branch. No EXEC SQL CONNECT.
|
||||
*/
|
||||
#include <string.h>
|
||||
#include <userlog.h>
|
||||
#include "lg_dbio.h"
|
||||
|
||||
long lgdb_next_voucher_id(void)
|
||||
{
|
||||
EXEC SQL BEGIN DECLARE SECTION;
|
||||
long h_id;
|
||||
EXEC SQL END DECLARE SECTION;
|
||||
|
||||
EXEC SQL SELECT nextval('lg_voucher_seq') INTO :h_id;
|
||||
if (sqlca.sqlcode < 0) {
|
||||
userlog("lgdb_next_voucher_id FAIL [%d] %s", sqlca.sqlcode, sqlca.sqlerrm.sqlerrmc);
|
||||
return -1;
|
||||
}
|
||||
return h_id;
|
||||
}
|
||||
|
||||
long lgdb_next_line_id(void)
|
||||
{
|
||||
EXEC SQL BEGIN DECLARE SECTION;
|
||||
long h_id;
|
||||
EXEC SQL END DECLARE SECTION;
|
||||
|
||||
EXEC SQL SELECT nextval('lg_line_seq') INTO :h_id;
|
||||
if (sqlca.sqlcode < 0) {
|
||||
userlog("lgdb_next_line_id FAIL [%d] %s", sqlca.sqlcode, sqlca.sqlerrm.sqlerrmc);
|
||||
return -1;
|
||||
}
|
||||
return h_id;
|
||||
}
|
||||
|
||||
int lgdb_insert_voucher(long vid, const char *vtype, long ref_id,
|
||||
const char *status, const char *bizdate, const char *memo)
|
||||
{
|
||||
EXEC SQL BEGIN DECLARE SECTION;
|
||||
long h_vid = vid, h_ref = ref_id;
|
||||
char h_type[16], h_status[8], h_bizdate[16], h_memo[128];
|
||||
EXEC SQL END DECLARE SECTION;
|
||||
|
||||
strncpy(h_type, vtype, sizeof(h_type) - 1); h_type[sizeof(h_type)-1] = 0;
|
||||
strncpy(h_status, status, sizeof(h_status) - 1); h_status[sizeof(h_status)-1] = 0;
|
||||
strncpy(h_bizdate, bizdate, sizeof(h_bizdate)-1); h_bizdate[sizeof(h_bizdate)-1] = 0;
|
||||
strncpy(h_memo, memo, sizeof(h_memo) - 1); h_memo[sizeof(h_memo)-1] = 0;
|
||||
|
||||
EXEC SQL INSERT INTO lg_voucher (voucher_id, voucher_type, ref_id, biz_date, status, memo)
|
||||
VALUES (:h_vid, :h_type, :h_ref, :h_bizdate, :h_status, :h_memo);
|
||||
if (sqlca.sqlcode < 0) {
|
||||
userlog("lgdb_insert_voucher FAIL [%d] %s", sqlca.sqlcode, sqlca.sqlerrm.sqlerrmc);
|
||||
return -1;
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
int lgdb_update_voucher_status(long vid, const char *status)
|
||||
{
|
||||
EXEC SQL BEGIN DECLARE SECTION;
|
||||
long h_vid = vid;
|
||||
char h_status[8];
|
||||
EXEC SQL END DECLARE SECTION;
|
||||
|
||||
strncpy(h_status, status, sizeof(h_status) - 1); h_status[sizeof(h_status)-1] = 0;
|
||||
|
||||
EXEC SQL UPDATE lg_voucher SET status = :h_status, updated_at = now()
|
||||
WHERE voucher_id = :h_vid;
|
||||
if (sqlca.sqlcode < 0 || sqlca.sqlcode == 100) {
|
||||
userlog("lgdb_update_voucher_status FAIL [%d] %s", sqlca.sqlcode, sqlca.sqlerrm.sqlerrmc);
|
||||
return -1;
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
int lgdb_get_voucher_status(long vid, char *out)
|
||||
{
|
||||
EXEC SQL BEGIN DECLARE SECTION;
|
||||
long h_vid = vid;
|
||||
char h_status[8];
|
||||
EXEC SQL END DECLARE SECTION;
|
||||
|
||||
EXEC SQL SELECT status INTO :h_status FROM lg_voucher WHERE voucher_id = :h_vid;
|
||||
if (sqlca.sqlcode < 0 || sqlca.sqlcode == 100) {
|
||||
userlog("lgdb_get_voucher_status FAIL [%d] %s", sqlca.sqlcode, sqlca.sqlerrm.sqlerrmc);
|
||||
return -1;
|
||||
}
|
||||
out[0] = h_status[0];
|
||||
out[1] = 0;
|
||||
return 0;
|
||||
}
|
||||
|
||||
int lgdb_insert_line(long vid, const char *acct, const char *dc, long amount,
|
||||
const char *merch, const char *bizdate, const char *status)
|
||||
{
|
||||
EXEC SQL BEGIN DECLARE SECTION;
|
||||
long h_id, h_vid = vid, h_amount = amount;
|
||||
char h_acct[32], h_dc[4], h_merch[64], h_bizdate[16], h_status[16];
|
||||
EXEC SQL END DECLARE SECTION;
|
||||
|
||||
strncpy(h_acct, acct, sizeof(h_acct) - 1); h_acct[sizeof(h_acct)-1] = 0;
|
||||
strncpy(h_dc, dc, sizeof(h_dc) - 1); h_dc[sizeof(h_dc)-1] = 0;
|
||||
strncpy(h_merch, merch, sizeof(h_merch) - 1); h_merch[sizeof(h_merch)-1] = 0;
|
||||
strncpy(h_bizdate, bizdate, sizeof(h_bizdate)-1); h_bizdate[sizeof(h_bizdate)-1] = 0;
|
||||
strncpy(h_status, status, sizeof(h_status) - 1); h_status[sizeof(h_status)-1] = 0;
|
||||
|
||||
EXEC SQL SELECT nextval('lg_line_seq') INTO :h_id;
|
||||
if (sqlca.sqlcode < 0) return -1;
|
||||
|
||||
EXEC SQL INSERT INTO lg_ledger_line
|
||||
(line_id, voucher_id, acct, dc, amount, merchant_id, biz_date, status)
|
||||
VALUES (:h_id, :h_vid, :h_acct, :h_dc, :h_amount, :h_merch, :h_bizdate, :h_status);
|
||||
if (sqlca.sqlcode < 0) {
|
||||
userlog("lgdb_insert_line FAIL [%d] %s", sqlca.sqlcode, sqlca.sqlerrm.sqlerrmc);
|
||||
return -1;
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
703
app/src/lg/lg_svr.pgc
Normal file
703
app/src/lg/lg_svr.pgc
Normal file
|
|
@ -0,0 +1,703 @@
|
|||
/*
|
||||
* lg_svr.pgc - lg (원장/ledger) MODULE SERVER.
|
||||
*
|
||||
* ONE binary that tpadvertise()s ALL ~30 online lg services (the real Tuxedo
|
||||
* "one module server, many services" pattern). Every service is a distinct 원장
|
||||
* (복식부기) operation with genuine EXEC SQL (directly, or via the linked
|
||||
* liblgdbio.a dbio layer) and common math from libacqcommon.a.
|
||||
*
|
||||
* 복식부기 원칙: 모든 전표는 차변(D) 합 = 대변(C) 합. 계정잔액 balance = 차변-대변.
|
||||
*
|
||||
* XA: the connection is opened once by tpopen() (ECPG XA switch libndrxxaecpg.so);
|
||||
* there is NO EXEC SQL CONNECT. Each tpcall runs on this process's XA branch.
|
||||
*/
|
||||
#include <stdio.h>
|
||||
#include <stdlib.h>
|
||||
#include <string.h>
|
||||
#include <atmi.h>
|
||||
#include <ubf.h>
|
||||
#include <userlog.h>
|
||||
#include <ndebug.h>
|
||||
#include "acq.fd.h"
|
||||
#include "lg_dbio.h"
|
||||
#include "acq_common.h"
|
||||
|
||||
/* ----- small UBF helpers ------------------------------------------------- */
|
||||
static long getl(UBFH *b, BFLDID f) { long v = 0; Bget(b, f, 0, (char *)&v, 0L); return v; }
|
||||
static void gets_(UBFH *b, BFLDID f, char *out, int cap)
|
||||
{ BFLDLEN l = cap; out[0] = 0; Bget(b, f, 0, out, &l); }
|
||||
static void setl(UBFH *b, BFLDID f, long v) { Bchg(b, f, 0, (char *)&v, 0L); }
|
||||
|
||||
#define FAIL(b) do { tpreturn(TPFAIL, 0, (char *)(b), 0L, 0L); return; } while (0)
|
||||
#define OK(b) do { tpreturn(TPSUCCESS, 0, (char *)(b), 0L, 0L); return; } while (0)
|
||||
|
||||
/* ----- service prototypes ------------------------------------------------ */
|
||||
void LG_POST(TPSVCINFO *p); void LG_CREDIT(TPSVCINFO *p);
|
||||
void LG_DEBIT(TPSVCINFO *p); void LG_DOUBLE(TPSVCINFO *p);
|
||||
void LG_BALCHK(TPSVCINFO *p); void LG_REVERSE(TPSVCINFO *p);
|
||||
void LG_CARRY(TPSVCINFO *p); void LG_CLOSE_LINK(TPSVCINFO *p);
|
||||
void LG_ACCTSUM(TPSVCINFO *p); void LG_RECALC(TPSVCINFO *p);
|
||||
void LG_UNSETTLED(TPSVCINFO *p); void LG_STATS(TPSVCINFO *p);
|
||||
void LG_DIFFCHK(TPSVCINFO *p); void LG_CORRECT(TPSVCINFO *p);
|
||||
void LG_INQUIRY(TPSVCINFO *p); void LG_TRIALBAL(TPSVCINFO *p);
|
||||
void LG_JOURNAL(TPSVCINFO *p); void LG_VOUCHER(TPSVCINFO *p);
|
||||
void LG_APPLY(TPSVCINFO *p); void LG_ROLLFWD(TPSVCINFO *p);
|
||||
void LG_MERCHBAL(TPSVCINFO *p); void LG_DAILYSUM(TPSVCINFO *p);
|
||||
void LG_MONTHLYSUM(TPSVCINFO *p); void LG_ADJUST(TPSVCINFO *p);
|
||||
void LG_TRANSFER(TPSVCINFO *p); void LG_SETTLEPOST(TPSVCINFO *p);
|
||||
void LG_ACQPOST(TPSVCINFO *p); void LG_FEEPOST(TPSVCINFO *p);
|
||||
void LG_STATUS(TPSVCINFO *p); void LG_CONFIRM(TPSVCINFO *p);
|
||||
|
||||
/* ----- advertise table (this is what "scales to 30" cleanly) ------------- */
|
||||
static struct { const char *name; void (*fn)(TPSVCINFO *); } SVCS[] = {
|
||||
{"LG_POST", LG_POST}, {"LG_CREDIT", LG_CREDIT},
|
||||
{"LG_DEBIT", LG_DEBIT}, {"LG_DOUBLE", LG_DOUBLE},
|
||||
{"LG_BALCHK", LG_BALCHK}, {"LG_REVERSE", LG_REVERSE},
|
||||
{"LG_CARRY", LG_CARRY}, {"LG_CLOSE_LINK", LG_CLOSE_LINK},
|
||||
{"LG_ACCTSUM", LG_ACCTSUM}, {"LG_RECALC", LG_RECALC},
|
||||
{"LG_UNSETTLED", LG_UNSETTLED}, {"LG_STATS", LG_STATS},
|
||||
{"LG_DIFFCHK", LG_DIFFCHK}, {"LG_CORRECT", LG_CORRECT},
|
||||
{"LG_INQUIRY", LG_INQUIRY}, {"LG_TRIALBAL", LG_TRIALBAL},
|
||||
{"LG_JOURNAL", LG_JOURNAL}, {"LG_VOUCHER", LG_VOUCHER},
|
||||
{"LG_APPLY", LG_APPLY}, {"LG_ROLLFWD", LG_ROLLFWD},
|
||||
{"LG_MERCHBAL", LG_MERCHBAL}, {"LG_DAILYSUM", LG_DAILYSUM},
|
||||
{"LG_MONTHLYSUM", LG_MONTHLYSUM},{"LG_ADJUST", LG_ADJUST},
|
||||
{"LG_TRANSFER", LG_TRANSFER}, {"LG_SETTLEPOST", LG_SETTLEPOST},
|
||||
{"LG_ACQPOST", LG_ACQPOST}, {"LG_FEEPOST", LG_FEEPOST},
|
||||
{"LG_STATUS", LG_STATUS}, {"LG_CONFIRM", LG_CONFIRM},
|
||||
{NULL, NULL}
|
||||
};
|
||||
|
||||
int tpsvrinit(int argc, char **argv)
|
||||
{
|
||||
int i;
|
||||
if (tpopen() < 0) {
|
||||
userlog("lg_svr: tpopen FAIL: %s", tpstrerror(tperrno));
|
||||
return -1;
|
||||
}
|
||||
for (i = 0; SVCS[i].name != NULL; i++) {
|
||||
if (tpadvertise((char *)SVCS[i].name, SVCS[i].fn) < 0) {
|
||||
userlog("lg_svr: tpadvertise(%s) FAIL: %s", SVCS[i].name, tpstrerror(tperrno));
|
||||
return -1;
|
||||
}
|
||||
}
|
||||
userlog("lg_svr: %d lg 서비스 광고 완료, RM 개방", i);
|
||||
return 0;
|
||||
}
|
||||
|
||||
void tpsvrdone(void)
|
||||
{
|
||||
tpclose();
|
||||
userlog("lg_svr: tpsvrdone");
|
||||
}
|
||||
|
||||
/* ========================================================================= */
|
||||
/* 1. LG_POST - 전표 작성(draft) + 차변/대변 균형 분개 2줄. */
|
||||
/* ========================================================================= */
|
||||
void LG_POST(TPSVCINFO *p)
|
||||
{
|
||||
UBFH *b = (UBFH *)p->data;
|
||||
char dacct[32], cacct[32], bizdate[16], merch[64];
|
||||
long vid, ref, amount;
|
||||
|
||||
gets_(b, T_KEY1, dacct, sizeof(dacct)); if (dacct[0] == 0) strcpy(dacct, "RECV");
|
||||
gets_(b, T_KEY2, cacct, sizeof(cacct)); if (cacct[0] == 0) strcpy(cacct, "MERCH_PAY");
|
||||
gets_(b, T_BIZDATE, bizdate, sizeof(bizdate));
|
||||
gets_(b, T_MERCHANT, merch, sizeof(merch));
|
||||
ref = getl(b, T_PURCHASE_ID);
|
||||
amount = getl(b, T_AMOUNT);
|
||||
|
||||
vid = lgdb_next_voucher_id();
|
||||
if (vid < 0) FAIL(b);
|
||||
if (lgdb_insert_voucher(vid, "POST", ref, "D", bizdate, "전표작성") < 0) FAIL(b);
|
||||
if (lgdb_insert_line(vid, dacct, "D", amount, merch, bizdate, "posted") < 0) FAIL(b);
|
||||
if (lgdb_insert_line(vid, cacct, "C", amount, merch, bizdate, "posted") < 0) FAIL(b);
|
||||
|
||||
setl(b, T_ID1, vid);
|
||||
Bchg(b, T_STATUS, 0, "D", 0L);
|
||||
userlog("LG_POST vid=%ld 차변=%s 대변=%s 금액=%ld", vid, dacct, cacct, amount);
|
||||
OK(b);
|
||||
}
|
||||
|
||||
/* 2. LG_CREDIT - 기존 전표에 대변(C) 분개 1줄 추가. */
|
||||
void LG_CREDIT(TPSVCINFO *p)
|
||||
{
|
||||
UBFH *b = (UBFH *)p->data;
|
||||
char acct[32], bizdate[16], merch[64];
|
||||
long vid = getl(b, T_ID1), amount = getl(b, T_AMOUNT);
|
||||
gets_(b, T_KEY1, acct, sizeof(acct)); if (acct[0] == 0) strcpy(acct, "MERCH_PAY");
|
||||
gets_(b, T_BIZDATE, bizdate, sizeof(bizdate));
|
||||
gets_(b, T_MERCHANT, merch, sizeof(merch));
|
||||
if (lgdb_insert_line(vid, acct, "C", amount, merch, bizdate, "posted") < 0) FAIL(b);
|
||||
userlog("LG_CREDIT vid=%ld 대변 %s %ld", vid, acct, amount);
|
||||
OK(b);
|
||||
}
|
||||
|
||||
/* 3. LG_DEBIT - 기존 전표에 차변(D) 분개 1줄 추가. */
|
||||
void LG_DEBIT(TPSVCINFO *p)
|
||||
{
|
||||
UBFH *b = (UBFH *)p->data;
|
||||
char acct[32], bizdate[16], merch[64];
|
||||
long vid = getl(b, T_ID1), amount = getl(b, T_AMOUNT);
|
||||
gets_(b, T_KEY1, acct, sizeof(acct)); if (acct[0] == 0) strcpy(acct, "RECV");
|
||||
gets_(b, T_BIZDATE, bizdate, sizeof(bizdate));
|
||||
gets_(b, T_MERCHANT, merch, sizeof(merch));
|
||||
if (lgdb_insert_line(vid, acct, "D", amount, merch, bizdate, "posted") < 0) FAIL(b);
|
||||
userlog("LG_DEBIT vid=%ld 차변 %s %ld", vid, acct, amount);
|
||||
OK(b);
|
||||
}
|
||||
|
||||
/* 4. LG_DOUBLE - 신규 전표 즉시 전기(posted): 차변/대변 + 양 계정잔액 반영. */
|
||||
void LG_DOUBLE(TPSVCINFO *p)
|
||||
{
|
||||
UBFH *b = (UBFH *)p->data;
|
||||
char dacct[32], cacct[32], bizdate[16], merch[64];
|
||||
long vid, amount;
|
||||
|
||||
gets_(b, T_KEY1, dacct, sizeof(dacct)); if (dacct[0] == 0) strcpy(dacct, "RECV");
|
||||
gets_(b, T_KEY2, cacct, sizeof(cacct)); if (cacct[0] == 0) strcpy(cacct, "MERCH_PAY");
|
||||
gets_(b, T_BIZDATE, bizdate, sizeof(bizdate));
|
||||
gets_(b, T_MERCHANT, merch, sizeof(merch));
|
||||
amount = getl(b, T_AMOUNT);
|
||||
|
||||
vid = lgdb_next_voucher_id();
|
||||
if (vid < 0) FAIL(b);
|
||||
if (lgdb_insert_voucher(vid, "DOUBLE", getl(b, T_PURCHASE_ID), "P", bizdate, "복식전기") < 0) FAIL(b);
|
||||
if (lgdb_insert_line(vid, dacct, "D", amount, merch, bizdate, "posted") < 0) FAIL(b);
|
||||
if (lgdb_insert_line(vid, cacct, "C", amount, merch, bizdate, "posted") < 0) FAIL(b);
|
||||
if (lgdb_upsert_acct_bal(dacct, bizdate, amount, 0) < 0) FAIL(b);
|
||||
if (lgdb_upsert_acct_bal(cacct, bizdate, 0, amount) < 0) FAIL(b);
|
||||
|
||||
setl(b, T_ID1, vid);
|
||||
Bchg(b, T_STATUS, 0, "P", 0L);
|
||||
OK(b);
|
||||
}
|
||||
|
||||
/* 5. LG_BALCHK - 전표 단위 대차 균형 점검 (차변합 vs 대변합). */
|
||||
void LG_BALCHK(TPSVCINFO *p)
|
||||
{
|
||||
UBFH *b = (UBFH *)p->data;
|
||||
EXEC SQL BEGIN DECLARE SECTION;
|
||||
long h_vid, h_dr, h_cr;
|
||||
EXEC SQL END DECLARE SECTION;
|
||||
h_vid = getl(b, T_ID1);
|
||||
EXEC SQL SELECT coalesce(sum(CASE WHEN dc = 'D' THEN amount ELSE 0 END), 0),
|
||||
coalesce(sum(CASE WHEN dc = 'C' THEN amount ELSE 0 END), 0)
|
||||
INTO :h_dr, :h_cr FROM lg_ledger_line WHERE voucher_id = :h_vid;
|
||||
if (sqlca.sqlcode < 0) FAIL(b);
|
||||
setl(b, T_AMT2, h_dr);
|
||||
setl(b, T_AMT3, h_cr);
|
||||
setl(b, T_RC, (h_dr == h_cr) ? 0 : 1);
|
||||
userlog("LG_BALCHK vid=%ld 차변=%ld 대변=%ld %s", h_vid, h_dr, h_cr,
|
||||
(h_dr == h_cr) ? "균형" : "불균형");
|
||||
OK(b);
|
||||
}
|
||||
|
||||
/* 6. LG_REVERSE - 역분개 전표: 원전표 라인의 차/대를 뒤집어 신규 전표에 복사. */
|
||||
void LG_REVERSE(TPSVCINFO *p)
|
||||
{
|
||||
UBFH *b = (UBFH *)p->data;
|
||||
char bizdate[16];
|
||||
EXEC SQL BEGIN DECLARE SECTION;
|
||||
long h_src, h_new;
|
||||
char h_bizdate[16];
|
||||
EXEC SQL END DECLARE SECTION;
|
||||
|
||||
h_src = getl(b, T_ID1);
|
||||
gets_(b, T_BIZDATE, bizdate, sizeof(bizdate));
|
||||
strncpy(h_bizdate, bizdate, sizeof(h_bizdate)-1); h_bizdate[sizeof(h_bizdate)-1] = 0;
|
||||
|
||||
h_new = lgdb_next_voucher_id();
|
||||
if (h_new < 0) FAIL(b);
|
||||
if (lgdb_insert_voucher(h_new, "REVERSE", h_src, "P", bizdate, "역분개") < 0) FAIL(b);
|
||||
|
||||
EXEC SQL INSERT INTO lg_ledger_line
|
||||
(line_id, voucher_id, acct, dc, amount, merchant_id, biz_date, status)
|
||||
SELECT nextval('lg_line_seq'), :h_new, acct,
|
||||
CASE WHEN dc = 'D' THEN 'C' ELSE 'D' END,
|
||||
amount, merchant_id, :h_bizdate, 'posted'
|
||||
FROM lg_ledger_line WHERE voucher_id = :h_src;
|
||||
if (sqlca.sqlcode < 0) { userlog("LG_REVERSE FAIL [%d] %s", sqlca.sqlcode, sqlca.sqlerrm.sqlerrmc); FAIL(b); }
|
||||
setl(b, T_ID2, h_new);
|
||||
setl(b, T_COUNT, sqlca.sqlerrd[2]);
|
||||
OK(b);
|
||||
}
|
||||
|
||||
/* 7. LG_CARRY - 이월: 당일 계정잔액을 익영업일 개시잔액으로 이월(upsert). */
|
||||
void LG_CARRY(TPSVCINFO *p)
|
||||
{
|
||||
UBFH *b = (UBFH *)p->data;
|
||||
char bizdate[16], nextday[16];
|
||||
EXEC SQL BEGIN DECLARE SECTION;
|
||||
char h_bizdate[16], h_next[16];
|
||||
long h_cnt;
|
||||
EXEC SQL END DECLARE SECTION;
|
||||
|
||||
gets_(b, T_BIZDATE, bizdate, sizeof(bizdate));
|
||||
if (acq_next_bizday(bizdate, nextday) < 0) strcpy(nextday, bizdate);
|
||||
strncpy(h_bizdate, bizdate, sizeof(h_bizdate)-1); h_bizdate[sizeof(h_bizdate)-1] = 0;
|
||||
strncpy(h_next, nextday, sizeof(h_next)-1); h_next[sizeof(h_next)-1] = 0;
|
||||
|
||||
EXEC SQL INSERT INTO lg_account_bal (acct, biz_date, debit_total, credit_total, balance)
|
||||
SELECT acct, :h_next, debit_total, credit_total, balance
|
||||
FROM lg_account_bal WHERE biz_date = :h_bizdate
|
||||
ON CONFLICT (acct, biz_date) DO UPDATE
|
||||
SET debit_total = EXCLUDED.debit_total,
|
||||
credit_total = EXCLUDED.credit_total,
|
||||
balance = EXCLUDED.balance,
|
||||
updated_at = now();
|
||||
if (sqlca.sqlcode < 0) FAIL(b);
|
||||
h_cnt = sqlca.sqlerrd[2];
|
||||
setl(b, T_COUNT, h_cnt);
|
||||
userlog("LG_CARRY %s -> %s 계정 %ld건 이월", bizdate, nextday, h_cnt);
|
||||
OK(b);
|
||||
}
|
||||
|
||||
/* 8. LG_CLOSE_LINK - 마감연계: 전표 라인 closed + 전표 상태 S(마감연계). */
|
||||
void LG_CLOSE_LINK(TPSVCINFO *p)
|
||||
{
|
||||
UBFH *b = (UBFH *)p->data;
|
||||
EXEC SQL BEGIN DECLARE SECTION;
|
||||
long h_vid;
|
||||
EXEC SQL END DECLARE SECTION;
|
||||
h_vid = getl(b, T_ID1);
|
||||
EXEC SQL UPDATE lg_ledger_line SET status = 'closed'
|
||||
WHERE voucher_id = :h_vid AND status = 'posted';
|
||||
if (sqlca.sqlcode < 0) FAIL(b);
|
||||
setl(b, T_COUNT, sqlca.sqlerrd[2]);
|
||||
if (lgdb_update_voucher_status(h_vid, "S") < 0) FAIL(b);
|
||||
Bchg(b, T_STATUS, 0, "S", 0L);
|
||||
OK(b);
|
||||
}
|
||||
|
||||
/* 9. LG_ACCTSUM - 계정 잔액 재집계(dbio) 후 잔액 반환. */
|
||||
void LG_ACCTSUM(TPSVCINFO *p)
|
||||
{
|
||||
UBFH *b = (UBFH *)p->data;
|
||||
char acct[32], bizdate[16];
|
||||
long bal = 0;
|
||||
gets_(b, T_KEY1, acct, sizeof(acct)); if (acct[0] == 0) strcpy(acct, "RECV");
|
||||
gets_(b, T_BIZDATE, bizdate, sizeof(bizdate));
|
||||
if (lgdb_recalc_acct_bal(acct, bizdate) < 0) FAIL(b);
|
||||
if (lgdb_get_acct_bal(acct, bizdate, &bal) < 0) FAIL(b);
|
||||
setl(b, T_NET, bal);
|
||||
OK(b);
|
||||
}
|
||||
|
||||
/* 10. LG_RECALC - 계정 잔액 원장 라인 기준 재계산(dbio). */
|
||||
void LG_RECALC(TPSVCINFO *p)
|
||||
{
|
||||
UBFH *b = (UBFH *)p->data;
|
||||
char acct[32], bizdate[16];
|
||||
gets_(b, T_KEY1, acct, sizeof(acct)); if (acct[0] == 0) strcpy(acct, "RECV");
|
||||
gets_(b, T_BIZDATE, bizdate, sizeof(bizdate));
|
||||
if (lgdb_recalc_acct_bal(acct, bizdate) < 0) FAIL(b);
|
||||
OK(b);
|
||||
}
|
||||
|
||||
/* 11. LG_UNSETTLED - 미마감(status=posted) 원장 라인 건수. */
|
||||
void LG_UNSETTLED(TPSVCINFO *p)
|
||||
{
|
||||
UBFH *b = (UBFH *)p->data;
|
||||
char bizdate[16];
|
||||
EXEC SQL BEGIN DECLARE SECTION;
|
||||
long h_cnt;
|
||||
char h_bizdate[16];
|
||||
EXEC SQL END DECLARE SECTION;
|
||||
gets_(b, T_BIZDATE, bizdate, sizeof(bizdate));
|
||||
strncpy(h_bizdate, bizdate, sizeof(h_bizdate)-1); h_bizdate[sizeof(h_bizdate)-1] = 0;
|
||||
EXEC SQL SELECT count(*) INTO :h_cnt FROM lg_ledger_line
|
||||
WHERE biz_date = :h_bizdate AND status = 'posted';
|
||||
if (sqlca.sqlcode < 0) FAIL(b);
|
||||
setl(b, T_COUNT, h_cnt);
|
||||
OK(b);
|
||||
}
|
||||
|
||||
/* 12. LG_STATS - 당일 전표/원장 통계(전표수, 총차변). */
|
||||
void LG_STATS(TPSVCINFO *p)
|
||||
{
|
||||
UBFH *b = (UBFH *)p->data;
|
||||
char bizdate[16];
|
||||
EXEC SQL BEGIN DECLARE SECTION;
|
||||
long h_vc, h_dr;
|
||||
char h_bizdate[16];
|
||||
EXEC SQL END DECLARE SECTION;
|
||||
gets_(b, T_BIZDATE, bizdate, sizeof(bizdate));
|
||||
strncpy(h_bizdate, bizdate, sizeof(h_bizdate)-1); h_bizdate[sizeof(h_bizdate)-1] = 0;
|
||||
EXEC SQL SELECT count(DISTINCT voucher_id),
|
||||
coalesce(sum(CASE WHEN dc = 'D' THEN amount ELSE 0 END), 0)
|
||||
INTO :h_vc, :h_dr FROM lg_ledger_line WHERE biz_date = :h_bizdate;
|
||||
if (sqlca.sqlcode < 0) FAIL(b);
|
||||
setl(b, T_COUNT, h_vc);
|
||||
setl(b, T_GROSS, h_dr);
|
||||
OK(b);
|
||||
}
|
||||
|
||||
/* 13. LG_DIFFCHK - 당일 전체 대차 차액 점검(총차변-총대변). */
|
||||
void LG_DIFFCHK(TPSVCINFO *p)
|
||||
{
|
||||
UBFH *b = (UBFH *)p->data;
|
||||
char bizdate[16];
|
||||
EXEC SQL BEGIN DECLARE SECTION;
|
||||
long h_dr, h_cr;
|
||||
char h_bizdate[16];
|
||||
EXEC SQL END DECLARE SECTION;
|
||||
gets_(b, T_BIZDATE, bizdate, sizeof(bizdate));
|
||||
strncpy(h_bizdate, bizdate, sizeof(h_bizdate)-1); h_bizdate[sizeof(h_bizdate)-1] = 0;
|
||||
EXEC SQL SELECT coalesce(sum(CASE WHEN dc = 'D' THEN amount ELSE 0 END), 0),
|
||||
coalesce(sum(CASE WHEN dc = 'C' THEN amount ELSE 0 END), 0)
|
||||
INTO :h_dr, :h_cr FROM lg_ledger_line WHERE biz_date = :h_bizdate;
|
||||
if (sqlca.sqlcode < 0) FAIL(b);
|
||||
setl(b, T_AMT2, h_dr);
|
||||
setl(b, T_AMT3, h_cr);
|
||||
setl(b, T_DELTA, h_dr - h_cr);
|
||||
setl(b, T_RC, (h_dr == h_cr) ? 0 : 1);
|
||||
OK(b);
|
||||
}
|
||||
|
||||
/* 14. LG_CORRECT - 원장 라인 금액 정정. */
|
||||
void LG_CORRECT(TPSVCINFO *p)
|
||||
{
|
||||
UBFH *b = (UBFH *)p->data;
|
||||
EXEC SQL BEGIN DECLARE SECTION;
|
||||
long h_line, h_amount;
|
||||
EXEC SQL END DECLARE SECTION;
|
||||
h_line = getl(b, T_ID1);
|
||||
h_amount = getl(b, T_AMOUNT);
|
||||
EXEC SQL UPDATE lg_ledger_line SET amount = :h_amount WHERE line_id = :h_line;
|
||||
if (sqlca.sqlcode < 0 || sqlca.sqlcode == 100) FAIL(b);
|
||||
OK(b);
|
||||
}
|
||||
|
||||
/* 15. LG_INQUIRY - 전표 상태 조회(dbio). */
|
||||
void LG_INQUIRY(TPSVCINFO *p)
|
||||
{
|
||||
UBFH *b = (UBFH *)p->data;
|
||||
long vid = getl(b, T_ID1);
|
||||
char st[4];
|
||||
if (lgdb_get_voucher_status(vid, st) < 0) FAIL(b);
|
||||
Bchg(b, T_STATUS, 0, st, 0L);
|
||||
OK(b);
|
||||
}
|
||||
|
||||
/* 16. LG_TRIALBAL - 계정별 시산표 산출 후 lg_trial_bal upsert. */
|
||||
void LG_TRIALBAL(TPSVCINFO *p)
|
||||
{
|
||||
UBFH *b = (UBFH *)p->data;
|
||||
char acct[32], bizdate[16];
|
||||
EXEC SQL BEGIN DECLARE SECTION;
|
||||
long h_dr, h_cr;
|
||||
char h_acct[32], h_bizdate[16];
|
||||
EXEC SQL END DECLARE SECTION;
|
||||
gets_(b, T_KEY1, acct, sizeof(acct)); if (acct[0] == 0) strcpy(acct, "RECV");
|
||||
gets_(b, T_BIZDATE, bizdate, sizeof(bizdate));
|
||||
strncpy(h_acct, acct, sizeof(h_acct)-1); h_acct[sizeof(h_acct)-1] = 0;
|
||||
strncpy(h_bizdate, bizdate, sizeof(h_bizdate)-1); h_bizdate[sizeof(h_bizdate)-1] = 0;
|
||||
|
||||
EXEC SQL SELECT coalesce(sum(CASE WHEN dc = 'D' THEN amount ELSE 0 END), 0),
|
||||
coalesce(sum(CASE WHEN dc = 'C' THEN amount ELSE 0 END), 0)
|
||||
INTO :h_dr, :h_cr FROM lg_ledger_line
|
||||
WHERE acct = :h_acct AND biz_date = :h_bizdate;
|
||||
if (sqlca.sqlcode < 0) FAIL(b);
|
||||
EXEC SQL INSERT INTO lg_trial_bal (acct, biz_date, debit_total, credit_total, diff)
|
||||
VALUES (:h_acct, :h_bizdate, :h_dr, :h_cr, :h_dr - :h_cr)
|
||||
ON CONFLICT (acct, biz_date) DO UPDATE
|
||||
SET debit_total = EXCLUDED.debit_total, credit_total = EXCLUDED.credit_total,
|
||||
diff = EXCLUDED.diff, updated_at = now();
|
||||
if (sqlca.sqlcode < 0) FAIL(b);
|
||||
setl(b, T_AMT2, h_dr); setl(b, T_AMT3, h_cr); setl(b, T_DELTA, h_dr - h_cr);
|
||||
OK(b);
|
||||
}
|
||||
|
||||
/* 17. LG_JOURNAL - 당일 분개(원장 라인) 건수 조회. */
|
||||
void LG_JOURNAL(TPSVCINFO *p)
|
||||
{
|
||||
UBFH *b = (UBFH *)p->data;
|
||||
char bizdate[16];
|
||||
EXEC SQL BEGIN DECLARE SECTION;
|
||||
long h_cnt;
|
||||
char h_bizdate[16];
|
||||
EXEC SQL END DECLARE SECTION;
|
||||
gets_(b, T_BIZDATE, bizdate, sizeof(bizdate));
|
||||
strncpy(h_bizdate, bizdate, sizeof(h_bizdate)-1); h_bizdate[sizeof(h_bizdate)-1] = 0;
|
||||
EXEC SQL SELECT count(*) INTO :h_cnt FROM lg_ledger_line WHERE biz_date = :h_bizdate;
|
||||
if (sqlca.sqlcode < 0) FAIL(b);
|
||||
setl(b, T_COUNT, h_cnt);
|
||||
OK(b);
|
||||
}
|
||||
|
||||
/* 18. LG_VOUCHER - 빈 전표 헤더 생성(작성 상태). */
|
||||
void LG_VOUCHER(TPSVCINFO *p)
|
||||
{
|
||||
UBFH *b = (UBFH *)p->data;
|
||||
char vtype[16], bizdate[16], memo[128];
|
||||
long vid, ref = getl(b, T_PURCHASE_ID);
|
||||
gets_(b, T_STR2, vtype, sizeof(vtype)); if (vtype[0] == 0) strcpy(vtype, "POST");
|
||||
gets_(b, T_BIZDATE, bizdate, sizeof(bizdate));
|
||||
gets_(b, T_MSG, memo, sizeof(memo)); if (memo[0] == 0) strcpy(memo, "전표생성");
|
||||
vid = lgdb_next_voucher_id();
|
||||
if (vid < 0) FAIL(b);
|
||||
if (lgdb_insert_voucher(vid, vtype, ref, "D", bizdate, memo) < 0) FAIL(b);
|
||||
setl(b, T_ID1, vid);
|
||||
Bchg(b, T_STATUS, 0, "D", 0L);
|
||||
OK(b);
|
||||
}
|
||||
|
||||
/* 19. LG_APPLY - 전표 전기확정(P): 라인들의 계정잔액을 잔액테이블에 반영. */
|
||||
void LG_APPLY(TPSVCINFO *p)
|
||||
{
|
||||
UBFH *b = (UBFH *)p->data;
|
||||
char bizdate[16];
|
||||
EXEC SQL BEGIN DECLARE SECTION;
|
||||
long h_vid;
|
||||
char h_bizdate[16];
|
||||
EXEC SQL END DECLARE SECTION;
|
||||
h_vid = getl(b, T_ID1);
|
||||
gets_(b, T_BIZDATE, bizdate, sizeof(bizdate));
|
||||
strncpy(h_bizdate, bizdate, sizeof(h_bizdate)-1); h_bizdate[sizeof(h_bizdate)-1] = 0;
|
||||
|
||||
EXEC SQL INSERT INTO lg_account_bal (acct, biz_date, debit_total, credit_total, balance)
|
||||
SELECT acct, :h_bizdate,
|
||||
coalesce(sum(CASE WHEN dc = 'D' THEN amount ELSE 0 END), 0),
|
||||
coalesce(sum(CASE WHEN dc = 'C' THEN amount ELSE 0 END), 0),
|
||||
coalesce(sum(CASE WHEN dc = 'D' THEN amount ELSE -amount END), 0)
|
||||
FROM lg_ledger_line WHERE voucher_id = :h_vid
|
||||
GROUP BY acct
|
||||
ON CONFLICT (acct, biz_date) DO UPDATE
|
||||
SET debit_total = lg_account_bal.debit_total + EXCLUDED.debit_total,
|
||||
credit_total = lg_account_bal.credit_total + EXCLUDED.credit_total,
|
||||
balance = lg_account_bal.balance + EXCLUDED.balance,
|
||||
updated_at = now();
|
||||
if (sqlca.sqlcode < 0) FAIL(b);
|
||||
if (lgdb_update_voucher_status(h_vid, "P") < 0) FAIL(b);
|
||||
Bchg(b, T_STATUS, 0, "P", 0L);
|
||||
OK(b);
|
||||
}
|
||||
|
||||
/* 20. LG_ROLLFWD - 전 계정 잔액 익일 이월(개시잔액 롤포워드). */
|
||||
void LG_ROLLFWD(TPSVCINFO *p)
|
||||
{
|
||||
UBFH *b = (UBFH *)p->data;
|
||||
char bizdate[16], nextday[16];
|
||||
EXEC SQL BEGIN DECLARE SECTION;
|
||||
char h_bizdate[16], h_next[16];
|
||||
long h_cnt;
|
||||
EXEC SQL END DECLARE SECTION;
|
||||
gets_(b, T_BIZDATE, bizdate, sizeof(bizdate));
|
||||
if (acq_next_bizday(bizdate, nextday) < 0) strcpy(nextday, bizdate);
|
||||
strncpy(h_bizdate, bizdate, sizeof(h_bizdate)-1); h_bizdate[sizeof(h_bizdate)-1] = 0;
|
||||
strncpy(h_next, nextday, sizeof(h_next)-1); h_next[sizeof(h_next)-1] = 0;
|
||||
|
||||
EXEC SQL INSERT INTO lg_account_bal (acct, biz_date, debit_total, credit_total, balance)
|
||||
SELECT acct, :h_next, 0, 0, balance
|
||||
FROM lg_account_bal WHERE biz_date = :h_bizdate
|
||||
ON CONFLICT (acct, biz_date) DO UPDATE
|
||||
SET balance = EXCLUDED.balance, updated_at = now();
|
||||
if (sqlca.sqlcode < 0) FAIL(b);
|
||||
h_cnt = sqlca.sqlerrd[2];
|
||||
setl(b, T_COUNT, h_cnt);
|
||||
OK(b);
|
||||
}
|
||||
|
||||
/* 21. LG_MERCHBAL - 가맹점 잔액 재집계(dbio). */
|
||||
void LG_MERCHBAL(TPSVCINFO *p)
|
||||
{
|
||||
UBFH *b = (UBFH *)p->data;
|
||||
char merch[64], bizdate[16];
|
||||
gets_(b, T_MERCHANT, merch, sizeof(merch));
|
||||
gets_(b, T_BIZDATE, bizdate, sizeof(bizdate));
|
||||
if (merch[0] == 0) FAIL(b);
|
||||
if (lgdb_upsert_merch_bal(merch, bizdate) < 0) FAIL(b);
|
||||
OK(b);
|
||||
}
|
||||
|
||||
/* 22. LG_DAILYSUM - 당일 계정별 잔액 일괄 재집계(전 계정 upsert). */
|
||||
void LG_DAILYSUM(TPSVCINFO *p)
|
||||
{
|
||||
UBFH *b = (UBFH *)p->data;
|
||||
char bizdate[16];
|
||||
EXEC SQL BEGIN DECLARE SECTION;
|
||||
char h_bizdate[16];
|
||||
long h_cnt;
|
||||
EXEC SQL END DECLARE SECTION;
|
||||
gets_(b, T_BIZDATE, bizdate, sizeof(bizdate));
|
||||
strncpy(h_bizdate, bizdate, sizeof(h_bizdate)-1); h_bizdate[sizeof(h_bizdate)-1] = 0;
|
||||
|
||||
EXEC SQL INSERT INTO lg_account_bal (acct, biz_date, debit_total, credit_total, balance)
|
||||
SELECT acct, :h_bizdate,
|
||||
coalesce(sum(CASE WHEN dc = 'D' THEN amount ELSE 0 END), 0),
|
||||
coalesce(sum(CASE WHEN dc = 'C' THEN amount ELSE 0 END), 0),
|
||||
coalesce(sum(CASE WHEN dc = 'D' THEN amount ELSE -amount END), 0)
|
||||
FROM lg_ledger_line WHERE biz_date = :h_bizdate
|
||||
GROUP BY acct
|
||||
ON CONFLICT (acct, biz_date) DO UPDATE
|
||||
SET debit_total = EXCLUDED.debit_total,
|
||||
credit_total = EXCLUDED.credit_total,
|
||||
balance = EXCLUDED.balance,
|
||||
updated_at = now();
|
||||
if (sqlca.sqlcode < 0) FAIL(b);
|
||||
h_cnt = sqlca.sqlerrd[2];
|
||||
setl(b, T_COUNT, h_cnt);
|
||||
userlog("LG_DAILYSUM %s 계정 %ld건 집계", bizdate, h_cnt);
|
||||
OK(b);
|
||||
}
|
||||
|
||||
/* 23. LG_MONTHLYSUM - 월 계정과목별 차/대 집계 건수 조회(YYYY-MM). */
|
||||
void LG_MONTHLYSUM(TPSVCINFO *p)
|
||||
{
|
||||
UBFH *b = (UBFH *)p->data;
|
||||
char month[16];
|
||||
EXEC SQL BEGIN DECLARE SECTION;
|
||||
long h_cnt, h_dr;
|
||||
char h_month[16];
|
||||
EXEC SQL END DECLARE SECTION;
|
||||
gets_(b, T_STR1, month, sizeof(month));
|
||||
if (month[0] == 0) { gets_(b, T_BIZDATE, month, sizeof(month)); month[7] = 0; }
|
||||
strncpy(h_month, month, sizeof(h_month)-1); h_month[sizeof(h_month)-1] = 0;
|
||||
EXEC SQL SELECT count(DISTINCT acct),
|
||||
coalesce(sum(CASE WHEN dc = 'D' THEN amount ELSE 0 END), 0)
|
||||
INTO :h_cnt, :h_dr FROM lg_ledger_line
|
||||
WHERE to_char(biz_date, 'YYYY-MM') = :h_month;
|
||||
if (sqlca.sqlcode < 0) FAIL(b);
|
||||
setl(b, T_COUNT, h_cnt);
|
||||
setl(b, T_GROSS, h_dr);
|
||||
OK(b);
|
||||
}
|
||||
|
||||
/* 24. LG_ADJUST - 조정 전표: 부호에 따라 차변/대변 단일 분개. */
|
||||
void LG_ADJUST(TPSVCINFO *p)
|
||||
{
|
||||
UBFH *b = (UBFH *)p->data;
|
||||
char acct[32], bizdate[16], merch[64];
|
||||
long vid, delta, amt;
|
||||
const char *dc;
|
||||
gets_(b, T_KEY1, acct, sizeof(acct)); if (acct[0] == 0) strcpy(acct, "ADJ");
|
||||
gets_(b, T_BIZDATE, bizdate, sizeof(bizdate));
|
||||
gets_(b, T_MERCHANT, merch, sizeof(merch));
|
||||
delta = getl(b, T_DELTA);
|
||||
dc = (delta >= 0) ? "D" : "C";
|
||||
amt = (delta >= 0) ? delta : -delta;
|
||||
vid = lgdb_next_voucher_id();
|
||||
if (vid < 0) FAIL(b);
|
||||
if (lgdb_insert_voucher(vid, "ADJ", getl(b, T_PURCHASE_ID), "P", bizdate, "조정분개") < 0) FAIL(b);
|
||||
if (lgdb_insert_line(vid, acct, dc, amt, merch, bizdate, "posted") < 0) FAIL(b);
|
||||
setl(b, T_ID1, vid);
|
||||
OK(b);
|
||||
}
|
||||
|
||||
/* 25. LG_TRANSFER - 계정간 대체: 출금계정 대변, 입금계정 차변(균형). */
|
||||
void LG_TRANSFER(TPSVCINFO *p)
|
||||
{
|
||||
UBFH *b = (UBFH *)p->data;
|
||||
char src[32], dst[32], bizdate[16], merch[64];
|
||||
long vid, amount;
|
||||
gets_(b, T_KEY1, src, sizeof(src)); if (src[0] == 0) strcpy(src, "MERCH_PAY");
|
||||
gets_(b, T_KEY2, dst, sizeof(dst)); if (dst[0] == 0) strcpy(dst, "CASH");
|
||||
gets_(b, T_BIZDATE, bizdate, sizeof(bizdate));
|
||||
gets_(b, T_MERCHANT, merch, sizeof(merch));
|
||||
amount = getl(b, T_AMOUNT);
|
||||
vid = lgdb_next_voucher_id();
|
||||
if (vid < 0) FAIL(b);
|
||||
if (lgdb_insert_voucher(vid, "TRANSFER", 0, "P", bizdate, "계정대체") < 0) FAIL(b);
|
||||
if (lgdb_insert_line(vid, src, "C", amount, merch, bizdate, "posted") < 0) FAIL(b);
|
||||
if (lgdb_insert_line(vid, dst, "D", amount, merch, bizdate, "posted") < 0) FAIL(b);
|
||||
setl(b, T_ID1, vid);
|
||||
OK(b);
|
||||
}
|
||||
|
||||
/* 26. LG_SETTLEPOST - 정산 전기: 차변 MERCH_PAY, 대변 CASH (net). */
|
||||
void LG_SETTLEPOST(TPSVCINFO *p)
|
||||
{
|
||||
UBFH *b = (UBFH *)p->data;
|
||||
char bizdate[16], merch[64];
|
||||
long vid, net;
|
||||
gets_(b, T_BIZDATE, bizdate, sizeof(bizdate));
|
||||
gets_(b, T_MERCHANT, merch, sizeof(merch));
|
||||
net = getl(b, T_NET);
|
||||
vid = lgdb_next_voucher_id();
|
||||
if (vid < 0) FAIL(b);
|
||||
if (lgdb_insert_voucher(vid, "SETTLE", getl(b, T_PURCHASE_ID), "P", bizdate, "정산전기") < 0) FAIL(b);
|
||||
if (lgdb_insert_line(vid, "MERCH_PAY", "D", net, merch, bizdate, "posted") < 0) FAIL(b);
|
||||
if (lgdb_insert_line(vid, "CASH", "C", net, merch, bizdate, "posted") < 0) FAIL(b);
|
||||
if (lgdb_upsert_merch_bal(merch, bizdate) < 0) FAIL(b);
|
||||
setl(b, T_ID1, vid);
|
||||
userlog("LG_SETTLEPOST vid=%ld merch=%s net=%ld", vid, merch, net);
|
||||
OK(b);
|
||||
}
|
||||
|
||||
/* 27. LG_ACQPOST - 매입 전기: 차변 RECV=amount, 대변 MERCH_PAY=net + FEE_INC=fee. */
|
||||
void LG_ACQPOST(TPSVCINFO *p)
|
||||
{
|
||||
UBFH *b = (UBFH *)p->data;
|
||||
char bizdate[16], merch[64];
|
||||
long vid, amount, fee, net;
|
||||
gets_(b, T_BIZDATE, bizdate, sizeof(bizdate));
|
||||
gets_(b, T_MERCHANT, merch, sizeof(merch));
|
||||
amount = getl(b, T_AMOUNT);
|
||||
fee = getl(b, T_FEE);
|
||||
net = getl(b, T_NET);
|
||||
if (net == 0) net = amount - fee;
|
||||
vid = lgdb_next_voucher_id();
|
||||
if (vid < 0) FAIL(b);
|
||||
if (lgdb_insert_voucher(vid, "ACQ", getl(b, T_PURCHASE_ID), "P", bizdate, "매입전기") < 0) FAIL(b);
|
||||
if (lgdb_insert_line(vid, "RECV", "D", amount, merch, bizdate, "posted") < 0) FAIL(b);
|
||||
if (lgdb_insert_line(vid, "MERCH_PAY", "C", net, merch, bizdate, "posted") < 0) FAIL(b);
|
||||
if (lgdb_insert_line(vid, "FEE_INC", "C", fee, merch, bizdate, "posted") < 0) FAIL(b);
|
||||
setl(b, T_ID1, vid);
|
||||
userlog("LG_ACQPOST vid=%ld amount=%ld net=%ld fee=%ld", vid, amount, net, fee);
|
||||
OK(b);
|
||||
}
|
||||
|
||||
/* 28. LG_FEEPOST - 수수료수익 전기: 차변 RECV=fee, 대변 FEE_INC=fee. */
|
||||
void LG_FEEPOST(TPSVCINFO *p)
|
||||
{
|
||||
UBFH *b = (UBFH *)p->data;
|
||||
char bizdate[16], merch[64];
|
||||
long vid, fee;
|
||||
gets_(b, T_BIZDATE, bizdate, sizeof(bizdate));
|
||||
gets_(b, T_MERCHANT, merch, sizeof(merch));
|
||||
fee = getl(b, T_FEE);
|
||||
vid = lgdb_next_voucher_id();
|
||||
if (vid < 0) FAIL(b);
|
||||
if (lgdb_insert_voucher(vid, "FEE", getl(b, T_PURCHASE_ID), "P", bizdate, "수수료전기") < 0) FAIL(b);
|
||||
if (lgdb_insert_line(vid, "RECV", "D", fee, merch, bizdate, "posted") < 0) FAIL(b);
|
||||
if (lgdb_insert_line(vid, "FEE_INC", "C", fee, merch, bizdate, "posted") < 0) FAIL(b);
|
||||
setl(b, T_ID1, vid);
|
||||
OK(b);
|
||||
}
|
||||
|
||||
/* 29. LG_STATUS - 전표 상태 직접 변경(setter). */
|
||||
void LG_STATUS(TPSVCINFO *p)
|
||||
{
|
||||
UBFH *b = (UBFH *)p->data;
|
||||
char st[8];
|
||||
long vid = getl(b, T_ID1);
|
||||
gets_(b, T_STATUS, st, sizeof(st)); if (st[0] == 0) strcpy(st, "P");
|
||||
if (lgdb_update_voucher_status(vid, st) < 0) FAIL(b);
|
||||
OK(b);
|
||||
}
|
||||
|
||||
/* 30. LG_CONFIRM - 전표 확정: 대차 균형이면 P로 확정, 아니면 실패. */
|
||||
void LG_CONFIRM(TPSVCINFO *p)
|
||||
{
|
||||
UBFH *b = (UBFH *)p->data;
|
||||
EXEC SQL BEGIN DECLARE SECTION;
|
||||
long h_vid, h_dr, h_cr;
|
||||
EXEC SQL END DECLARE SECTION;
|
||||
h_vid = getl(b, T_ID1);
|
||||
EXEC SQL SELECT coalesce(sum(CASE WHEN dc = 'D' THEN amount ELSE 0 END), 0),
|
||||
coalesce(sum(CASE WHEN dc = 'C' THEN amount ELSE 0 END), 0)
|
||||
INTO :h_dr, :h_cr FROM lg_ledger_line WHERE voucher_id = :h_vid;
|
||||
if (sqlca.sqlcode < 0) FAIL(b);
|
||||
if (h_dr != h_cr) {
|
||||
userlog("LG_CONFIRM vid=%ld 대차 불균형 차변=%ld 대변=%ld -> 거부", h_vid, h_dr, h_cr);
|
||||
setl(b, T_RC, 1);
|
||||
FAIL(b);
|
||||
}
|
||||
if (lgdb_update_voucher_status(h_vid, "P") < 0) FAIL(b);
|
||||
setl(b, T_RC, 0);
|
||||
Bchg(b, T_STATUS, 0, "P", 0L);
|
||||
OK(b);
|
||||
}
|
||||
|
||||
/* vim: set ts=4 sw=4 et smartindent: */
|
||||
56
app/src/mg/batch/mg_resend_batch.pgc
Normal file
56
app/src/mg/batch/mg_resend_batch.pgc
Normal file
|
|
@ -0,0 +1,56 @@
|
|||
/*
|
||||
* mg_resend_batch.pgc - mg 실패 전문 재전송 배치 (커서 + XA).
|
||||
*
|
||||
* ATMI client: tpinit/tpopen open the ECPG XA RM, tpbegin starts ONE global
|
||||
* transaction, an EXEC SQL cursor sweeps mg_queue rows in F(실패)/Q(대기) state
|
||||
* for a biz_date, re-queues each (status->Q, retry_cnt++) via the linked dbio
|
||||
* lib, then tpcommit drives XA 2PC. Usage: mg_resend_batch [YYYY-MM-DD]
|
||||
*/
|
||||
#include <stdio.h>
|
||||
#include <stdlib.h>
|
||||
#include <string.h>
|
||||
#include <atmi.h>
|
||||
#include "mg_dbio.h"
|
||||
|
||||
int main(int argc, char **argv)
|
||||
{
|
||||
const char *bizdate = (argc > 1) ? argv[1] : "2026-07-19";
|
||||
long rows = 0;
|
||||
|
||||
EXEC SQL BEGIN DECLARE SECTION;
|
||||
char h_bizdate[16];
|
||||
long h_qid, h_retry;
|
||||
EXEC SQL END DECLARE SECTION;
|
||||
|
||||
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 rsc CURSOR FOR
|
||||
SELECT queue_id, retry_cnt FROM mg_queue
|
||||
WHERE biz_date = :h_bizdate AND status = 'F'
|
||||
ORDER BY queue_id;
|
||||
EXEC SQL OPEN rsc;
|
||||
if (sqlca.sqlcode < 0) { fprintf(stderr, "OPEN FAIL [%d] %s\n", sqlca.sqlcode, sqlca.sqlerrm.sqlerrmc); tpabort(0); return 1; }
|
||||
|
||||
for (;;) {
|
||||
EXEC SQL FETCH rsc INTO :h_qid, :h_retry;
|
||||
if (sqlca.sqlcode == 100) break;
|
||||
if (sqlca.sqlcode < 0) { fprintf(stderr, "FETCH FAIL [%d] %s\n", sqlca.sqlcode, sqlca.sqlerrm.sqlerrmc); EXEC SQL CLOSE rsc; tpabort(0); return 1; }
|
||||
/* 실패 전문을 재전송 대기(Q)로 되돌리고 retry_cnt 증가 */
|
||||
EXEC SQL UPDATE mg_queue
|
||||
SET status = 'Q', retry_cnt = retry_cnt + 1, updated_at = now()
|
||||
WHERE queue_id = :h_qid;
|
||||
if (sqlca.sqlcode < 0 || sqlca.sqlcode == 100) { EXEC SQL CLOSE rsc; tpabort(0); return 1; }
|
||||
rows++;
|
||||
}
|
||||
EXEC SQL CLOSE rsc;
|
||||
|
||||
if (tpcommit(0) < 0) { fprintf(stderr, "tpcommit FAIL: %s\n", tpstrerror(tperrno)); tpabort(0); return 1; }
|
||||
printf(">>> mg_resend_batch COMMIT: bizdate=%s resent=%ld\n", bizdate, rows);
|
||||
|
||||
tpclose(); tpterm();
|
||||
return 0;
|
||||
}
|
||||
62
app/src/mg/batch/mg_stat_batch.pgc
Normal file
62
app/src/mg/batch/mg_stat_batch.pgc
Normal file
|
|
@ -0,0 +1,62 @@
|
|||
/*
|
||||
* mg_stat_batch.pgc - mg 채널 일통계 배치 (커서 + XA).
|
||||
*
|
||||
* Cursors over mg_msg_log aggregated per channel for a biz_date and upserts
|
||||
* mg_stat (수신/송신/오류 건수 + 금액합), all under ONE global XA transaction.
|
||||
* Usage: mg_stat_batch [YYYY-MM-DD]
|
||||
*/
|
||||
#include <stdio.h>
|
||||
#include <stdlib.h>
|
||||
#include <string.h>
|
||||
#include <atmi.h>
|
||||
#include "mg_dbio.h"
|
||||
|
||||
int main(int argc, char **argv)
|
||||
{
|
||||
const char *bizdate = (argc > 1) ? argv[1] : "2026-07-19";
|
||||
long rows = 0;
|
||||
|
||||
EXEC SQL BEGIN DECLARE SECTION;
|
||||
char h_bizdate[16];
|
||||
char h_chan[16];
|
||||
long h_in, h_out, h_err, h_sum;
|
||||
EXEC SQL END DECLARE SECTION;
|
||||
|
||||
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 msc CURSOR FOR
|
||||
SELECT channel,
|
||||
coalesce(sum(CASE WHEN direction='IN' THEN 1 ELSE 0 END),0),
|
||||
coalesce(sum(CASE WHEN direction='OUT' THEN 1 ELSE 0 END),0),
|
||||
coalesce(sum(CASE WHEN rc<>'00' THEN 1 ELSE 0 END),0),
|
||||
coalesce(sum(amount),0)
|
||||
FROM mg_msg_log WHERE biz_date = :h_bizdate
|
||||
GROUP BY channel ORDER BY channel;
|
||||
EXEC SQL OPEN msc;
|
||||
if (sqlca.sqlcode < 0) { fprintf(stderr, "OPEN FAIL [%d] %s\n", sqlca.sqlcode, sqlca.sqlerrm.sqlerrmc); tpabort(0); return 1; }
|
||||
|
||||
for (;;) {
|
||||
EXEC SQL FETCH msc INTO :h_chan, :h_in, :h_out, :h_err, :h_sum;
|
||||
if (sqlca.sqlcode == 100) break;
|
||||
if (sqlca.sqlcode < 0) { fprintf(stderr, "FETCH FAIL [%d] %s\n", sqlca.sqlcode, sqlca.sqlerrm.sqlerrmc); EXEC SQL CLOSE msc; tpabort(0); return 1; }
|
||||
EXEC SQL INSERT INTO mg_stat (channel, biz_date, in_cnt, out_cnt, err_cnt, amount_sum)
|
||||
VALUES (:h_chan, :h_bizdate, :h_in, :h_out, :h_err, :h_sum)
|
||||
ON CONFLICT (channel, biz_date) DO UPDATE
|
||||
SET in_cnt = EXCLUDED.in_cnt, out_cnt = EXCLUDED.out_cnt,
|
||||
err_cnt = EXCLUDED.err_cnt, amount_sum = EXCLUDED.amount_sum,
|
||||
updated_at = now();
|
||||
if (sqlca.sqlcode < 0) { fprintf(stderr, "UPSERT FAIL [%d] %s\n", sqlca.sqlcode, sqlca.sqlerrm.sqlerrmc); EXEC SQL CLOSE msc; tpabort(0); return 1; }
|
||||
rows++;
|
||||
}
|
||||
EXEC SQL CLOSE msc;
|
||||
|
||||
if (tpcommit(0) < 0) { fprintf(stderr, "tpcommit FAIL: %s\n", tpstrerror(tperrno)); tpabort(0); return 1; }
|
||||
printf(">>> mg_stat_batch COMMIT: bizdate=%s channels=%ld\n", bizdate, rows);
|
||||
|
||||
tpclose(); tpterm();
|
||||
return 0;
|
||||
}
|
||||
41
app/src/mg/dbio/mg_dbio.h
Normal file
41
app/src/mg/dbio/mg_dbio.h
Normal file
|
|
@ -0,0 +1,41 @@
|
|||
/*
|
||||
* mg_dbio.h - mg 모듈 (전문게이트웨이) DB 접근 계층 (libmgdbio.a).
|
||||
* ECPG (EXEC SQL) functions compiled into a static library and linked into
|
||||
* mg_svr and the mg batches. They run on the caller's XA branch/connection
|
||||
* (no EXEC SQL CONNECT); sqlca is the shared per-thread ECPG SQLCA.
|
||||
* Return convention: 0 = ok, -1 = SQL error (caller may also inspect sqlca).
|
||||
*/
|
||||
#ifndef MG_DBIO_H
|
||||
#define MG_DBIO_H
|
||||
|
||||
/* 채번 (STAN/전문 시퀀스) */
|
||||
long mgdb_next_stan(void);
|
||||
long mgdb_next_msg_id(void);
|
||||
long mgdb_next_queue_id(void);
|
||||
long mgdb_seq_bump(const char *name); /* mg_seq 이름있는 카운터 +1 -> 새 값 */
|
||||
|
||||
/* 전문 로그 (mg_msg_log) */
|
||||
int mgdb_insert_msg(long msg_id, long stan, const char *mti, const char *channel,
|
||||
const char *direction, const char *proc_code, const char *bitmap,
|
||||
long amount, const char *pan, const char *rc, const char *raw,
|
||||
const char *status, const char *bizdate);
|
||||
int mgdb_update_msg_rc(long msg_id, const char *rc, const char *status);
|
||||
int mgdb_get_msg_status(long msg_id, char *out /* >= 2 bytes */);
|
||||
long mgdb_find_msg_by_stan(long stan); /* msg_id 반환, 없으면 -1 */
|
||||
|
||||
/* 라우팅 (mg_route) */
|
||||
int mgdb_insert_route(long route_id, const char *channel, const char *mti,
|
||||
const char *dest, long priority, const char *bizdate);
|
||||
int mgdb_lookup_route(const char *channel, const char *mti, char *dest_out /* >= 64 */);
|
||||
|
||||
/* 채널 (mg_channel) */
|
||||
int mgdb_get_channel_status(const char *channel, char *status_out /* >= 16 */);
|
||||
int mgdb_set_channel_status(const char *channel, const char *status);
|
||||
int mgdb_set_channel_session(const char *channel, const char *session_key, long stan);
|
||||
|
||||
/* 큐 (mg_queue) */
|
||||
int mgdb_enqueue(long queue_id, long stan, const char *channel, const char *mti,
|
||||
const char *payload, const char *bizdate);
|
||||
int mgdb_set_queue_status(long queue_id, const char *status);
|
||||
|
||||
#endif /* MG_DBIO_H */
|
||||
200
app/src/mg/dbio/mg_msg_dbio.pgc
Normal file
200
app/src/mg/dbio/mg_msg_dbio.pgc
Normal file
|
|
@ -0,0 +1,200 @@
|
|||
/*
|
||||
* mg_msg_dbio.pgc - 전문 로그/채번/큐 DB 접근 (part of libmgdbio.a).
|
||||
* ECPG functions; run on the caller's XA branch. No EXEC SQL CONNECT.
|
||||
*/
|
||||
#include <string.h>
|
||||
#include <userlog.h>
|
||||
#include "mg_dbio.h"
|
||||
|
||||
long mgdb_next_stan(void)
|
||||
{
|
||||
EXEC SQL BEGIN DECLARE SECTION;
|
||||
long h_id;
|
||||
EXEC SQL END DECLARE SECTION;
|
||||
|
||||
EXEC SQL SELECT nextval('mg_stan_seq') INTO :h_id;
|
||||
if (sqlca.sqlcode < 0) {
|
||||
userlog("mgdb_next_stan FAIL [%d] %s", sqlca.sqlcode, sqlca.sqlerrm.sqlerrmc);
|
||||
return -1;
|
||||
}
|
||||
return h_id;
|
||||
}
|
||||
|
||||
long mgdb_next_msg_id(void)
|
||||
{
|
||||
EXEC SQL BEGIN DECLARE SECTION;
|
||||
long h_id;
|
||||
EXEC SQL END DECLARE SECTION;
|
||||
|
||||
EXEC SQL SELECT nextval('mg_msg_seq') INTO :h_id;
|
||||
if (sqlca.sqlcode < 0) {
|
||||
userlog("mgdb_next_msg_id FAIL [%d] %s", sqlca.sqlcode, sqlca.sqlerrm.sqlerrmc);
|
||||
return -1;
|
||||
}
|
||||
return h_id;
|
||||
}
|
||||
|
||||
long mgdb_next_queue_id(void)
|
||||
{
|
||||
EXEC SQL BEGIN DECLARE SECTION;
|
||||
long h_id;
|
||||
EXEC SQL END DECLARE SECTION;
|
||||
|
||||
EXEC SQL SELECT nextval('mg_queue_seq') INTO :h_id;
|
||||
if (sqlca.sqlcode < 0) {
|
||||
userlog("mgdb_next_queue_id FAIL [%d] %s", sqlca.sqlcode, sqlca.sqlerrm.sqlerrmc);
|
||||
return -1;
|
||||
}
|
||||
return h_id;
|
||||
}
|
||||
|
||||
/* mg_seq 이름있는 카운터를 원자적으로 +1 하고 새 값을 돌려준다 (세션 시퀀스 채번). */
|
||||
long mgdb_seq_bump(const char *name)
|
||||
{
|
||||
EXEC SQL BEGIN DECLARE SECTION;
|
||||
long h_val;
|
||||
char h_name[32];
|
||||
EXEC SQL END DECLARE SECTION;
|
||||
|
||||
strncpy(h_name, name, sizeof(h_name) - 1); h_name[sizeof(h_name)-1] = 0;
|
||||
|
||||
EXEC SQL UPDATE mg_seq SET seq_val = seq_val + 1, updated_at = now()
|
||||
WHERE seq_name = :h_name;
|
||||
if (sqlca.sqlcode < 0) {
|
||||
userlog("mgdb_seq_bump UPDATE FAIL [%d] %s", sqlca.sqlcode, sqlca.sqlerrm.sqlerrmc);
|
||||
return -1;
|
||||
}
|
||||
if (sqlca.sqlcode == 100) { /* 최초 사용: 행 생성 */
|
||||
EXEC SQL INSERT INTO mg_seq (seq_name, seq_val) VALUES (:h_name, 1);
|
||||
if (sqlca.sqlcode < 0) return -1;
|
||||
return 1;
|
||||
}
|
||||
EXEC SQL SELECT seq_val INTO :h_val FROM mg_seq WHERE seq_name = :h_name;
|
||||
if (sqlca.sqlcode < 0 || sqlca.sqlcode == 100) return -1;
|
||||
return h_val;
|
||||
}
|
||||
|
||||
int mgdb_insert_msg(long msg_id, long stan, const char *mti, const char *channel,
|
||||
const char *direction, const char *proc_code, const char *bitmap,
|
||||
long amount, const char *pan, const char *rc, const char *raw,
|
||||
const char *status, const char *bizdate)
|
||||
{
|
||||
EXEC SQL BEGIN DECLARE SECTION;
|
||||
long h_id = msg_id, h_stan = stan, h_amount = amount;
|
||||
char h_mti[8], h_chan[16], h_dir[8], h_proc[8], h_bitmap[40];
|
||||
char h_pan[24], h_rc[4], h_raw[512], h_status[4], h_bizdate[16];
|
||||
EXEC SQL END DECLARE SECTION;
|
||||
|
||||
strncpy(h_mti, mti, sizeof(h_mti)-1); h_mti[sizeof(h_mti)-1] = 0;
|
||||
strncpy(h_chan, channel, sizeof(h_chan)-1); h_chan[sizeof(h_chan)-1] = 0;
|
||||
strncpy(h_dir, direction, sizeof(h_dir)-1); h_dir[sizeof(h_dir)-1] = 0;
|
||||
strncpy(h_proc, proc_code, sizeof(h_proc)-1); h_proc[sizeof(h_proc)-1] = 0;
|
||||
strncpy(h_bitmap, bitmap, sizeof(h_bitmap)-1); h_bitmap[sizeof(h_bitmap)-1] = 0;
|
||||
strncpy(h_pan, pan, sizeof(h_pan)-1); h_pan[sizeof(h_pan)-1] = 0;
|
||||
strncpy(h_rc, rc, sizeof(h_rc)-1); h_rc[sizeof(h_rc)-1] = 0;
|
||||
strncpy(h_raw, raw, sizeof(h_raw)-1); h_raw[sizeof(h_raw)-1] = 0;
|
||||
strncpy(h_status, status, sizeof(h_status)-1); h_status[sizeof(h_status)-1] = 0;
|
||||
strncpy(h_bizdate, bizdate, sizeof(h_bizdate)-1); h_bizdate[sizeof(h_bizdate)-1] = 0;
|
||||
|
||||
EXEC SQL INSERT INTO mg_msg_log
|
||||
(msg_id, stan, mti, channel, direction, proc_code, bitmap, amount,
|
||||
pan, rc, raw_msg, status, biz_date)
|
||||
VALUES (:h_id, :h_stan, :h_mti, :h_chan, :h_dir, :h_proc, :h_bitmap,
|
||||
:h_amount, :h_pan, :h_rc, :h_raw, :h_status, :h_bizdate);
|
||||
if (sqlca.sqlcode < 0) {
|
||||
userlog("mgdb_insert_msg FAIL [%d] %s", sqlca.sqlcode, sqlca.sqlerrm.sqlerrmc);
|
||||
return -1;
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
int mgdb_update_msg_rc(long msg_id, const char *rc, const char *status)
|
||||
{
|
||||
EXEC SQL BEGIN DECLARE SECTION;
|
||||
long h_id = msg_id;
|
||||
char h_rc[4], h_status[4];
|
||||
EXEC SQL END DECLARE SECTION;
|
||||
|
||||
strncpy(h_rc, rc, sizeof(h_rc)-1); h_rc[sizeof(h_rc)-1] = 0;
|
||||
strncpy(h_status, status, sizeof(h_status)-1); h_status[sizeof(h_status)-1] = 0;
|
||||
|
||||
EXEC SQL UPDATE mg_msg_log SET rc = :h_rc, status = :h_status WHERE msg_id = :h_id;
|
||||
if (sqlca.sqlcode < 0 || sqlca.sqlcode == 100) {
|
||||
userlog("mgdb_update_msg_rc FAIL [%d] %s", sqlca.sqlcode, sqlca.sqlerrm.sqlerrmc);
|
||||
return -1;
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
int mgdb_get_msg_status(long msg_id, char *out)
|
||||
{
|
||||
EXEC SQL BEGIN DECLARE SECTION;
|
||||
long h_id = msg_id;
|
||||
char h_status[4];
|
||||
EXEC SQL END DECLARE SECTION;
|
||||
|
||||
EXEC SQL SELECT status INTO :h_status FROM mg_msg_log WHERE msg_id = :h_id;
|
||||
if (sqlca.sqlcode < 0 || sqlca.sqlcode == 100) {
|
||||
userlog("mgdb_get_msg_status FAIL [%d] %s", sqlca.sqlcode, sqlca.sqlerrm.sqlerrmc);
|
||||
return -1;
|
||||
}
|
||||
out[0] = h_status[0];
|
||||
out[1] = 0;
|
||||
return 0;
|
||||
}
|
||||
|
||||
long mgdb_find_msg_by_stan(long stan)
|
||||
{
|
||||
EXEC SQL BEGIN DECLARE SECTION;
|
||||
long h_stan = stan, h_id;
|
||||
EXEC SQL END DECLARE SECTION;
|
||||
|
||||
EXEC SQL SELECT msg_id INTO :h_id FROM mg_msg_log
|
||||
WHERE stan = :h_stan ORDER BY msg_id DESC LIMIT 1;
|
||||
if (sqlca.sqlcode == 100) return -1;
|
||||
if (sqlca.sqlcode < 0) {
|
||||
userlog("mgdb_find_msg_by_stan FAIL [%d] %s", sqlca.sqlcode, sqlca.sqlerrm.sqlerrmc);
|
||||
return -1;
|
||||
}
|
||||
return h_id;
|
||||
}
|
||||
|
||||
int mgdb_enqueue(long queue_id, long stan, const char *channel, const char *mti,
|
||||
const char *payload, const char *bizdate)
|
||||
{
|
||||
EXEC SQL BEGIN DECLARE SECTION;
|
||||
long h_id = queue_id, h_stan = stan;
|
||||
char h_chan[16], h_mti[8], h_payload[512], h_bizdate[16];
|
||||
EXEC SQL END DECLARE SECTION;
|
||||
|
||||
strncpy(h_chan, channel, sizeof(h_chan)-1); h_chan[sizeof(h_chan)-1] = 0;
|
||||
strncpy(h_mti, mti, sizeof(h_mti)-1); h_mti[sizeof(h_mti)-1] = 0;
|
||||
strncpy(h_payload, payload, sizeof(h_payload)-1); h_payload[sizeof(h_payload)-1] = 0;
|
||||
strncpy(h_bizdate, bizdate, sizeof(h_bizdate)-1); h_bizdate[sizeof(h_bizdate)-1] = 0;
|
||||
|
||||
EXEC SQL INSERT INTO mg_queue (queue_id, stan, channel, mti, payload, status, biz_date)
|
||||
VALUES (:h_id, :h_stan, :h_chan, :h_mti, :h_payload, 'Q', :h_bizdate);
|
||||
if (sqlca.sqlcode < 0) {
|
||||
userlog("mgdb_enqueue FAIL [%d] %s", sqlca.sqlcode, sqlca.sqlerrm.sqlerrmc);
|
||||
return -1;
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
int mgdb_set_queue_status(long queue_id, const char *status)
|
||||
{
|
||||
EXEC SQL BEGIN DECLARE SECTION;
|
||||
long h_id = queue_id;
|
||||
char h_status[4];
|
||||
EXEC SQL END DECLARE SECTION;
|
||||
|
||||
strncpy(h_status, status, sizeof(h_status)-1); h_status[sizeof(h_status)-1] = 0;
|
||||
|
||||
EXEC SQL UPDATE mg_queue SET status = :h_status, updated_at = now()
|
||||
WHERE queue_id = :h_id;
|
||||
if (sqlca.sqlcode < 0 || sqlca.sqlcode == 100) {
|
||||
userlog("mgdb_set_queue_status FAIL [%d] %s", sqlca.sqlcode, sqlca.sqlerrm.sqlerrmc);
|
||||
return -1;
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
111
app/src/mg/dbio/mg_route_dbio.pgc
Normal file
111
app/src/mg/dbio/mg_route_dbio.pgc
Normal file
|
|
@ -0,0 +1,111 @@
|
|||
/*
|
||||
* mg_route_dbio.pgc - 라우팅 룰 + 채널 세션 DB 접근 (part of libmgdbio.a).
|
||||
*/
|
||||
#include <string.h>
|
||||
#include <userlog.h>
|
||||
#include "mg_dbio.h"
|
||||
|
||||
int mgdb_insert_route(long route_id, const char *channel, const char *mti,
|
||||
const char *dest, long priority, const char *bizdate)
|
||||
{
|
||||
EXEC SQL BEGIN DECLARE SECTION;
|
||||
long h_id = route_id, h_pri = priority;
|
||||
char h_chan[16], h_mti[8], h_dest[64], h_bizdate[16];
|
||||
EXEC SQL END DECLARE SECTION;
|
||||
|
||||
strncpy(h_chan, channel, sizeof(h_chan)-1); h_chan[sizeof(h_chan)-1] = 0;
|
||||
strncpy(h_mti, mti, sizeof(h_mti)-1); h_mti[sizeof(h_mti)-1] = 0;
|
||||
strncpy(h_dest, dest, sizeof(h_dest)-1); h_dest[sizeof(h_dest)-1] = 0;
|
||||
strncpy(h_bizdate, bizdate, sizeof(h_bizdate)-1); h_bizdate[sizeof(h_bizdate)-1] = 0;
|
||||
|
||||
EXEC SQL INSERT INTO mg_route (route_id, channel, mti, dest, priority, active, biz_date)
|
||||
VALUES (:h_id, :h_chan, :h_mti, :h_dest, :h_pri, true, :h_bizdate);
|
||||
if (sqlca.sqlcode < 0) {
|
||||
userlog("mgdb_insert_route FAIL [%d] %s", sqlca.sqlcode, sqlca.sqlerrm.sqlerrmc);
|
||||
return -1;
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
/* 채널+전문유형으로 활성 라우팅 목적지를 우선순위 순으로 1건 조회. */
|
||||
int mgdb_lookup_route(const char *channel, const char *mti, char *dest_out)
|
||||
{
|
||||
EXEC SQL BEGIN DECLARE SECTION;
|
||||
char h_chan[16], h_mti[8], h_dest[64];
|
||||
EXEC SQL END DECLARE SECTION;
|
||||
|
||||
strncpy(h_chan, channel, sizeof(h_chan)-1); h_chan[sizeof(h_chan)-1] = 0;
|
||||
strncpy(h_mti, mti, sizeof(h_mti)-1); h_mti[sizeof(h_mti)-1] = 0;
|
||||
|
||||
EXEC SQL SELECT dest INTO :h_dest FROM mg_route
|
||||
WHERE channel = :h_chan AND mti = :h_mti AND active = true
|
||||
ORDER BY priority ASC LIMIT 1;
|
||||
if (sqlca.sqlcode == 100) { dest_out[0] = 0; return -1; }
|
||||
if (sqlca.sqlcode < 0) {
|
||||
userlog("mgdb_lookup_route FAIL [%d] %s", sqlca.sqlcode, sqlca.sqlerrm.sqlerrmc);
|
||||
return -1;
|
||||
}
|
||||
strncpy(dest_out, h_dest, 63); dest_out[63] = 0;
|
||||
return 0;
|
||||
}
|
||||
|
||||
int mgdb_get_channel_status(const char *channel, char *status_out)
|
||||
{
|
||||
EXEC SQL BEGIN DECLARE SECTION;
|
||||
char h_chan[16], h_status[16];
|
||||
EXEC SQL END DECLARE SECTION;
|
||||
|
||||
strncpy(h_chan, channel, sizeof(h_chan)-1); h_chan[sizeof(h_chan)-1] = 0;
|
||||
|
||||
EXEC SQL SELECT status INTO :h_status FROM mg_channel WHERE channel = :h_chan;
|
||||
if (sqlca.sqlcode == 100) { strcpy(status_out, "UNKNOWN"); return -1; }
|
||||
if (sqlca.sqlcode < 0) {
|
||||
userlog("mgdb_get_channel_status FAIL [%d] %s", sqlca.sqlcode, sqlca.sqlerrm.sqlerrmc);
|
||||
return -1;
|
||||
}
|
||||
strncpy(status_out, h_status, 15); status_out[15] = 0;
|
||||
return 0;
|
||||
}
|
||||
|
||||
int mgdb_set_channel_status(const char *channel, const char *status)
|
||||
{
|
||||
EXEC SQL BEGIN DECLARE SECTION;
|
||||
char h_chan[16], h_status[16];
|
||||
EXEC SQL END DECLARE SECTION;
|
||||
|
||||
strncpy(h_chan, channel, sizeof(h_chan)-1); h_chan[sizeof(h_chan)-1] = 0;
|
||||
strncpy(h_status, status, sizeof(h_status)-1); h_status[sizeof(h_status)-1] = 0;
|
||||
|
||||
EXEC SQL UPDATE mg_channel SET status = :h_status, updated_at = now()
|
||||
WHERE channel = :h_chan;
|
||||
if (sqlca.sqlcode < 0) {
|
||||
userlog("mgdb_set_channel_status FAIL [%d] %s", sqlca.sqlcode, sqlca.sqlerrm.sqlerrmc);
|
||||
return -1;
|
||||
}
|
||||
if (sqlca.sqlcode == 100) { /* 신규 채널 등록 */
|
||||
EXEC SQL INSERT INTO mg_channel (channel, status) VALUES (:h_chan, :h_status);
|
||||
if (sqlca.sqlcode < 0) return -1;
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
int mgdb_set_channel_session(const char *channel, const char *session_key, long stan)
|
||||
{
|
||||
EXEC SQL BEGIN DECLARE SECTION;
|
||||
long h_stan = stan;
|
||||
char h_chan[16], h_key[64];
|
||||
EXEC SQL END DECLARE SECTION;
|
||||
|
||||
strncpy(h_chan, channel, sizeof(h_chan)-1); h_chan[sizeof(h_chan)-1] = 0;
|
||||
strncpy(h_key, session_key, sizeof(h_key)-1); h_key[sizeof(h_key)-1] = 0;
|
||||
|
||||
EXEC SQL UPDATE mg_channel
|
||||
SET session_key = :h_key, last_stan = :h_stan, status = 'SIGNON',
|
||||
signon_at = now(), updated_at = now()
|
||||
WHERE channel = :h_chan;
|
||||
if (sqlca.sqlcode < 0 || sqlca.sqlcode == 100) {
|
||||
userlog("mgdb_set_channel_session FAIL [%d] %s", sqlca.sqlcode, sqlca.sqlerrm.sqlerrmc);
|
||||
return -1;
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
649
app/src/mg/mg_svr.pgc
Normal file
649
app/src/mg/mg_svr.pgc
Normal file
|
|
@ -0,0 +1,649 @@
|
|||
/*
|
||||
* mg_svr.pgc - mg (전문게이트웨이 / message gateway) MODULE SERVER.
|
||||
*
|
||||
* ONE binary that tpadvertise()s ALL ~30 online mg services (the real Tuxedo
|
||||
* "one module server, many services" pattern). Every service is a distinct 전문
|
||||
* (ISO8583) gateway operation with genuine EXEC SQL (directly, or via the linked
|
||||
* libmgdbio.a dbio layer) plus 고정길이/비트맵 pack/unpack idioms and STAN 채번
|
||||
* via a Postgres sequence.
|
||||
*
|
||||
* XA: the connection is opened once by tpopen() (ECPG XA switch libndrxxaecpg.so);
|
||||
* there is NO EXEC SQL CONNECT. Each tpcall runs on this process's XA branch.
|
||||
*/
|
||||
#include <stdio.h>
|
||||
#include <stdlib.h>
|
||||
#include <string.h>
|
||||
#include <atmi.h>
|
||||
#include <ubf.h>
|
||||
#include <userlog.h>
|
||||
#include <ndebug.h>
|
||||
#include "acq.fd.h"
|
||||
#include "mg_dbio.h"
|
||||
#include "acq_common.h"
|
||||
|
||||
/* ----- small UBF helpers ------------------------------------------------- */
|
||||
static long getl(UBFH *b, BFLDID f) { long v = 0; Bget(b, f, 0, (char *)&v, 0L); return v; }
|
||||
static void gets_(UBFH *b, BFLDID f, char *out, int cap)
|
||||
{ BFLDLEN l = cap; out[0] = 0; Bget(b, f, 0, out, &l); }
|
||||
static void setl(UBFH *b, BFLDID f, long v) { Bchg(b, f, 0, (char *)&v, 0L); }
|
||||
static void sets_(UBFH *b, BFLDID f, const char *s) { Bchg(b, f, 0, (char *)s, 0L); }
|
||||
|
||||
#define FAIL(b) do { tpreturn(TPFAIL, 0, (char *)(b), 0L, 0L); return; } while (0)
|
||||
#define OK(b) do { tpreturn(TPSUCCESS, 0, (char *)(b), 0L, 0L); return; } while (0)
|
||||
|
||||
/* ----- service prototypes ------------------------------------------------ */
|
||||
void MG_RECV(TPSVCINFO *p); void MG_SEND(TPSVCINFO *p);
|
||||
void MG_ISO8583(TPSVCINFO *p); void MG_BITMAP(TPSVCINFO *p);
|
||||
void MG_STAN(TPSVCINFO *p); void MG_ROUTE(TPSVCINFO *p);
|
||||
void MG_RESEND(TPSVCINFO *p); void MG_CONVERT(TPSVCINFO *p);
|
||||
void MG_ERRMSG(TPSVCINFO *p); void MG_LOG(TPSVCINFO *p);
|
||||
void MG_CHANAUTH(TPSVCINFO *p); void MG_SESSION(TPSVCINFO *p);
|
||||
void MG_ACK(TPSVCINFO *p); void MG_NACK(TPSVCINFO *p);
|
||||
void MG_ECHO(TPSVCINFO *p); void MG_HEALTH(TPSVCINFO *p);
|
||||
void MG_SIGNON(TPSVCINFO *p); void MG_SIGNOFF(TPSVCINFO *p);
|
||||
void MG_KEYEXCH(TPSVCINFO *p); void MG_MAC(TPSVCINFO *p);
|
||||
void MG_PARSE(TPSVCINFO *p); void MG_PACK(TPSVCINFO *p);
|
||||
void MG_UNPACK(TPSVCINFO *p); void MG_QUEUE(TPSVCINFO *p);
|
||||
void MG_DEQUEUE(TPSVCINFO *p); void MG_RETRY(TPSVCINFO *p);
|
||||
void MG_STATS(TPSVCINFO *p); void MG_CHANSTAT(TPSVCINFO *p);
|
||||
void MG_STATUS(TPSVCINFO *p); void MG_CONFIRM(TPSVCINFO *p);
|
||||
|
||||
/* ----- advertise table (this is what "scales to 30" cleanly) ------------- */
|
||||
static struct { const char *name; void (*fn)(TPSVCINFO *); } SVCS[] = {
|
||||
{"MG_RECV", MG_RECV}, {"MG_SEND", MG_SEND},
|
||||
{"MG_ISO8583", MG_ISO8583}, {"MG_BITMAP", MG_BITMAP},
|
||||
{"MG_STAN", MG_STAN}, {"MG_ROUTE", MG_ROUTE},
|
||||
{"MG_RESEND", MG_RESEND}, {"MG_CONVERT", MG_CONVERT},
|
||||
{"MG_ERRMSG", MG_ERRMSG}, {"MG_LOG", MG_LOG},
|
||||
{"MG_CHANAUTH", MG_CHANAUTH}, {"MG_SESSION", MG_SESSION},
|
||||
{"MG_ACK", MG_ACK}, {"MG_NACK", MG_NACK},
|
||||
{"MG_ECHO", MG_ECHO}, {"MG_HEALTH", MG_HEALTH},
|
||||
{"MG_SIGNON", MG_SIGNON}, {"MG_SIGNOFF", MG_SIGNOFF},
|
||||
{"MG_KEYEXCH", MG_KEYEXCH}, {"MG_MAC", MG_MAC},
|
||||
{"MG_PARSE", MG_PARSE}, {"MG_PACK", MG_PACK},
|
||||
{"MG_UNPACK", MG_UNPACK}, {"MG_QUEUE", MG_QUEUE},
|
||||
{"MG_DEQUEUE", MG_DEQUEUE}, {"MG_RETRY", MG_RETRY},
|
||||
{"MG_STATS", MG_STATS}, {"MG_CHANSTAT", MG_CHANSTAT},
|
||||
{"MG_STATUS", MG_STATUS}, {"MG_CONFIRM", MG_CONFIRM},
|
||||
{NULL, NULL}
|
||||
};
|
||||
|
||||
int tpsvrinit(int argc, char **argv)
|
||||
{
|
||||
int i;
|
||||
if (tpopen() < 0) {
|
||||
userlog("mg_svr: tpopen FAIL: %s", tpstrerror(tperrno));
|
||||
return -1;
|
||||
}
|
||||
for (i = 0; SVCS[i].name != NULL; i++) {
|
||||
if (tpadvertise((char *)SVCS[i].name, SVCS[i].fn) < 0) {
|
||||
userlog("mg_svr: tpadvertise(%s) FAIL: %s", SVCS[i].name, tpstrerror(tperrno));
|
||||
return -1;
|
||||
}
|
||||
}
|
||||
userlog("mg_svr: %d mg 서비스 광고 완료, RM 오픈", i);
|
||||
return 0;
|
||||
}
|
||||
|
||||
void tpsvrdone(void)
|
||||
{
|
||||
tpclose();
|
||||
userlog("mg_svr: tpsvrdone");
|
||||
}
|
||||
|
||||
/* ----- module-local pure helpers (고정길이/비트맵/체크섬) ----------------- */
|
||||
/* 16-hex primary bitmap from a 64-bit field-present mask. */
|
||||
static void mg_bitmap_hex(unsigned long mask, char *out /* >= 17 */)
|
||||
{
|
||||
sprintf(out, "%016lX", mask);
|
||||
}
|
||||
/* simple additive 4-hex MAC/checksum over a buffer (전문 무결성 데모). */
|
||||
static void mg_checksum(const char *s, char *out /* >= 5 */)
|
||||
{
|
||||
unsigned int sum = 0; int i;
|
||||
for (i = 0; s[i]; i++) sum = (sum + (unsigned char)s[i] * 31u) & 0xFFFF;
|
||||
sprintf(out, "%04X", sum);
|
||||
}
|
||||
|
||||
/* ========================================================================= */
|
||||
/* 1. MG_RECV - 전문 수신 접수 (direction=IN). STAN 채번 후 로그 적재. */
|
||||
/* ========================================================================= */
|
||||
void MG_RECV(TPSVCINFO *p)
|
||||
{
|
||||
UBFH *b = (UBFH *)p->data;
|
||||
char chan[16], mti[8], raw[512], bizdate[16], pan[24];
|
||||
long stan, msg_id, amount;
|
||||
|
||||
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, raw, sizeof(raw));
|
||||
gets_(b, T_STR5, pan, sizeof(pan));
|
||||
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, mti, chan, "IN", "000000", "", amount,
|
||||
pan, "00", raw, "R", bizdate) < 0) FAIL(b);
|
||||
|
||||
setl(b, T_ARG1, stan);
|
||||
setl(b, T_ID1, msg_id);
|
||||
userlog("MG_RECV 수신 stan=%ld msg_id=%ld chan=%s mti=%s amt=%ld", stan, msg_id, chan, mti, amount);
|
||||
OK(b);
|
||||
}
|
||||
|
||||
/* 2. MG_SEND - 전문 송신 (direction=OUT). */
|
||||
void MG_SEND(TPSVCINFO *p)
|
||||
{
|
||||
UBFH *b = (UBFH *)p->data;
|
||||
char chan[16], mti[8], raw[512], bizdate[16];
|
||||
long stan, msg_id, amount;
|
||||
|
||||
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, "0210");
|
||||
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, mti, chan, "OUT", "000000", "", amount,
|
||||
"", "00", raw, "S", bizdate) < 0) FAIL(b);
|
||||
setl(b, T_ARG1, stan);
|
||||
setl(b, T_ID1, msg_id);
|
||||
OK(b);
|
||||
}
|
||||
|
||||
/* 3. MG_ISO8583 - ISO8583 전문 파싱 후 로그 (mti=전문 앞 4자리). */
|
||||
void MG_ISO8583(TPSVCINFO *p)
|
||||
{
|
||||
UBFH *b = (UBFH *)p->data;
|
||||
char chan[16], raw[512], bizdate[16], mti[8], proc[8];
|
||||
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);
|
||||
/* 고정길이 전문: [0..3]=MTI, [4..9]=처리코드, [10..21]=금액(12자리) */
|
||||
memset(mti, 0, sizeof(mti)); memset(proc, 0, sizeof(proc));
|
||||
if (n >= 4) strncpy(mti, raw, 4);
|
||||
else strcpy(mti, "0200");
|
||||
if (n >= 10) strncpy(proc, raw + 4, 6);
|
||||
else strcpy(proc, "000000");
|
||||
amount = (n >= 22) ? atol(raw + 10) : 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, mti, chan, "IN", proc, "", amount,
|
||||
"", "00", raw, "R", bizdate) < 0) FAIL(b);
|
||||
sets_(b, T_STR1, mti);
|
||||
sets_(b, T_STR4, proc);
|
||||
setl(b, T_AMOUNT, amount);
|
||||
setl(b, T_ID1, msg_id);
|
||||
OK(b);
|
||||
}
|
||||
|
||||
/* 4. MG_BITMAP - 비트맵 계산 후 해당 전문(msg_id)에 반영. */
|
||||
void MG_BITMAP(TPSVCINFO *p)
|
||||
{
|
||||
UBFH *b = (UBFH *)p->data;
|
||||
char hex[24];
|
||||
unsigned long mask = (unsigned long)getl(b, T_ARG1); /* 필드 존재 마스크 */
|
||||
EXEC SQL BEGIN DECLARE SECTION;
|
||||
long h_id;
|
||||
char h_bitmap[40];
|
||||
EXEC SQL END DECLARE SECTION;
|
||||
|
||||
mg_bitmap_hex(mask, hex);
|
||||
sets_(b, T_STR6, hex);
|
||||
|
||||
h_id = getl(b, T_ID1);
|
||||
if (h_id > 0) {
|
||||
strncpy(h_bitmap, hex, sizeof(h_bitmap)-1); h_bitmap[sizeof(h_bitmap)-1] = 0;
|
||||
EXEC SQL UPDATE mg_msg_log SET bitmap = :h_bitmap WHERE msg_id = :h_id;
|
||||
if (sqlca.sqlcode < 0) { userlog("MG_BITMAP UPDATE FAIL [%d] %s", sqlca.sqlcode, sqlca.sqlerrm.sqlerrmc); FAIL(b); }
|
||||
}
|
||||
OK(b);
|
||||
}
|
||||
|
||||
/* 5. MG_STAN - STAN 채번 (Postgres sequence). */
|
||||
void MG_STAN(TPSVCINFO *p)
|
||||
{
|
||||
UBFH *b = (UBFH *)p->data;
|
||||
long stan = mgdb_next_stan();
|
||||
if (stan < 0) FAIL(b);
|
||||
setl(b, T_ARG1, stan);
|
||||
OK(b);
|
||||
}
|
||||
|
||||
/* 6. MG_ROUTE - 채널+전문유형 -> 목적지 라우팅 조회. */
|
||||
void MG_ROUTE(TPSVCINFO *p)
|
||||
{
|
||||
UBFH *b = (UBFH *)p->data;
|
||||
char chan[16], mti[8], dest[64];
|
||||
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");
|
||||
if (mgdb_lookup_route(chan, mti, dest) < 0) {
|
||||
userlog("MG_ROUTE 라우팅 없음 chan=%s mti=%s", chan, mti);
|
||||
setl(b, T_RC, 1);
|
||||
FAIL(b);
|
||||
}
|
||||
sets_(b, T_STR3, dest);
|
||||
setl(b, T_RC, 0);
|
||||
OK(b);
|
||||
}
|
||||
|
||||
/* 7. MG_RESEND - 전문 재전송: STAN 으로 원전문 찾아 큐에 적재. */
|
||||
void MG_RESEND(TPSVCINFO *p)
|
||||
{
|
||||
UBFH *b = (UBFH *)p->data;
|
||||
char chan[16], mti[8], raw[512], bizdate[16];
|
||||
long stan, msg_id, qid;
|
||||
|
||||
stan = getl(b, T_ARG1);
|
||||
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, raw, sizeof(raw));
|
||||
gets_(b, T_BIZDATE, bizdate, sizeof(bizdate));
|
||||
|
||||
msg_id = mgdb_find_msg_by_stan(stan);
|
||||
if (msg_id < 0) { userlog("MG_RESEND 원전문 없음 stan=%ld", stan); FAIL(b); }
|
||||
|
||||
qid = mgdb_next_queue_id(); if (qid < 0) FAIL(b);
|
||||
if (mgdb_enqueue(qid, stan, chan, mti, raw, bizdate) < 0) FAIL(b);
|
||||
setl(b, T_ID2, qid);
|
||||
userlog("MG_RESEND 재전송 큐적재 stan=%ld qid=%ld", stan, qid);
|
||||
OK(b);
|
||||
}
|
||||
|
||||
/* 8. MG_CONVERT - 요청전문 -> 응답전문 변환 (MTI +0x10) 후 송신 로그. */
|
||||
void MG_CONVERT(TPSVCINFO *p)
|
||||
{
|
||||
UBFH *b = (UBFH *)p->data;
|
||||
char chan[16], mti[8], rmti[8], raw[512], bizdate[16];
|
||||
long stan, msg_id, amount, mnum;
|
||||
|
||||
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, raw, sizeof(raw));
|
||||
gets_(b, T_BIZDATE, bizdate, sizeof(bizdate));
|
||||
amount = getl(b, T_AMOUNT);
|
||||
|
||||
mnum = atol(mti) + 10; /* 0200 -> 0210 응답 */
|
||||
sprintf(rmti, "%04ld", mnum);
|
||||
|
||||
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, rmti, chan, "OUT", "000000", "", amount,
|
||||
"", "00", raw, "S", bizdate) < 0) FAIL(b);
|
||||
sets_(b, T_STR1, rmti);
|
||||
setl(b, T_ID1, msg_id);
|
||||
OK(b);
|
||||
}
|
||||
|
||||
/* 9. MG_ERRMSG - 오류 전문 기록 (status=N, rc=입력). */
|
||||
void MG_ERRMSG(TPSVCINFO *p)
|
||||
{
|
||||
UBFH *b = (UBFH *)p->data;
|
||||
char chan[16], mti[8], raw[512], bizdate[16], rc[4];
|
||||
long stan, msg_id, rcnum;
|
||||
|
||||
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, "0210");
|
||||
gets_(b, T_STR3, raw, sizeof(raw));
|
||||
gets_(b, T_BIZDATE, bizdate, sizeof(bizdate));
|
||||
rcnum = getl(b, T_RC); if (rcnum <= 0) rcnum = 96;
|
||||
sprintf(rc, "%02ld", rcnum % 100);
|
||||
|
||||
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, mti, chan, "OUT", "000000", "", 0,
|
||||
"", rc, raw, "N", bizdate) < 0) FAIL(b);
|
||||
setl(b, T_ID1, msg_id);
|
||||
OK(b);
|
||||
}
|
||||
|
||||
/* 10. MG_LOG - 채널 당일 전문 로그 건수 조회. */
|
||||
void MG_LOG(TPSVCINFO *p)
|
||||
{
|
||||
UBFH *b = (UBFH *)p->data;
|
||||
char chan[16], bizdate[16];
|
||||
EXEC SQL BEGIN DECLARE SECTION;
|
||||
long h_cnt;
|
||||
char h_chan[16], h_bizdate[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));
|
||||
strncpy(h_chan, chan, sizeof(h_chan)-1); h_chan[sizeof(h_chan)-1] = 0;
|
||||
strncpy(h_bizdate, bizdate, sizeof(h_bizdate)-1); h_bizdate[sizeof(h_bizdate)-1] = 0;
|
||||
|
||||
EXEC SQL SELECT count(*) INTO :h_cnt FROM mg_msg_log
|
||||
WHERE channel = :h_chan AND biz_date = :h_bizdate;
|
||||
if (sqlca.sqlcode < 0) FAIL(b);
|
||||
setl(b, T_COUNT, h_cnt);
|
||||
OK(b);
|
||||
}
|
||||
|
||||
/* 11. MG_CHANAUTH - 채널 인증: UP/SIGNON 상태만 통과. */
|
||||
void MG_CHANAUTH(TPSVCINFO *p)
|
||||
{
|
||||
UBFH *b = (UBFH *)p->data;
|
||||
char chan[16], st[16];
|
||||
gets_(b, T_CHANNEL, chan, sizeof(chan)); if (chan[0] == 0) strcpy(chan, "CH01");
|
||||
if (mgdb_get_channel_status(chan, st) < 0) { setl(b, T_RC, 2); FAIL(b); }
|
||||
if (strcmp(st, "UP") == 0 || strcmp(st, "SIGNON") == 0) {
|
||||
setl(b, T_RC, 0);
|
||||
sets_(b, T_STATUS, st);
|
||||
OK(b);
|
||||
}
|
||||
userlog("MG_CHANAUTH 거부 chan=%s status=%s", chan, st);
|
||||
setl(b, T_RC, 1);
|
||||
sets_(b, T_STATUS, st);
|
||||
FAIL(b);
|
||||
}
|
||||
|
||||
/* 12. MG_SESSION - 세션 시퀀스 채번 (mg_seq SESSION). */
|
||||
void MG_SESSION(TPSVCINFO *p)
|
||||
{
|
||||
UBFH *b = (UBFH *)p->data;
|
||||
long v = mgdb_seq_bump("SESSION");
|
||||
if (v < 0) FAIL(b);
|
||||
setl(b, T_ARG2, v);
|
||||
OK(b);
|
||||
}
|
||||
|
||||
/* 13. MG_ACK - 응답 확인 (status=A, rc=00). */
|
||||
void MG_ACK(TPSVCINFO *p)
|
||||
{
|
||||
UBFH *b = (UBFH *)p->data;
|
||||
long msg_id = getl(b, T_ID1);
|
||||
if (mgdb_update_msg_rc(msg_id, "00", "A") < 0) FAIL(b);
|
||||
sets_(b, T_STATUS, "A");
|
||||
OK(b);
|
||||
}
|
||||
|
||||
/* 14. MG_NACK - 부정 응답 (status=N, rc=입력). */
|
||||
void MG_NACK(TPSVCINFO *p)
|
||||
{
|
||||
UBFH *b = (UBFH *)p->data;
|
||||
char rc[4];
|
||||
long msg_id = getl(b, T_ID1), rcnum = getl(b, T_RC);
|
||||
if (rcnum <= 0) rcnum = 91;
|
||||
sprintf(rc, "%02ld", rcnum % 100);
|
||||
if (mgdb_update_msg_rc(msg_id, rc, "N") < 0) FAIL(b);
|
||||
sets_(b, T_STATUS, "N");
|
||||
OK(b);
|
||||
}
|
||||
|
||||
/* 15. MG_ECHO - 에코 전문(0800) 처리 + 채널 UP 표시. */
|
||||
void MG_ECHO(TPSVCINFO *p)
|
||||
{
|
||||
UBFH *b = (UBFH *)p->data;
|
||||
char chan[16], bizdate[16];
|
||||
long stan, msg_id;
|
||||
gets_(b, T_CHANNEL, chan, sizeof(chan)); if (chan[0] == 0) strcpy(chan, "CH01");
|
||||
gets_(b, T_BIZDATE, bizdate, sizeof(bizdate));
|
||||
|
||||
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", "990000", "", 0,
|
||||
"", "00", "ECHO-TEST", "A", bizdate) < 0) FAIL(b);
|
||||
if (mgdb_set_channel_status(chan, "UP") < 0) FAIL(b);
|
||||
setl(b, T_ID1, msg_id);
|
||||
sets_(b, T_STATUS, "UP");
|
||||
OK(b);
|
||||
}
|
||||
|
||||
/* 16. MG_HEALTH - 게이트웨이 헬스: 활성 채널 수 조회. */
|
||||
void MG_HEALTH(TPSVCINFO *p)
|
||||
{
|
||||
UBFH *b = (UBFH *)p->data;
|
||||
EXEC SQL BEGIN DECLARE SECTION;
|
||||
long h_up;
|
||||
EXEC SQL END DECLARE SECTION;
|
||||
EXEC SQL SELECT count(*) INTO :h_up FROM mg_channel WHERE status IN ('UP','SIGNON');
|
||||
if (sqlca.sqlcode < 0) FAIL(b);
|
||||
setl(b, T_COUNT, h_up);
|
||||
setl(b, T_RC, 0);
|
||||
OK(b);
|
||||
}
|
||||
|
||||
/* 17. MG_SIGNON - 채널 사인온 + 세션키 설정. */
|
||||
void MG_SIGNON(TPSVCINFO *p)
|
||||
{
|
||||
UBFH *b = (UBFH *)p->data;
|
||||
char chan[16], key[64];
|
||||
long stan;
|
||||
gets_(b, T_CHANNEL, chan, sizeof(chan)); if (chan[0] == 0) strcpy(chan, "CH01");
|
||||
gets_(b, T_KEY1, key, sizeof(key)); if (key[0] == 0) strcpy(key, "SESSION-KEY-DFLT");
|
||||
stan = mgdb_next_stan(); if (stan < 0) FAIL(b);
|
||||
if (mgdb_set_channel_session(chan, key, stan) < 0) FAIL(b);
|
||||
sets_(b, T_STATUS, "SIGNON");
|
||||
userlog("MG_SIGNON chan=%s stan=%ld", chan, stan);
|
||||
OK(b);
|
||||
}
|
||||
|
||||
/* 18. MG_SIGNOFF - 채널 사인오프 (status=DOWN). */
|
||||
void MG_SIGNOFF(TPSVCINFO *p)
|
||||
{
|
||||
UBFH *b = (UBFH *)p->data;
|
||||
char chan[16];
|
||||
gets_(b, T_CHANNEL, chan, sizeof(chan)); if (chan[0] == 0) strcpy(chan, "CH01");
|
||||
if (mgdb_set_channel_status(chan, "DOWN") < 0) FAIL(b);
|
||||
sets_(b, T_STATUS, "DOWN");
|
||||
OK(b);
|
||||
}
|
||||
|
||||
/* 19. MG_KEYEXCH - 키 교환: 키버전 채번 + 세션키 갱신. */
|
||||
void MG_KEYEXCH(TPSVCINFO *p)
|
||||
{
|
||||
UBFH *b = (UBFH *)p->data;
|
||||
char chan[16], key[64];
|
||||
long ver, stan;
|
||||
gets_(b, T_CHANNEL, chan, sizeof(chan)); if (chan[0] == 0) strcpy(chan, "CH01");
|
||||
gets_(b, T_KEY1, key, sizeof(key)); if (key[0] == 0) strcpy(key, "NEW-KEY");
|
||||
ver = mgdb_seq_bump("KEYVER"); if (ver < 0) FAIL(b);
|
||||
stan = mgdb_next_stan(); if (stan < 0) FAIL(b);
|
||||
if (mgdb_set_channel_session(chan, key, stan) < 0) FAIL(b);
|
||||
setl(b, T_ARG2, ver);
|
||||
userlog("MG_KEYEXCH chan=%s keyver=%ld", chan, ver);
|
||||
OK(b);
|
||||
}
|
||||
|
||||
/* 20. MG_MAC - 전문 MAC 계산 후 해당 전문에 반영 (무결성). */
|
||||
void MG_MAC(TPSVCINFO *p)
|
||||
{
|
||||
UBFH *b = (UBFH *)p->data;
|
||||
char raw[512], mac[8];
|
||||
EXEC SQL BEGIN DECLARE SECTION;
|
||||
long h_id;
|
||||
char h_mac[8];
|
||||
EXEC SQL END DECLARE SECTION;
|
||||
|
||||
gets_(b, T_STR3, raw, sizeof(raw));
|
||||
mg_checksum(raw, mac);
|
||||
sets_(b, T_KEY1, mac);
|
||||
|
||||
h_id = getl(b, T_ID1);
|
||||
if (h_id > 0) {
|
||||
strncpy(h_mac, mac, sizeof(h_mac)-1); h_mac[sizeof(h_mac)-1] = 0;
|
||||
EXEC SQL UPDATE mg_msg_log SET mac = :h_mac WHERE msg_id = :h_id;
|
||||
if (sqlca.sqlcode < 0) { userlog("MG_MAC UPDATE FAIL [%d] %s", sqlca.sqlcode, sqlca.sqlerrm.sqlerrmc); FAIL(b); }
|
||||
}
|
||||
OK(b);
|
||||
}
|
||||
|
||||
/* 21. MG_PARSE - 고정길이 전문에서 처리코드 필드 추출 (순수 계산). */
|
||||
void MG_PARSE(TPSVCINFO *p)
|
||||
{
|
||||
UBFH *b = (UBFH *)p->data;
|
||||
char raw[512], mti[8], proc[8];
|
||||
long n;
|
||||
gets_(b, T_STR3, raw, sizeof(raw));
|
||||
n = (long)strlen(raw);
|
||||
memset(mti, 0, sizeof(mti)); memset(proc, 0, sizeof(proc));
|
||||
if (n >= 4) strncpy(mti, raw, 4); else strcpy(mti, "0000");
|
||||
if (n >= 10) strncpy(proc, raw + 4, 6); else strcpy(proc, "000000");
|
||||
sets_(b, T_STR1, mti);
|
||||
sets_(b, T_STR4, proc);
|
||||
setl(b, T_COUNT, n);
|
||||
OK(b);
|
||||
}
|
||||
|
||||
/* 22. MG_PACK - 고정길이 전문 조립 (순수 계산): MTI(4)+처리코드(6)+금액(12). */
|
||||
void MG_PACK(TPSVCINFO *p)
|
||||
{
|
||||
UBFH *b = (UBFH *)p->data;
|
||||
char mti[8], proc[8], raw[64];
|
||||
long amount;
|
||||
gets_(b, T_STR1, mti, sizeof(mti)); if (mti[0] == 0) strcpy(mti, "0200");
|
||||
gets_(b, T_STR4, proc, sizeof(proc)); if (proc[0] == 0) strcpy(proc, "000000");
|
||||
amount = getl(b, T_AMOUNT);
|
||||
sprintf(raw, "%.4s%.6s%012ld", mti, proc, amount);
|
||||
sets_(b, T_STR3, raw);
|
||||
setl(b, T_COUNT, (long)strlen(raw));
|
||||
OK(b);
|
||||
}
|
||||
|
||||
/* 23. MG_UNPACK - 고정길이 전문 해체 (순수 계산). */
|
||||
void MG_UNPACK(TPSVCINFO *p)
|
||||
{
|
||||
UBFH *b = (UBFH *)p->data;
|
||||
char raw[512], mti[8], proc[8];
|
||||
long amount, n;
|
||||
gets_(b, T_STR3, raw, sizeof(raw));
|
||||
n = (long)strlen(raw);
|
||||
memset(mti, 0, sizeof(mti)); memset(proc, 0, sizeof(proc));
|
||||
if (n >= 4) strncpy(mti, raw, 4); else strcpy(mti, "0000");
|
||||
if (n >= 10) strncpy(proc, raw + 4, 6); else strcpy(proc, "000000");
|
||||
amount = (n >= 22) ? atol(raw + 10) : 0;
|
||||
sets_(b, T_STR1, mti);
|
||||
sets_(b, T_STR4, proc);
|
||||
setl(b, T_AMOUNT, amount);
|
||||
OK(b);
|
||||
}
|
||||
|
||||
/* 24. MG_QUEUE - 송신 큐 적재. */
|
||||
void MG_QUEUE(TPSVCINFO *p)
|
||||
{
|
||||
UBFH *b = (UBFH *)p->data;
|
||||
char chan[16], mti[8], raw[512], bizdate[16];
|
||||
long stan, qid;
|
||||
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, raw, sizeof(raw));
|
||||
gets_(b, T_BIZDATE, bizdate, sizeof(bizdate));
|
||||
stan = getl(b, T_ARG1);
|
||||
if (stan <= 0) { stan = mgdb_next_stan(); if (stan < 0) FAIL(b); }
|
||||
qid = mgdb_next_queue_id(); if (qid < 0) FAIL(b);
|
||||
if (mgdb_enqueue(qid, stan, chan, mti, raw, bizdate) < 0) FAIL(b);
|
||||
setl(b, T_ID2, qid);
|
||||
setl(b, T_ARG1, stan);
|
||||
OK(b);
|
||||
}
|
||||
|
||||
/* 25. MG_DEQUEUE - 대기(Q) 전문 1건 인출 후 송신중(S) 전이. */
|
||||
void MG_DEQUEUE(TPSVCINFO *p)
|
||||
{
|
||||
UBFH *b = (UBFH *)p->data;
|
||||
EXEC SQL BEGIN DECLARE SECTION;
|
||||
long h_qid, h_stan;
|
||||
char h_payload[512], h_chan[16];
|
||||
EXEC SQL END DECLARE SECTION;
|
||||
|
||||
EXEC SQL SELECT queue_id, stan, channel, payload INTO :h_qid, :h_stan, :h_chan, :h_payload
|
||||
FROM mg_queue WHERE status = 'Q' ORDER BY queue_id ASC LIMIT 1;
|
||||
if (sqlca.sqlcode == 100) { setl(b, T_COUNT, 0); OK(b); }
|
||||
if (sqlca.sqlcode < 0) FAIL(b);
|
||||
|
||||
EXEC SQL UPDATE mg_queue SET status = 'S', updated_at = now() WHERE queue_id = :h_qid;
|
||||
if (sqlca.sqlcode < 0 || sqlca.sqlcode == 100) FAIL(b);
|
||||
|
||||
setl(b, T_ID2, h_qid);
|
||||
setl(b, T_ARG1, h_stan);
|
||||
sets_(b, T_CHANNEL, h_chan);
|
||||
sets_(b, T_STR3, h_payload);
|
||||
setl(b, T_COUNT, 1);
|
||||
OK(b);
|
||||
}
|
||||
|
||||
/* 26. MG_RETRY - 큐 재시도: retry_cnt +1, status=Q. */
|
||||
void MG_RETRY(TPSVCINFO *p)
|
||||
{
|
||||
UBFH *b = (UBFH *)p->data;
|
||||
EXEC SQL BEGIN DECLARE SECTION;
|
||||
long h_qid, h_retry;
|
||||
EXEC SQL END DECLARE SECTION;
|
||||
h_qid = getl(b, T_ID2);
|
||||
EXEC SQL UPDATE mg_queue SET retry_cnt = retry_cnt + 1, status = 'Q', updated_at = now()
|
||||
WHERE queue_id = :h_qid;
|
||||
if (sqlca.sqlcode < 0 || sqlca.sqlcode == 100) FAIL(b);
|
||||
EXEC SQL SELECT retry_cnt INTO :h_retry FROM mg_queue WHERE queue_id = :h_qid;
|
||||
if (sqlca.sqlcode < 0 || sqlca.sqlcode == 100) FAIL(b);
|
||||
setl(b, T_COUNT, h_retry);
|
||||
OK(b);
|
||||
}
|
||||
|
||||
/* 27. MG_STATS - 채널 당일 통계: 수신/송신/오류 건수. */
|
||||
void MG_STATS(TPSVCINFO *p)
|
||||
{
|
||||
UBFH *b = (UBFH *)p->data;
|
||||
char chan[16], bizdate[16];
|
||||
EXEC SQL BEGIN DECLARE SECTION;
|
||||
long h_in, h_out, h_err, h_sum;
|
||||
char h_chan[16], h_bizdate[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));
|
||||
strncpy(h_chan, chan, sizeof(h_chan)-1); h_chan[sizeof(h_chan)-1] = 0;
|
||||
strncpy(h_bizdate, bizdate, sizeof(h_bizdate)-1); h_bizdate[sizeof(h_bizdate)-1] = 0;
|
||||
|
||||
EXEC SQL SELECT
|
||||
coalesce(sum(CASE WHEN direction='IN' THEN 1 ELSE 0 END),0),
|
||||
coalesce(sum(CASE WHEN direction='OUT' THEN 1 ELSE 0 END),0),
|
||||
coalesce(sum(CASE WHEN rc<>'00' THEN 1 ELSE 0 END),0),
|
||||
coalesce(sum(amount),0)
|
||||
INTO :h_in, :h_out, :h_err, :h_sum
|
||||
FROM mg_msg_log WHERE channel = :h_chan AND biz_date = :h_bizdate;
|
||||
if (sqlca.sqlcode < 0) FAIL(b);
|
||||
setl(b, T_ARG1, h_in);
|
||||
setl(b, T_ARG2, h_out);
|
||||
setl(b, T_ARG3, h_err);
|
||||
setl(b, T_GROSS, h_sum);
|
||||
OK(b);
|
||||
}
|
||||
|
||||
/* 28. MG_CHANSTAT - 채널 상태 조회. */
|
||||
void MG_CHANSTAT(TPSVCINFO *p)
|
||||
{
|
||||
UBFH *b = (UBFH *)p->data;
|
||||
char chan[16], st[16];
|
||||
gets_(b, T_CHANNEL, chan, sizeof(chan)); if (chan[0] == 0) strcpy(chan, "CH01");
|
||||
if (mgdb_get_channel_status(chan, st) < 0) FAIL(b);
|
||||
sets_(b, T_STATUS, st);
|
||||
OK(b);
|
||||
}
|
||||
|
||||
/* 29. MG_STATUS - 전문 처리 상태 조회 (msg_id). */
|
||||
void MG_STATUS(TPSVCINFO *p)
|
||||
{
|
||||
UBFH *b = (UBFH *)p->data;
|
||||
long msg_id = getl(b, T_ID1);
|
||||
char st[4];
|
||||
if (mgdb_get_msg_status(msg_id, st) < 0) FAIL(b);
|
||||
sets_(b, T_STATUS, st);
|
||||
OK(b);
|
||||
}
|
||||
|
||||
/* 30. MG_CONFIRM - 전문 확정 (status=C). */
|
||||
void MG_CONFIRM(TPSVCINFO *p)
|
||||
{
|
||||
UBFH *b = (UBFH *)p->data;
|
||||
long msg_id = getl(b, T_ID1);
|
||||
if (mgdb_update_msg_rc(msg_id, "00", "C") < 0) FAIL(b);
|
||||
sets_(b, T_STATUS, "C");
|
||||
OK(b);
|
||||
}
|
||||
|
||||
/* vim: set ts=4 sw=4 et smartindent: */
|
||||
68
app/src/mm/batch/mm_grade_recalc_batch.pgc
Normal file
68
app/src/mm/batch/mm_grade_recalc_batch.pgc
Normal file
|
|
@ -0,0 +1,68 @@
|
|||
/*
|
||||
* mm_grade_recalc_batch.pgc - 가맹점 등급 재산정 배치 (누적매출 커서 + XA).
|
||||
*
|
||||
* ATMI client: tpinit/tpopen open the ECPG XA RM, tpbegin starts ONE global
|
||||
* transaction, an EXEC SQL cursor sums each merchant's 누적 매출(gross) from
|
||||
* merchant_summary, derives a grade (S/A/B/C) and UPDATEs mm_merchant_ext (via
|
||||
* the linked dbio lib), then tpcommit drives XA 2PC.
|
||||
* Usage: mm_grade_recalc_batch
|
||||
*/
|
||||
#include <stdio.h>
|
||||
#include <stdlib.h>
|
||||
#include <string.h>
|
||||
#include <atmi.h>
|
||||
#include "mm_dbio.h"
|
||||
|
||||
/* 누적매출 -> 등급 임계 (원): S>=10억, A>=3억, B>=5천만, 그 외 C */
|
||||
static const char *grade_of(long gross)
|
||||
{
|
||||
if (gross >= 1000000000L) return "S";
|
||||
if (gross >= 300000000L) return "A";
|
||||
if (gross >= 50000000L) return "B";
|
||||
return "C";
|
||||
}
|
||||
|
||||
int main(int argc, char **argv)
|
||||
{
|
||||
long rows = 0;
|
||||
(void)argc; (void)argv;
|
||||
|
||||
EXEC SQL BEGIN DECLARE SECTION;
|
||||
char h_merch[64];
|
||||
long h_gross;
|
||||
EXEC SQL END DECLARE SECTION;
|
||||
|
||||
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; }
|
||||
|
||||
/* 가맹점별 누적 매출 = merchant_summary.gross_amount 합계 */
|
||||
EXEC SQL DECLARE grc CURSOR FOR
|
||||
SELECT ms.merchant_id, coalesce(sum(ms.gross_amount),0)
|
||||
FROM merchant_summary ms
|
||||
JOIN mm_merchant_ext me ON me.merchant_id = ms.merchant_id
|
||||
WHERE me.status = 'ACTIVE'
|
||||
GROUP BY ms.merchant_id ORDER BY ms.merchant_id;
|
||||
EXEC SQL OPEN grc;
|
||||
if (sqlca.sqlcode < 0) { fprintf(stderr, "OPEN FAIL [%d] %s\n", sqlca.sqlcode, sqlca.sqlerrm.sqlerrmc); tpabort(0); return 1; }
|
||||
|
||||
for (;;) {
|
||||
EXEC SQL FETCH grc INTO :h_merch, :h_gross;
|
||||
if (sqlca.sqlcode == 100) break;
|
||||
if (sqlca.sqlcode < 0) { fprintf(stderr, "FETCH FAIL [%d] %s\n", sqlca.sqlcode, sqlca.sqlerrm.sqlerrmc); EXEC SQL CLOSE grc; tpabort(0); return 1; }
|
||||
if (mmdb_update_merchant_grade(h_merch, grade_of(h_gross)) < 0) {
|
||||
EXEC SQL CLOSE grc; tpabort(0); return 1;
|
||||
}
|
||||
if (mmdb_insert_merch_hist(h_merch, "GRADE", "", grade_of(h_gross), "배치 재산정") < 0) {
|
||||
EXEC SQL CLOSE grc; tpabort(0); return 1;
|
||||
}
|
||||
rows++;
|
||||
}
|
||||
EXEC SQL CLOSE grc;
|
||||
|
||||
if (tpcommit(0) < 0) { fprintf(stderr, "tpcommit FAIL: %s\n", tpstrerror(tperrno)); tpabort(0); return 1; }
|
||||
printf(">>> mm_grade_recalc_batch COMMIT: 재산정 가맹점=%ld\n", rows);
|
||||
|
||||
tpclose(); tpterm();
|
||||
return 0;
|
||||
}
|
||||
53
app/src/mm/batch/mm_stat_batch.pgc
Normal file
53
app/src/mm/batch/mm_stat_batch.pgc
Normal file
|
|
@ -0,0 +1,53 @@
|
|||
/*
|
||||
* mm_stat_batch.pgc - 등급별 가맹점 수 통계 배치 (커서 + XA).
|
||||
*
|
||||
* Cursors over mm_merchant_ext grouped by grade, upserting a per-grade count
|
||||
* into mm_grade_stat for the run date, all under ONE global XA transaction.
|
||||
* Usage: mm_stat_batch [YYYY-MM-DD]
|
||||
*/
|
||||
#include <stdio.h>
|
||||
#include <stdlib.h>
|
||||
#include <string.h>
|
||||
#include <atmi.h>
|
||||
|
||||
int main(int argc, char **argv)
|
||||
{
|
||||
const char *statdate = (argc > 1) ? argv[1] : "2026-07-19";
|
||||
long rows = 0;
|
||||
|
||||
EXEC SQL BEGIN DECLARE SECTION;
|
||||
char h_statdate[16], h_grade[8];
|
||||
long h_cnt;
|
||||
EXEC SQL END DECLARE SECTION;
|
||||
|
||||
strncpy(h_statdate, statdate, sizeof(h_statdate)-1); h_statdate[sizeof(h_statdate)-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 gsc CURSOR FOR
|
||||
SELECT grade, count(*) FROM mm_merchant_ext
|
||||
WHERE status = 'ACTIVE' GROUP BY grade ORDER BY grade;
|
||||
EXEC SQL OPEN gsc;
|
||||
if (sqlca.sqlcode < 0) { fprintf(stderr, "OPEN FAIL [%d] %s\n", sqlca.sqlcode, sqlca.sqlerrm.sqlerrmc); tpabort(0); return 1; }
|
||||
|
||||
for (;;) {
|
||||
EXEC SQL FETCH gsc INTO :h_grade, :h_cnt;
|
||||
if (sqlca.sqlcode == 100) break;
|
||||
if (sqlca.sqlcode < 0) { fprintf(stderr, "FETCH FAIL [%d] %s\n", sqlca.sqlcode, sqlca.sqlerrm.sqlerrmc); EXEC SQL CLOSE gsc; tpabort(0); return 1; }
|
||||
EXEC SQL INSERT INTO mm_grade_stat (stat_date, grade, merch_cnt)
|
||||
VALUES (:h_statdate, :h_grade, :h_cnt)
|
||||
ON CONFLICT (stat_date, grade) DO UPDATE
|
||||
SET merch_cnt = EXCLUDED.merch_cnt, updated_at = now();
|
||||
if (sqlca.sqlcode < 0) { fprintf(stderr, "UPSERT FAIL [%d] %s\n", sqlca.sqlcode, sqlca.sqlerrm.sqlerrmc); EXEC SQL CLOSE gsc; tpabort(0); return 1; }
|
||||
rows++;
|
||||
}
|
||||
EXEC SQL CLOSE gsc;
|
||||
|
||||
if (tpcommit(0) < 0) { fprintf(stderr, "tpcommit FAIL: %s\n", tpstrerror(tperrno)); tpabort(0); return 1; }
|
||||
printf(">>> mm_stat_batch COMMIT: statdate=%s 등급구간=%ld\n", statdate, rows);
|
||||
|
||||
tpclose(); tpterm();
|
||||
return 0;
|
||||
}
|
||||
28
app/src/mm/dbio/mm_dbio.h
Normal file
28
app/src/mm/dbio/mm_dbio.h
Normal file
|
|
@ -0,0 +1,28 @@
|
|||
/*
|
||||
* mm_dbio.h - mm 모듈 (마스터/master) DB 접근 계층 (libmmdbio.a).
|
||||
* ECPG (EXEC SQL) functions compiled into a static library and linked into
|
||||
* mm_svr and the mm batches. They run on the caller's XA branch/connection
|
||||
* (no EXEC SQL CONNECT); sqlca is the shared per-thread ECPG SQLCA.
|
||||
* Return convention: 0 = ok, -1 = SQL error (caller may also inspect sqlca).
|
||||
*/
|
||||
#ifndef MM_DBIO_H
|
||||
#define MM_DBIO_H
|
||||
|
||||
/* 가맹점 마스터 (mm_merch_dbio) */
|
||||
long mmdb_next_hist_id(void);
|
||||
int mmdb_insert_merchant(const char *merch, const char *name, const char *grade,
|
||||
const char *acct, const char *status);
|
||||
int mmdb_get_merchant(const char *merch, char *name_out /*>=64*/,
|
||||
char *grade_out /*>=8*/, char *status_out /*>=16*/);
|
||||
int mmdb_update_merchant_status(const char *merch, const char *status);
|
||||
int mmdb_update_merchant_grade(const char *merch, const char *grade);
|
||||
int mmdb_insert_merch_hist(const char *merch, const char *action,
|
||||
const char *before_val, const char *after_val,
|
||||
const char *memo);
|
||||
|
||||
/* 수수료율 마스터 (mm_feerate_dbio) */
|
||||
int mmdb_set_fee_rate(const char *merch, const char *card_type, long rate_bps);
|
||||
int mmdb_get_fee_rate(const char *merch, const char *card_type, long *bps_out);
|
||||
long mmdb_feerate_hist_count(const char *merch);
|
||||
|
||||
#endif /* MM_DBIO_H */
|
||||
77
app/src/mm/dbio/mm_feerate_dbio.pgc
Normal file
77
app/src/mm/dbio/mm_feerate_dbio.pgc
Normal file
|
|
@ -0,0 +1,77 @@
|
|||
/*
|
||||
* mm_feerate_dbio.pgc - 수수료율 마스터 upsert + 이력 적재 (libmmdbio.a).
|
||||
* ECPG functions; run on the caller's XA branch. No EXEC SQL CONNECT.
|
||||
*/
|
||||
#include <string.h>
|
||||
#include <userlog.h>
|
||||
#include "mm_dbio.h"
|
||||
|
||||
int mmdb_set_fee_rate(const char *merch, const char *card_type, long rate_bps)
|
||||
{
|
||||
EXEC SQL BEGIN DECLARE SECTION;
|
||||
long h_id, h_bps = rate_bps;
|
||||
char h_merch[64], h_ctype[16];
|
||||
EXEC SQL END DECLARE SECTION;
|
||||
|
||||
strncpy(h_merch, merch, sizeof(h_merch) - 1); h_merch[sizeof(h_merch)-1] = 0;
|
||||
strncpy(h_ctype, card_type, sizeof(h_ctype) - 1); h_ctype[sizeof(h_ctype)-1] = 0;
|
||||
|
||||
EXEC SQL INSERT INTO mm_fee_rate (merchant_id, card_type, rate_bps, eff_date)
|
||||
VALUES (:h_merch, :h_ctype, :h_bps, current_date)
|
||||
ON CONFLICT (merchant_id, card_type) DO UPDATE
|
||||
SET rate_bps = EXCLUDED.rate_bps, eff_date = current_date, updated_at = now();
|
||||
if (sqlca.sqlcode < 0) {
|
||||
userlog("mmdb_set_fee_rate FAIL [%d] %s", sqlca.sqlcode, sqlca.sqlerrm.sqlerrmc);
|
||||
return -1;
|
||||
}
|
||||
|
||||
/* 이력 적재 */
|
||||
EXEC SQL SELECT nextval('mm_feerate_hist_seq') INTO :h_id;
|
||||
if (sqlca.sqlcode < 0) return -1;
|
||||
EXEC SQL INSERT INTO mm_fee_rate_hist
|
||||
(hist_id, merchant_id, card_type, rate_bps, eff_date)
|
||||
VALUES (:h_id, :h_merch, :h_ctype, :h_bps, current_date);
|
||||
if (sqlca.sqlcode < 0) {
|
||||
userlog("mmdb_set_fee_rate hist FAIL [%d] %s", sqlca.sqlcode, sqlca.sqlerrm.sqlerrmc);
|
||||
return -1;
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
int mmdb_get_fee_rate(const char *merch, const char *card_type, long *bps_out)
|
||||
{
|
||||
EXEC SQL BEGIN DECLARE SECTION;
|
||||
long h_bps;
|
||||
char h_merch[64], h_ctype[16];
|
||||
EXEC SQL END DECLARE SECTION;
|
||||
|
||||
strncpy(h_merch, merch, sizeof(h_merch) - 1); h_merch[sizeof(h_merch)-1] = 0;
|
||||
strncpy(h_ctype, card_type, sizeof(h_ctype) - 1); h_ctype[sizeof(h_ctype)-1] = 0;
|
||||
|
||||
EXEC SQL SELECT rate_bps INTO :h_bps FROM mm_fee_rate
|
||||
WHERE merchant_id = :h_merch AND card_type = :h_ctype;
|
||||
if (sqlca.sqlcode < 0 || sqlca.sqlcode == 100) {
|
||||
userlog("mmdb_get_fee_rate FAIL [%d] %s", sqlca.sqlcode, sqlca.sqlerrm.sqlerrmc);
|
||||
return -1;
|
||||
}
|
||||
*bps_out = h_bps;
|
||||
return 0;
|
||||
}
|
||||
|
||||
long mmdb_feerate_hist_count(const char *merch)
|
||||
{
|
||||
EXEC SQL BEGIN DECLARE SECTION;
|
||||
long h_cnt;
|
||||
char h_merch[64];
|
||||
EXEC SQL END DECLARE SECTION;
|
||||
|
||||
strncpy(h_merch, merch, sizeof(h_merch) - 1); h_merch[sizeof(h_merch)-1] = 0;
|
||||
|
||||
EXEC SQL SELECT count(*) INTO :h_cnt FROM mm_fee_rate_hist
|
||||
WHERE merchant_id = :h_merch;
|
||||
if (sqlca.sqlcode < 0) {
|
||||
userlog("mmdb_feerate_hist_count FAIL [%d] %s", sqlca.sqlcode, sqlca.sqlerrm.sqlerrmc);
|
||||
return -1;
|
||||
}
|
||||
return h_cnt;
|
||||
}
|
||||
133
app/src/mm/dbio/mm_merch_dbio.pgc
Normal file
133
app/src/mm/dbio/mm_merch_dbio.pgc
Normal file
|
|
@ -0,0 +1,133 @@
|
|||
/*
|
||||
* mm_merch_dbio.pgc - 가맹점 확장 마스터 CRUD + 변경 이력 (libmmdbio.a).
|
||||
* ECPG functions; run on the caller's XA branch. No EXEC SQL CONNECT.
|
||||
*/
|
||||
#include <string.h>
|
||||
#include <userlog.h>
|
||||
#include "mm_dbio.h"
|
||||
|
||||
long mmdb_next_hist_id(void)
|
||||
{
|
||||
EXEC SQL BEGIN DECLARE SECTION;
|
||||
long h_id;
|
||||
EXEC SQL END DECLARE SECTION;
|
||||
|
||||
EXEC SQL SELECT nextval('mm_merch_hist_seq') INTO :h_id;
|
||||
if (sqlca.sqlcode < 0) {
|
||||
userlog("mmdb_next_hist_id FAIL [%d] %s", sqlca.sqlcode, sqlca.sqlerrm.sqlerrmc);
|
||||
return -1;
|
||||
}
|
||||
return h_id;
|
||||
}
|
||||
|
||||
int mmdb_insert_merchant(const char *merch, const char *name, const char *grade,
|
||||
const char *acct, const char *status)
|
||||
{
|
||||
EXEC SQL BEGIN DECLARE SECTION;
|
||||
char h_merch[64], h_name[128], h_grade[8], h_acct[64], h_status[16];
|
||||
EXEC SQL END DECLARE SECTION;
|
||||
|
||||
strncpy(h_merch, merch, sizeof(h_merch) - 1); h_merch[sizeof(h_merch)-1] = 0;
|
||||
strncpy(h_name, name, sizeof(h_name) - 1); h_name[sizeof(h_name)-1] = 0;
|
||||
strncpy(h_grade, grade, sizeof(h_grade) - 1); h_grade[sizeof(h_grade)-1] = 0;
|
||||
strncpy(h_acct, acct, sizeof(h_acct) - 1); h_acct[sizeof(h_acct)-1] = 0;
|
||||
strncpy(h_status, status, sizeof(h_status) - 1); h_status[sizeof(h_status)-1]= 0;
|
||||
|
||||
EXEC SQL INSERT INTO mm_merchant_ext
|
||||
(merchant_id, name, grade, settle_acct, status)
|
||||
VALUES (:h_merch, :h_name, :h_grade, :h_acct, :h_status)
|
||||
ON CONFLICT (merchant_id) DO UPDATE
|
||||
SET name = EXCLUDED.name, grade = EXCLUDED.grade,
|
||||
settle_acct = EXCLUDED.settle_acct, status = EXCLUDED.status,
|
||||
updated_at = now();
|
||||
if (sqlca.sqlcode < 0) {
|
||||
userlog("mmdb_insert_merchant FAIL [%d] %s", sqlca.sqlcode, sqlca.sqlerrm.sqlerrmc);
|
||||
return -1;
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
int mmdb_get_merchant(const char *merch, char *name_out, char *grade_out,
|
||||
char *status_out)
|
||||
{
|
||||
EXEC SQL BEGIN DECLARE SECTION;
|
||||
char h_merch[64], h_name[128], h_grade[8], h_status[16];
|
||||
EXEC SQL END DECLARE SECTION;
|
||||
|
||||
strncpy(h_merch, merch, sizeof(h_merch) - 1); h_merch[sizeof(h_merch)-1] = 0;
|
||||
|
||||
EXEC SQL SELECT name, grade, status INTO :h_name, :h_grade, :h_status
|
||||
FROM mm_merchant_ext WHERE merchant_id = :h_merch;
|
||||
if (sqlca.sqlcode < 0 || sqlca.sqlcode == 100) {
|
||||
userlog("mmdb_get_merchant FAIL [%d] %s", sqlca.sqlcode, sqlca.sqlerrm.sqlerrmc);
|
||||
return -1;
|
||||
}
|
||||
strncpy(name_out, h_name, 63); name_out[63] = 0;
|
||||
strncpy(grade_out, h_grade, 7); grade_out[7] = 0;
|
||||
strncpy(status_out, h_status, 15); status_out[15] = 0;
|
||||
return 0;
|
||||
}
|
||||
|
||||
int mmdb_update_merchant_status(const char *merch, const char *status)
|
||||
{
|
||||
EXEC SQL BEGIN DECLARE SECTION;
|
||||
char h_merch[64], h_status[16];
|
||||
EXEC SQL END DECLARE SECTION;
|
||||
|
||||
strncpy(h_merch, merch, sizeof(h_merch) - 1); h_merch[sizeof(h_merch)-1] = 0;
|
||||
strncpy(h_status, status, sizeof(h_status) - 1); h_status[sizeof(h_status)-1]= 0;
|
||||
|
||||
EXEC SQL UPDATE mm_merchant_ext SET status = :h_status, updated_at = now()
|
||||
WHERE merchant_id = :h_merch;
|
||||
if (sqlca.sqlcode < 0 || sqlca.sqlcode == 100) {
|
||||
userlog("mmdb_update_merchant_status FAIL [%d] %s", sqlca.sqlcode, sqlca.sqlerrm.sqlerrmc);
|
||||
return -1;
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
int mmdb_update_merchant_grade(const char *merch, const char *grade)
|
||||
{
|
||||
EXEC SQL BEGIN DECLARE SECTION;
|
||||
char h_merch[64], h_grade[8];
|
||||
EXEC SQL END DECLARE SECTION;
|
||||
|
||||
strncpy(h_merch, merch, sizeof(h_merch) - 1); h_merch[sizeof(h_merch)-1] = 0;
|
||||
strncpy(h_grade, grade, sizeof(h_grade) - 1); h_grade[sizeof(h_grade)-1] = 0;
|
||||
|
||||
EXEC SQL UPDATE mm_merchant_ext SET grade = :h_grade, updated_at = now()
|
||||
WHERE merchant_id = :h_merch;
|
||||
if (sqlca.sqlcode < 0 || sqlca.sqlcode == 100) {
|
||||
userlog("mmdb_update_merchant_grade FAIL [%d] %s", sqlca.sqlcode, sqlca.sqlerrm.sqlerrmc);
|
||||
return -1;
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
int mmdb_insert_merch_hist(const char *merch, const char *action,
|
||||
const char *before_val, const char *after_val,
|
||||
const char *memo)
|
||||
{
|
||||
EXEC SQL BEGIN DECLARE SECTION;
|
||||
long h_id;
|
||||
char h_merch[64], h_action[16], h_before[64], h_after[64], h_memo[128];
|
||||
EXEC SQL END DECLARE SECTION;
|
||||
|
||||
strncpy(h_merch, merch, sizeof(h_merch) - 1); h_merch[sizeof(h_merch)-1] = 0;
|
||||
strncpy(h_action, action, sizeof(h_action) - 1); h_action[sizeof(h_action)-1]= 0;
|
||||
strncpy(h_before, before_val, sizeof(h_before) - 1); h_before[sizeof(h_before)-1]= 0;
|
||||
strncpy(h_after, after_val, sizeof(h_after) - 1); h_after[sizeof(h_after)-1] = 0;
|
||||
strncpy(h_memo, memo, sizeof(h_memo) - 1); h_memo[sizeof(h_memo)-1] = 0;
|
||||
|
||||
EXEC SQL SELECT nextval('mm_merch_hist_seq') INTO :h_id;
|
||||
if (sqlca.sqlcode < 0) return -1;
|
||||
|
||||
EXEC SQL INSERT INTO mm_merch_hist
|
||||
(hist_id, merchant_id, action, before_val, after_val, memo)
|
||||
VALUES (:h_id, :h_merch, :h_action, :h_before, :h_after, :h_memo);
|
||||
if (sqlca.sqlcode < 0) {
|
||||
userlog("mmdb_insert_merch_hist FAIL [%d] %s", sqlca.sqlcode, sqlca.sqlerrm.sqlerrmc);
|
||||
return -1;
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
655
app/src/mm/mm_svr.pgc
Normal file
655
app/src/mm/mm_svr.pgc
Normal file
|
|
@ -0,0 +1,655 @@
|
|||
/*
|
||||
* mm_svr.pgc - mm (마스터/master) MODULE SERVER.
|
||||
*
|
||||
* ONE binary that tpadvertise()s ALL ~30 online mm services (the real Tuxedo
|
||||
* "one module server, many services" pattern). Every service is a distinct
|
||||
* 마스터(기준정보) operation with genuine EXEC SQL (directly, or via the linked
|
||||
* libmmdbio.a dbio layer) and common math from libacqcommon.a.
|
||||
*
|
||||
* XA: the connection is opened once by tpopen() (ECPG XA switch libndrxxaecpg.so);
|
||||
* there is NO EXEC SQL CONNECT. Each tpcall runs on this process's XA branch, so
|
||||
* a service sees its OWN uncommitted writes but NOT a sibling branch's.
|
||||
*/
|
||||
#include <stdio.h>
|
||||
#include <stdlib.h>
|
||||
#include <string.h>
|
||||
#include <atmi.h>
|
||||
#include <ubf.h>
|
||||
#include <userlog.h>
|
||||
#include <ndebug.h>
|
||||
#include "acq.fd.h"
|
||||
#include "mm_dbio.h"
|
||||
#include "acq_common.h"
|
||||
|
||||
/* ----- small UBF helpers ------------------------------------------------- */
|
||||
static long getl(UBFH *b, BFLDID f) { long v = 0; Bget(b, f, 0, (char *)&v, 0L); return v; }
|
||||
static void gets_(UBFH *b, BFLDID f, char *out, int cap)
|
||||
{ BFLDLEN l = cap; out[0] = 0; Bget(b, f, 0, out, &l); }
|
||||
static void setl(UBFH *b, BFLDID f, long v) { Bchg(b, f, 0, (char *)&v, 0L); }
|
||||
|
||||
#define FAIL(b) do { tpreturn(TPFAIL, 0, (char *)(b), 0L, 0L); return; } while (0)
|
||||
#define OK(b) do { tpreturn(TPSUCCESS, 0, (char *)(b), 0L, 0L); return; } while (0)
|
||||
|
||||
/* ----- service prototypes ------------------------------------------------ */
|
||||
void MM_MERCH_REG(TPSVCINFO *p); void MM_MERCH_UPD(TPSVCINFO *p);
|
||||
void MM_MERCH_INQ(TPSVCINFO *p); void MM_MERCH_DEL(TPSVCINFO *p);
|
||||
void MM_FEERATE_SET(TPSVCINFO *p); void MM_FEERATE_INQ(TPSVCINFO *p);
|
||||
void MM_FEERATE_HIST(TPSVCINFO *p); void MM_BIN_REG(TPSVCINFO *p);
|
||||
void MM_BIN_INQ(TPSVCINFO *p); void MM_LIMIT_SET(TPSVCINFO *p);
|
||||
void MM_LIMIT_INQ(TPSVCINFO *p); void MM_CODE_REG(TPSVCINFO *p);
|
||||
void MM_CODE_INQ(TPSVCINFO *p); void MM_ISSUER_REG(TPSVCINFO *p);
|
||||
void MM_ISSUER_INQ(TPSVCINFO *p); void MM_MERCH_STATUS(TPSVCINFO *p);
|
||||
void MM_MERCH_ACCT(TPSVCINFO *p); void MM_MERCH_GRADE(TPSVCINFO *p);
|
||||
void MM_TERMINAL_REG(TPSVCINFO *p); void MM_TERMINAL_INQ(TPSVCINFO *p);
|
||||
void MM_MCC_REG(TPSVCINFO *p); void MM_HOLIDAY_REG(TPSVCINFO *p);
|
||||
void MM_FX_REG(TPSVCINFO *p); void MM_FX_INQ(TPSVCINFO *p);
|
||||
void MM_MERCH_BULK(TPSVCINFO *p); void MM_VALIDATE(TPSVCINFO *p);
|
||||
void MM_MERCH_SUM(TPSVCINFO *p); void MM_CODE_CACHE(TPSVCINFO *p);
|
||||
void MM_STATUS(TPSVCINFO *p); void MM_CONFIRM(TPSVCINFO *p);
|
||||
|
||||
/* ----- advertise table (this is what "scales to 30" cleanly) ------------- */
|
||||
static struct { const char *name; void (*fn)(TPSVCINFO *); } SVCS[] = {
|
||||
{"MM_MERCH_REG", MM_MERCH_REG}, {"MM_MERCH_UPD", MM_MERCH_UPD},
|
||||
{"MM_MERCH_INQ", MM_MERCH_INQ}, {"MM_MERCH_DEL", MM_MERCH_DEL},
|
||||
{"MM_FEERATE_SET", MM_FEERATE_SET}, {"MM_FEERATE_INQ", MM_FEERATE_INQ},
|
||||
{"MM_FEERATE_HIST", MM_FEERATE_HIST}, {"MM_BIN_REG", MM_BIN_REG},
|
||||
{"MM_BIN_INQ", MM_BIN_INQ}, {"MM_LIMIT_SET", MM_LIMIT_SET},
|
||||
{"MM_LIMIT_INQ", MM_LIMIT_INQ}, {"MM_CODE_REG", MM_CODE_REG},
|
||||
{"MM_CODE_INQ", MM_CODE_INQ}, {"MM_ISSUER_REG", MM_ISSUER_REG},
|
||||
{"MM_ISSUER_INQ", MM_ISSUER_INQ}, {"MM_MERCH_STATUS", MM_MERCH_STATUS},
|
||||
{"MM_MERCH_ACCT", MM_MERCH_ACCT}, {"MM_MERCH_GRADE", MM_MERCH_GRADE},
|
||||
{"MM_TERMINAL_REG", MM_TERMINAL_REG}, {"MM_TERMINAL_INQ", MM_TERMINAL_INQ},
|
||||
{"MM_MCC_REG", MM_MCC_REG}, {"MM_HOLIDAY_REG", MM_HOLIDAY_REG},
|
||||
{"MM_FX_REG", MM_FX_REG}, {"MM_FX_INQ", MM_FX_INQ},
|
||||
{"MM_MERCH_BULK", MM_MERCH_BULK}, {"MM_VALIDATE", MM_VALIDATE},
|
||||
{"MM_MERCH_SUM", MM_MERCH_SUM}, {"MM_CODE_CACHE", MM_CODE_CACHE},
|
||||
{"MM_STATUS", MM_STATUS}, {"MM_CONFIRM", MM_CONFIRM},
|
||||
{NULL, NULL}
|
||||
};
|
||||
|
||||
int tpsvrinit(int argc, char **argv)
|
||||
{
|
||||
int i;
|
||||
if (tpopen() < 0) {
|
||||
userlog("mm_svr: tpopen 실패: %s", tpstrerror(tperrno));
|
||||
return -1;
|
||||
}
|
||||
for (i = 0; SVCS[i].name != NULL; i++) {
|
||||
if (tpadvertise((char *)SVCS[i].name, SVCS[i].fn) < 0) {
|
||||
userlog("mm_svr: tpadvertise(%s) 실패: %s", SVCS[i].name, tpstrerror(tperrno));
|
||||
return -1;
|
||||
}
|
||||
}
|
||||
userlog("mm_svr: mm 마스터 서비스 %d개 광고 완료, RM open", i);
|
||||
return 0;
|
||||
}
|
||||
|
||||
void tpsvrdone(void)
|
||||
{
|
||||
tpclose();
|
||||
userlog("mm_svr: tpsvrdone");
|
||||
}
|
||||
|
||||
/* ========================================================================= */
|
||||
/* 1. MM_MERCH_REG - 가맹점 확장 마스터 등록 (dbio upsert + 이력). */
|
||||
/* ========================================================================= */
|
||||
void MM_MERCH_REG(TPSVCINFO *p)
|
||||
{
|
||||
UBFH *b = (UBFH *)p->data;
|
||||
char merch[64], name[128], grade[8], acct[64];
|
||||
|
||||
gets_(b, T_MERCHANT, merch, sizeof(merch));
|
||||
if (merch[0] == 0) { userlog("MM_MERCH_REG: T_MERCHANT 누락"); FAIL(b); }
|
||||
gets_(b, T_STR1, name, sizeof(name)); if (name[0] == 0) strcpy(name, "무명가맹점");
|
||||
gets_(b, T_STR2, grade, sizeof(grade)); if (grade[0] == 0) strcpy(grade, "C");
|
||||
gets_(b, T_STR3, acct, sizeof(acct));
|
||||
|
||||
if (mmdb_insert_merchant(merch, name, grade, acct, "ACTIVE") < 0) FAIL(b);
|
||||
if (mmdb_insert_merch_hist(merch, "REG", "", grade, "신규 등록") < 0) FAIL(b);
|
||||
|
||||
Bchg(b, T_STATUS, 0, "ACTIVE", 0L);
|
||||
userlog("MM_MERCH_REG merch=%s grade=%s 등록", merch, grade);
|
||||
OK(b);
|
||||
}
|
||||
|
||||
/* 2. MM_MERCH_UPD - 가맹점명/계좌 수정 (직접 UPDATE + 이력). */
|
||||
void MM_MERCH_UPD(TPSVCINFO *p)
|
||||
{
|
||||
UBFH *b = (UBFH *)p->data;
|
||||
char merch[64], name[128], acct[64];
|
||||
EXEC SQL BEGIN DECLARE SECTION;
|
||||
char h_merch[64], h_name[128], h_acct[64];
|
||||
EXEC SQL END DECLARE SECTION;
|
||||
|
||||
gets_(b, T_MERCHANT, merch, sizeof(merch));
|
||||
gets_(b, T_STR1, name, sizeof(name)); if (name[0] == 0) strcpy(name, "무명가맹점");
|
||||
gets_(b, T_STR3, acct, sizeof(acct));
|
||||
strncpy(h_merch, merch, sizeof(h_merch)-1); h_merch[sizeof(h_merch)-1] = 0;
|
||||
strncpy(h_name, name, sizeof(h_name)-1); h_name[sizeof(h_name)-1] = 0;
|
||||
strncpy(h_acct, acct, sizeof(h_acct)-1); h_acct[sizeof(h_acct)-1] = 0;
|
||||
|
||||
EXEC SQL UPDATE mm_merchant_ext
|
||||
SET name = :h_name, settle_acct = :h_acct, updated_at = now()
|
||||
WHERE merchant_id = :h_merch;
|
||||
if (sqlca.sqlcode < 0 || sqlca.sqlcode == 100) {
|
||||
userlog("MM_MERCH_UPD FAIL [%d] %s", sqlca.sqlcode, sqlca.sqlerrm.sqlerrmc); FAIL(b);
|
||||
}
|
||||
if (mmdb_insert_merch_hist(merch, "UPD", "", name, "정보 수정") < 0) FAIL(b);
|
||||
OK(b);
|
||||
}
|
||||
|
||||
/* 3. MM_MERCH_INQ - 가맹점 마스터 조회 (dbio). */
|
||||
void MM_MERCH_INQ(TPSVCINFO *p)
|
||||
{
|
||||
UBFH *b = (UBFH *)p->data;
|
||||
char merch[64], name[64], grade[8], status[16];
|
||||
gets_(b, T_MERCHANT, merch, sizeof(merch));
|
||||
if (mmdb_get_merchant(merch, name, grade, status) < 0) FAIL(b);
|
||||
Bchg(b, T_STR1, 0, name, 0L);
|
||||
Bchg(b, T_STR2, 0, grade, 0L);
|
||||
Bchg(b, T_STATUS, 0, status, 0L);
|
||||
OK(b);
|
||||
}
|
||||
|
||||
/* 4. MM_MERCH_DEL - 가맹점 논리 삭제 (상태 DELETED + 이력). */
|
||||
void MM_MERCH_DEL(TPSVCINFO *p)
|
||||
{
|
||||
UBFH *b = (UBFH *)p->data;
|
||||
char merch[64];
|
||||
gets_(b, T_MERCHANT, merch, sizeof(merch));
|
||||
if (mmdb_update_merchant_status(merch, "DELETED") < 0) FAIL(b);
|
||||
if (mmdb_insert_merch_hist(merch, "DEL", "ACTIVE", "DELETED", "논리 삭제") < 0) FAIL(b);
|
||||
Bchg(b, T_STATUS, 0, "DELETED", 0L);
|
||||
OK(b);
|
||||
}
|
||||
|
||||
/* 5. MM_FEERATE_SET - 수수료율 설정 (dbio upsert + 이력). */
|
||||
void MM_FEERATE_SET(TPSVCINFO *p)
|
||||
{
|
||||
UBFH *b = (UBFH *)p->data;
|
||||
char merch[64], ctype[16];
|
||||
long bps;
|
||||
gets_(b, T_MERCHANT, merch, sizeof(merch));
|
||||
gets_(b, T_STR4, ctype, sizeof(ctype)); if (ctype[0] == 0) strcpy(ctype, "CREDIT");
|
||||
bps = getl(b, T_ARG1); if (bps <= 0) bps = 25;
|
||||
if (mmdb_set_fee_rate(merch, ctype, bps) < 0) FAIL(b);
|
||||
userlog("MM_FEERATE_SET merch=%s type=%s bps=%ld", merch, ctype, bps);
|
||||
OK(b);
|
||||
}
|
||||
|
||||
/* 6. MM_FEERATE_INQ - 수수료율 조회 (dbio). */
|
||||
void MM_FEERATE_INQ(TPSVCINFO *p)
|
||||
{
|
||||
UBFH *b = (UBFH *)p->data;
|
||||
char merch[64], ctype[16];
|
||||
long bps = 25;
|
||||
gets_(b, T_MERCHANT, merch, sizeof(merch));
|
||||
gets_(b, T_STR4, ctype, sizeof(ctype)); if (ctype[0] == 0) strcpy(ctype, "CREDIT");
|
||||
if (mmdb_get_fee_rate(merch, ctype, &bps) < 0) FAIL(b);
|
||||
setl(b, T_ARG1, bps);
|
||||
OK(b);
|
||||
}
|
||||
|
||||
/* 7. MM_FEERATE_HIST - 수수료율 변경 이력 건수 (dbio). */
|
||||
void MM_FEERATE_HIST(TPSVCINFO *p)
|
||||
{
|
||||
UBFH *b = (UBFH *)p->data;
|
||||
char merch[64];
|
||||
long cnt;
|
||||
gets_(b, T_MERCHANT, merch, sizeof(merch));
|
||||
cnt = mmdb_feerate_hist_count(merch);
|
||||
if (cnt < 0) FAIL(b);
|
||||
setl(b, T_COUNT, cnt);
|
||||
OK(b);
|
||||
}
|
||||
|
||||
/* 8. MM_BIN_REG - BIN 마스터 등록. */
|
||||
void MM_BIN_REG(TPSVCINFO *p)
|
||||
{
|
||||
UBFH *b = (UBFH *)p->data;
|
||||
char bin[16], issuer[32], brand[16];
|
||||
EXEC SQL BEGIN DECLARE SECTION;
|
||||
char h_bin[16], h_issuer[32], h_brand[16];
|
||||
EXEC SQL END DECLARE SECTION;
|
||||
gets_(b, T_KEY1, bin, sizeof(bin));
|
||||
if (bin[0] == 0) { userlog("MM_BIN_REG: T_KEY1(bin) 누락"); FAIL(b); }
|
||||
gets_(b, T_ISSUER, issuer, sizeof(issuer)); if (issuer[0] == 0) strcpy(issuer, "UNKNOWN");
|
||||
gets_(b, T_STR5, brand, sizeof(brand)); if (brand[0] == 0) strcpy(brand, "LOCAL");
|
||||
strncpy(h_bin, bin, sizeof(h_bin)-1); h_bin[sizeof(h_bin)-1] = 0;
|
||||
strncpy(h_issuer, issuer, sizeof(h_issuer)-1); h_issuer[sizeof(h_issuer)-1] = 0;
|
||||
strncpy(h_brand, brand, sizeof(h_brand)-1); h_brand[sizeof(h_brand)-1] = 0;
|
||||
|
||||
EXEC SQL INSERT INTO mm_bin (bin, issuer_cd, card_brand)
|
||||
VALUES (:h_bin, :h_issuer, :h_brand)
|
||||
ON CONFLICT (bin) DO UPDATE
|
||||
SET issuer_cd = EXCLUDED.issuer_cd, card_brand = EXCLUDED.card_brand;
|
||||
if (sqlca.sqlcode < 0) { userlog("MM_BIN_REG FAIL [%d] %s", sqlca.sqlcode, sqlca.sqlerrm.sqlerrmc); FAIL(b); }
|
||||
OK(b);
|
||||
}
|
||||
|
||||
/* 9. MM_BIN_INQ - BIN 조회. */
|
||||
void MM_BIN_INQ(TPSVCINFO *p)
|
||||
{
|
||||
UBFH *b = (UBFH *)p->data;
|
||||
char bin[16];
|
||||
EXEC SQL BEGIN DECLARE SECTION;
|
||||
char h_bin[16], h_issuer[32], h_status[16];
|
||||
EXEC SQL END DECLARE SECTION;
|
||||
gets_(b, T_KEY1, bin, sizeof(bin));
|
||||
strncpy(h_bin, bin, sizeof(h_bin)-1); h_bin[sizeof(h_bin)-1] = 0;
|
||||
EXEC SQL SELECT issuer_cd, status INTO :h_issuer, :h_status
|
||||
FROM mm_bin WHERE bin = :h_bin;
|
||||
if (sqlca.sqlcode < 0 || sqlca.sqlcode == 100) FAIL(b);
|
||||
Bchg(b, T_ISSUER, 0, h_issuer, 0L);
|
||||
Bchg(b, T_STATUS, 0, h_status, 0L);
|
||||
OK(b);
|
||||
}
|
||||
|
||||
/* 10. MM_LIMIT_SET - 가맹점 한도 설정 (upsert). */
|
||||
void MM_LIMIT_SET(TPSVCINFO *p)
|
||||
{
|
||||
UBFH *b = (UBFH *)p->data;
|
||||
EXEC SQL BEGIN DECLARE SECTION;
|
||||
long h_daily, h_monthly, h_pertxn;
|
||||
char h_merch[64];
|
||||
EXEC SQL END DECLARE SECTION;
|
||||
char merch[64];
|
||||
gets_(b, T_MERCHANT, merch, sizeof(merch));
|
||||
strncpy(h_merch, merch, sizeof(h_merch)-1); h_merch[sizeof(h_merch)-1] = 0;
|
||||
h_daily = getl(b, T_AMT1); if (h_daily <= 0) h_daily = 100000000;
|
||||
h_monthly = getl(b, T_AMT2); if (h_monthly <= 0) h_monthly = h_daily * 30;
|
||||
h_pertxn = getl(b, T_AMT3); if (h_pertxn <= 0) h_pertxn = 10000000;
|
||||
|
||||
EXEC SQL INSERT INTO mm_limit
|
||||
(merchant_id, daily_limit, monthly_limit, per_txn_limit)
|
||||
VALUES (:h_merch, :h_daily, :h_monthly, :h_pertxn)
|
||||
ON CONFLICT (merchant_id) DO UPDATE
|
||||
SET daily_limit = EXCLUDED.daily_limit, monthly_limit = EXCLUDED.monthly_limit,
|
||||
per_txn_limit = EXCLUDED.per_txn_limit, updated_at = now();
|
||||
if (sqlca.sqlcode < 0) { userlog("MM_LIMIT_SET FAIL [%d] %s", sqlca.sqlcode, sqlca.sqlerrm.sqlerrmc); FAIL(b); }
|
||||
OK(b);
|
||||
}
|
||||
|
||||
/* 11. MM_LIMIT_INQ - 가맹점 한도 조회. */
|
||||
void MM_LIMIT_INQ(TPSVCINFO *p)
|
||||
{
|
||||
UBFH *b = (UBFH *)p->data;
|
||||
EXEC SQL BEGIN DECLARE SECTION;
|
||||
long h_daily, h_monthly, h_pertxn;
|
||||
char h_merch[64];
|
||||
EXEC SQL END DECLARE SECTION;
|
||||
char merch[64];
|
||||
gets_(b, T_MERCHANT, merch, sizeof(merch));
|
||||
strncpy(h_merch, merch, sizeof(h_merch)-1); h_merch[sizeof(h_merch)-1] = 0;
|
||||
EXEC SQL SELECT daily_limit, monthly_limit, per_txn_limit
|
||||
INTO :h_daily, :h_monthly, :h_pertxn FROM mm_limit WHERE merchant_id = :h_merch;
|
||||
if (sqlca.sqlcode < 0 || sqlca.sqlcode == 100) FAIL(b);
|
||||
setl(b, T_AMT1, h_daily); setl(b, T_AMT2, h_monthly); setl(b, T_AMT3, h_pertxn);
|
||||
OK(b);
|
||||
}
|
||||
|
||||
/* 12. MM_CODE_REG - 공통코드 등록. */
|
||||
void MM_CODE_REG(TPSVCINFO *p)
|
||||
{
|
||||
UBFH *b = (UBFH *)p->data;
|
||||
char grp[32], code[32], name[64];
|
||||
EXEC SQL BEGIN DECLARE SECTION;
|
||||
char h_grp[32], h_code[32], h_name[64];
|
||||
EXEC SQL END DECLARE SECTION;
|
||||
gets_(b, T_STR1, grp, sizeof(grp)); if (grp[0] == 0) strcpy(grp, "GEN");
|
||||
gets_(b, T_KEY1, code, sizeof(code));
|
||||
if (code[0] == 0) { userlog("MM_CODE_REG: T_KEY1(code) 누락"); FAIL(b); }
|
||||
gets_(b, T_STR2, name, sizeof(name));
|
||||
strncpy(h_grp, grp, sizeof(h_grp)-1); h_grp[sizeof(h_grp)-1] = 0;
|
||||
strncpy(h_code, code, sizeof(h_code)-1); h_code[sizeof(h_code)-1] = 0;
|
||||
strncpy(h_name, name, sizeof(h_name)-1); h_name[sizeof(h_name)-1] = 0;
|
||||
EXEC SQL INSERT INTO mm_code (code_group, code, code_name)
|
||||
VALUES (:h_grp, :h_code, :h_name)
|
||||
ON CONFLICT (code_group, code) DO UPDATE SET code_name = EXCLUDED.code_name;
|
||||
if (sqlca.sqlcode < 0) { userlog("MM_CODE_REG FAIL [%d] %s", sqlca.sqlcode, sqlca.sqlerrm.sqlerrmc); FAIL(b); }
|
||||
OK(b);
|
||||
}
|
||||
|
||||
/* 13. MM_CODE_INQ - 공통코드 조회. */
|
||||
void MM_CODE_INQ(TPSVCINFO *p)
|
||||
{
|
||||
UBFH *b = (UBFH *)p->data;
|
||||
char grp[32], code[32];
|
||||
EXEC SQL BEGIN DECLARE SECTION;
|
||||
char h_grp[32], h_code[32], h_name[64], h_status[16];
|
||||
EXEC SQL END DECLARE SECTION;
|
||||
gets_(b, T_STR1, grp, sizeof(grp)); if (grp[0] == 0) strcpy(grp, "GEN");
|
||||
gets_(b, T_KEY1, code, sizeof(code));
|
||||
strncpy(h_grp, grp, sizeof(h_grp)-1); h_grp[sizeof(h_grp)-1] = 0;
|
||||
strncpy(h_code, code, sizeof(h_code)-1); h_code[sizeof(h_code)-1] = 0;
|
||||
EXEC SQL SELECT code_name, status INTO :h_name, :h_status
|
||||
FROM mm_code WHERE code_group = :h_grp AND code = :h_code;
|
||||
if (sqlca.sqlcode < 0 || sqlca.sqlcode == 100) FAIL(b);
|
||||
Bchg(b, T_STR2, 0, h_name, 0L);
|
||||
Bchg(b, T_STATUS, 0, h_status, 0L);
|
||||
OK(b);
|
||||
}
|
||||
|
||||
/* 14. MM_ISSUER_REG - 발급사 마스터 등록. */
|
||||
void MM_ISSUER_REG(TPSVCINFO *p)
|
||||
{
|
||||
UBFH *b = (UBFH *)p->data;
|
||||
char cd[32], name[64], country[8];
|
||||
EXEC SQL BEGIN DECLARE SECTION;
|
||||
char h_cd[32], h_name[64], h_country[8];
|
||||
EXEC SQL END DECLARE SECTION;
|
||||
gets_(b, T_ISSUER, cd, sizeof(cd));
|
||||
if (cd[0] == 0) { userlog("MM_ISSUER_REG: T_ISSUER 누락"); FAIL(b); }
|
||||
gets_(b, T_STR1, name, sizeof(name));
|
||||
gets_(b, T_STR2, country, sizeof(country)); if (country[0] == 0) strcpy(country, "KR");
|
||||
strncpy(h_cd, cd, sizeof(h_cd)-1); h_cd[sizeof(h_cd)-1] = 0;
|
||||
strncpy(h_name, name, sizeof(h_name)-1); h_name[sizeof(h_name)-1] = 0;
|
||||
strncpy(h_country, country, sizeof(h_country)-1); h_country[sizeof(h_country)-1] = 0;
|
||||
EXEC SQL INSERT INTO mm_issuer (issuer_cd, issuer_name, country)
|
||||
VALUES (:h_cd, :h_name, :h_country)
|
||||
ON CONFLICT (issuer_cd) DO UPDATE
|
||||
SET issuer_name = EXCLUDED.issuer_name, country = EXCLUDED.country;
|
||||
if (sqlca.sqlcode < 0) { userlog("MM_ISSUER_REG FAIL [%d] %s", sqlca.sqlcode, sqlca.sqlerrm.sqlerrmc); FAIL(b); }
|
||||
OK(b);
|
||||
}
|
||||
|
||||
/* 15. MM_ISSUER_INQ - 발급사 조회. */
|
||||
void MM_ISSUER_INQ(TPSVCINFO *p)
|
||||
{
|
||||
UBFH *b = (UBFH *)p->data;
|
||||
char cd[32];
|
||||
EXEC SQL BEGIN DECLARE SECTION;
|
||||
char h_cd[32], h_name[64], h_status[16];
|
||||
EXEC SQL END DECLARE SECTION;
|
||||
gets_(b, T_ISSUER, cd, sizeof(cd));
|
||||
strncpy(h_cd, cd, sizeof(h_cd)-1); h_cd[sizeof(h_cd)-1] = 0;
|
||||
EXEC SQL SELECT issuer_name, status INTO :h_name, :h_status
|
||||
FROM mm_issuer WHERE issuer_cd = :h_cd;
|
||||
if (sqlca.sqlcode < 0 || sqlca.sqlcode == 100) FAIL(b);
|
||||
Bchg(b, T_STR1, 0, h_name, 0L);
|
||||
Bchg(b, T_STATUS, 0, h_status, 0L);
|
||||
OK(b);
|
||||
}
|
||||
|
||||
/* 16. MM_MERCH_STATUS - 가맹점 상태 변경 (dbio + 이력). */
|
||||
void MM_MERCH_STATUS(TPSVCINFO *p)
|
||||
{
|
||||
UBFH *b = (UBFH *)p->data;
|
||||
char merch[64], status[16];
|
||||
gets_(b, T_MERCHANT, merch, sizeof(merch));
|
||||
gets_(b, T_STATUS, status, sizeof(status)); if (status[0] == 0) strcpy(status, "HOLD");
|
||||
if (mmdb_update_merchant_status(merch, status) < 0) FAIL(b);
|
||||
if (mmdb_insert_merch_hist(merch, "STATUS", "", status, "상태 변경") < 0) FAIL(b);
|
||||
OK(b);
|
||||
}
|
||||
|
||||
/* 17. MM_MERCH_ACCT - 정산계좌 변경 (직접 UPDATE + 이력). */
|
||||
void MM_MERCH_ACCT(TPSVCINFO *p)
|
||||
{
|
||||
UBFH *b = (UBFH *)p->data;
|
||||
char merch[64], acct[64];
|
||||
EXEC SQL BEGIN DECLARE SECTION;
|
||||
char h_merch[64], h_acct[64];
|
||||
EXEC SQL END DECLARE SECTION;
|
||||
gets_(b, T_MERCHANT, merch, sizeof(merch));
|
||||
gets_(b, T_STR3, acct, sizeof(acct));
|
||||
strncpy(h_merch, merch, sizeof(h_merch)-1); h_merch[sizeof(h_merch)-1] = 0;
|
||||
strncpy(h_acct, acct, sizeof(h_acct)-1); h_acct[sizeof(h_acct)-1] = 0;
|
||||
EXEC SQL UPDATE mm_merchant_ext SET settle_acct = :h_acct, updated_at = now()
|
||||
WHERE merchant_id = :h_merch;
|
||||
if (sqlca.sqlcode < 0 || sqlca.sqlcode == 100) FAIL(b);
|
||||
if (mmdb_insert_merch_hist(merch, "ACCT", "", acct, "계좌 변경") < 0) FAIL(b);
|
||||
OK(b);
|
||||
}
|
||||
|
||||
/* 18. MM_MERCH_GRADE - 가맹점 등급 변경 (dbio + 이력). */
|
||||
void MM_MERCH_GRADE(TPSVCINFO *p)
|
||||
{
|
||||
UBFH *b = (UBFH *)p->data;
|
||||
char merch[64], grade[8];
|
||||
gets_(b, T_MERCHANT, merch, sizeof(merch));
|
||||
gets_(b, T_STR2, grade, sizeof(grade)); if (grade[0] == 0) strcpy(grade, "C");
|
||||
if (mmdb_update_merchant_grade(merch, grade) < 0) FAIL(b);
|
||||
if (mmdb_insert_merch_hist(merch, "GRADE", "", grade, "등급 변경") < 0) FAIL(b);
|
||||
Bchg(b, T_STR2, 0, grade, 0L);
|
||||
OK(b);
|
||||
}
|
||||
|
||||
/* 19. MM_TERMINAL_REG - 단말 마스터 등록. */
|
||||
void MM_TERMINAL_REG(TPSVCINFO *p)
|
||||
{
|
||||
UBFH *b = (UBFH *)p->data;
|
||||
char term[32], merch[64], model[32];
|
||||
EXEC SQL BEGIN DECLARE SECTION;
|
||||
char h_term[32], h_merch[64], h_model[32];
|
||||
EXEC SQL END DECLARE SECTION;
|
||||
gets_(b, T_STR5, term, sizeof(term));
|
||||
if (term[0] == 0) { userlog("MM_TERMINAL_REG: T_STR5(terminal) 누락"); FAIL(b); }
|
||||
gets_(b, T_MERCHANT, merch, sizeof(merch));
|
||||
gets_(b, T_STR6, model, sizeof(model)); if (model[0] == 0) strcpy(model, "STD");
|
||||
strncpy(h_term, term, sizeof(h_term)-1); h_term[sizeof(h_term)-1] = 0;
|
||||
strncpy(h_merch, merch, sizeof(h_merch)-1); h_merch[sizeof(h_merch)-1] = 0;
|
||||
strncpy(h_model, model, sizeof(h_model)-1); h_model[sizeof(h_model)-1] = 0;
|
||||
EXEC SQL INSERT INTO mm_terminal (terminal_id, merchant_id, model)
|
||||
VALUES (:h_term, :h_merch, :h_model)
|
||||
ON CONFLICT (terminal_id) DO UPDATE
|
||||
SET merchant_id = EXCLUDED.merchant_id, model = EXCLUDED.model;
|
||||
if (sqlca.sqlcode < 0) { userlog("MM_TERMINAL_REG FAIL [%d] %s", sqlca.sqlcode, sqlca.sqlerrm.sqlerrmc); FAIL(b); }
|
||||
OK(b);
|
||||
}
|
||||
|
||||
/* 20. MM_TERMINAL_INQ - 단말 조회. */
|
||||
void MM_TERMINAL_INQ(TPSVCINFO *p)
|
||||
{
|
||||
UBFH *b = (UBFH *)p->data;
|
||||
char term[32];
|
||||
EXEC SQL BEGIN DECLARE SECTION;
|
||||
char h_term[32], h_merch[64], h_status[16];
|
||||
EXEC SQL END DECLARE SECTION;
|
||||
gets_(b, T_STR5, term, sizeof(term));
|
||||
strncpy(h_term, term, sizeof(h_term)-1); h_term[sizeof(h_term)-1] = 0;
|
||||
EXEC SQL SELECT merchant_id, status INTO :h_merch, :h_status
|
||||
FROM mm_terminal WHERE terminal_id = :h_term;
|
||||
if (sqlca.sqlcode < 0 || sqlca.sqlcode == 100) FAIL(b);
|
||||
Bchg(b, T_MERCHANT, 0, h_merch, 0L);
|
||||
Bchg(b, T_STATUS, 0, h_status, 0L);
|
||||
OK(b);
|
||||
}
|
||||
|
||||
/* 21. MM_MCC_REG - MCC 업종 마스터 등록. */
|
||||
void MM_MCC_REG(TPSVCINFO *p)
|
||||
{
|
||||
UBFH *b = (UBFH *)p->data;
|
||||
char mcc[16], name[64];
|
||||
EXEC SQL BEGIN DECLARE SECTION;
|
||||
long h_risk;
|
||||
char h_mcc[16], h_name[64];
|
||||
EXEC SQL END DECLARE SECTION;
|
||||
gets_(b, T_STR6, mcc, sizeof(mcc));
|
||||
if (mcc[0] == 0) { userlog("MM_MCC_REG: T_STR6(mcc) 누락"); FAIL(b); }
|
||||
gets_(b, T_STR1, name, sizeof(name));
|
||||
h_risk = getl(b, T_ARG1); if (h_risk <= 0) h_risk = 1;
|
||||
strncpy(h_mcc, mcc, sizeof(h_mcc)-1); h_mcc[sizeof(h_mcc)-1] = 0;
|
||||
strncpy(h_name, name, sizeof(h_name)-1); h_name[sizeof(h_name)-1] = 0;
|
||||
EXEC SQL INSERT INTO mm_mcc (mcc, mcc_name, risk_level)
|
||||
VALUES (:h_mcc, :h_name, :h_risk)
|
||||
ON CONFLICT (mcc) DO UPDATE
|
||||
SET mcc_name = EXCLUDED.mcc_name, risk_level = EXCLUDED.risk_level;
|
||||
if (sqlca.sqlcode < 0) { userlog("MM_MCC_REG FAIL [%d] %s", sqlca.sqlcode, sqlca.sqlerrm.sqlerrmc); FAIL(b); }
|
||||
OK(b);
|
||||
}
|
||||
|
||||
/* 22. MM_HOLIDAY_REG - 공휴일 마스터 등록. */
|
||||
void MM_HOLIDAY_REG(TPSVCINFO *p)
|
||||
{
|
||||
UBFH *b = (UBFH *)p->data;
|
||||
char hdate[16], name[64];
|
||||
EXEC SQL BEGIN DECLARE SECTION;
|
||||
char h_date[16], h_name[64];
|
||||
EXEC SQL END DECLARE SECTION;
|
||||
gets_(b, T_BIZDATE, hdate, sizeof(hdate));
|
||||
if (hdate[0] == 0) { userlog("MM_HOLIDAY_REG: T_BIZDATE 누락"); FAIL(b); }
|
||||
gets_(b, T_STR1, name, sizeof(name)); if (name[0] == 0) strcpy(name, "공휴일");
|
||||
strncpy(h_date, hdate, sizeof(h_date)-1); h_date[sizeof(h_date)-1] = 0;
|
||||
strncpy(h_name, name, sizeof(h_name)-1); h_name[sizeof(h_name)-1] = 0;
|
||||
EXEC SQL INSERT INTO mm_holiday (holiday_date, holiday_name)
|
||||
VALUES (:h_date, :h_name)
|
||||
ON CONFLICT (holiday_date) DO UPDATE SET holiday_name = EXCLUDED.holiday_name;
|
||||
if (sqlca.sqlcode < 0) { userlog("MM_HOLIDAY_REG FAIL [%d] %s", sqlca.sqlcode, sqlca.sqlerrm.sqlerrmc); FAIL(b); }
|
||||
OK(b);
|
||||
}
|
||||
|
||||
/* 23. MM_FX_REG - 환율 마스터 upsert. */
|
||||
void MM_FX_REG(TPSVCINFO *p)
|
||||
{
|
||||
UBFH *b = (UBFH *)p->data;
|
||||
char ccy[8];
|
||||
EXEC SQL BEGIN DECLARE SECTION;
|
||||
long h_rate;
|
||||
char h_ccy[8];
|
||||
EXEC SQL END DECLARE SECTION;
|
||||
gets_(b, T_CCY, ccy, sizeof(ccy)); if (ccy[0] == 0) strcpy(ccy, "USD");
|
||||
h_rate = getl(b, T_ARG2); if (h_rate <= 0) h_rate = 1350;
|
||||
strncpy(h_ccy, ccy, sizeof(h_ccy)-1); h_ccy[sizeof(h_ccy)-1] = 0;
|
||||
EXEC SQL INSERT INTO mm_fx_rate (ccy, rate_bps, base_date)
|
||||
VALUES (:h_ccy, :h_rate, current_date)
|
||||
ON CONFLICT (ccy) DO UPDATE
|
||||
SET rate_bps = EXCLUDED.rate_bps, base_date = current_date, updated_at = now();
|
||||
if (sqlca.sqlcode < 0) { userlog("MM_FX_REG FAIL [%d] %s", sqlca.sqlcode, sqlca.sqlerrm.sqlerrmc); FAIL(b); }
|
||||
OK(b);
|
||||
}
|
||||
|
||||
/* 24. MM_FX_INQ - 환율 조회 + 금액 환산 (rate_bps는 100 기준 bps). */
|
||||
void MM_FX_INQ(TPSVCINFO *p)
|
||||
{
|
||||
UBFH *b = (UBFH *)p->data;
|
||||
char ccy[8];
|
||||
long fx_amt, krw;
|
||||
EXEC SQL BEGIN DECLARE SECTION;
|
||||
long h_rate;
|
||||
char h_ccy[8];
|
||||
EXEC SQL END DECLARE SECTION;
|
||||
gets_(b, T_CCY, ccy, sizeof(ccy)); if (ccy[0] == 0) strcpy(ccy, "USD");
|
||||
fx_amt = getl(b, T_FX_AMT);
|
||||
strncpy(h_ccy, ccy, sizeof(h_ccy)-1); h_ccy[sizeof(h_ccy)-1] = 0;
|
||||
EXEC SQL SELECT rate_bps INTO :h_rate FROM mm_fx_rate WHERE ccy = :h_ccy;
|
||||
if (sqlca.sqlcode < 0 || sqlca.sqlcode == 100) FAIL(b);
|
||||
krw = fx_amt * h_rate / 100;
|
||||
setl(b, T_ARG2, h_rate);
|
||||
setl(b, T_AMOUNT, krw);
|
||||
OK(b);
|
||||
}
|
||||
|
||||
/* 25. MM_MERCH_BULK - 가맹점 대량 등록 (T_COUNT개 파생 생성). */
|
||||
void MM_MERCH_BULK(TPSVCINFO *p)
|
||||
{
|
||||
UBFH *b = (UBFH *)p->data;
|
||||
char prefix[48], grade[8];
|
||||
long n, i, made = 0;
|
||||
EXEC SQL BEGIN DECLARE SECTION;
|
||||
char h_merch[64], h_name[128], h_grade[8];
|
||||
EXEC SQL END DECLARE SECTION;
|
||||
gets_(b, T_MERCHANT, prefix, sizeof(prefix)); if (prefix[0] == 0) strcpy(prefix, "MBULK");
|
||||
gets_(b, T_STR2, grade, sizeof(grade)); if (grade[0] == 0) strcpy(grade, "C");
|
||||
n = getl(b, T_COUNT); if (n < 1) n = 1; if (n > 1000) n = 1000;
|
||||
strncpy(h_grade, grade, sizeof(h_grade)-1); h_grade[sizeof(h_grade)-1] = 0;
|
||||
for (i = 1; i <= n; i++) {
|
||||
snprintf(h_merch, sizeof(h_merch), "%s-%04ld", prefix, i);
|
||||
snprintf(h_name, sizeof(h_name), "%s 지점%ld", prefix, i);
|
||||
EXEC SQL INSERT INTO mm_merchant_ext (merchant_id, name, grade)
|
||||
VALUES (:h_merch, :h_name, :h_grade)
|
||||
ON CONFLICT (merchant_id) DO NOTHING;
|
||||
if (sqlca.sqlcode < 0) { userlog("MM_MERCH_BULK FAIL [%d] %s", sqlca.sqlcode, sqlca.sqlerrm.sqlerrmc); FAIL(b); }
|
||||
made++;
|
||||
}
|
||||
setl(b, T_COUNT, made);
|
||||
userlog("MM_MERCH_BULK prefix=%s 생성=%ld", prefix, made);
|
||||
OK(b);
|
||||
}
|
||||
|
||||
/* 26. MM_VALIDATE - 가맹점 마스터 정합성 검사 (존재+수수료율 존재). */
|
||||
void MM_VALIDATE(TPSVCINFO *p)
|
||||
{
|
||||
UBFH *b = (UBFH *)p->data;
|
||||
char merch[64];
|
||||
long rc = 0;
|
||||
EXEC SQL BEGIN DECLARE SECTION;
|
||||
long h_mcnt, h_fcnt;
|
||||
char h_merch[64];
|
||||
EXEC SQL END DECLARE SECTION;
|
||||
gets_(b, T_MERCHANT, merch, sizeof(merch));
|
||||
strncpy(h_merch, merch, sizeof(h_merch)-1); h_merch[sizeof(h_merch)-1] = 0;
|
||||
EXEC SQL SELECT count(*) INTO :h_mcnt FROM mm_merchant_ext
|
||||
WHERE merchant_id = :h_merch AND status = 'ACTIVE';
|
||||
if (sqlca.sqlcode < 0) FAIL(b);
|
||||
EXEC SQL SELECT count(*) INTO :h_fcnt FROM mm_fee_rate WHERE merchant_id = :h_merch;
|
||||
if (sqlca.sqlcode < 0) FAIL(b);
|
||||
if (h_mcnt == 0) rc = 1; /* 마스터 없음/비활성 */
|
||||
else if (h_fcnt == 0) rc = 2; /* 수수료율 미설정 */
|
||||
setl(b, T_RC, rc);
|
||||
OK(b);
|
||||
}
|
||||
|
||||
/* 27. MM_MERCH_SUM - 상태별 가맹점 수 집계 (ACTIVE 기준). */
|
||||
void MM_MERCH_SUM(TPSVCINFO *p)
|
||||
{
|
||||
UBFH *b = (UBFH *)p->data;
|
||||
char status[16];
|
||||
EXEC SQL BEGIN DECLARE SECTION;
|
||||
long h_cnt;
|
||||
char h_status[16];
|
||||
EXEC SQL END DECLARE SECTION;
|
||||
gets_(b, T_STATUS, status, sizeof(status)); if (status[0] == 0) strcpy(status, "ACTIVE");
|
||||
strncpy(h_status, status, sizeof(h_status)-1); h_status[sizeof(h_status)-1] = 0;
|
||||
EXEC SQL SELECT count(*) INTO :h_cnt FROM mm_merchant_ext WHERE status = :h_status;
|
||||
if (sqlca.sqlcode < 0) FAIL(b);
|
||||
setl(b, T_COUNT, h_cnt);
|
||||
OK(b);
|
||||
}
|
||||
|
||||
/* 28. MM_CODE_CACHE - 코드그룹의 활성 코드 수 (캐시 워밍 시뮬). */
|
||||
void MM_CODE_CACHE(TPSVCINFO *p)
|
||||
{
|
||||
UBFH *b = (UBFH *)p->data;
|
||||
char grp[32];
|
||||
EXEC SQL BEGIN DECLARE SECTION;
|
||||
long h_cnt;
|
||||
char h_grp[32];
|
||||
EXEC SQL END DECLARE SECTION;
|
||||
gets_(b, T_STR1, grp, sizeof(grp)); if (grp[0] == 0) strcpy(grp, "CARD_TYPE");
|
||||
strncpy(h_grp, grp, sizeof(h_grp)-1); h_grp[sizeof(h_grp)-1] = 0;
|
||||
EXEC SQL SELECT count(*) INTO :h_cnt FROM mm_code
|
||||
WHERE code_group = :h_grp AND status = 'ACTIVE';
|
||||
if (sqlca.sqlcode < 0) FAIL(b);
|
||||
setl(b, T_COUNT, h_cnt);
|
||||
OK(b);
|
||||
}
|
||||
|
||||
/* 29. MM_STATUS - 서비스 상태 (전체 가맹점 수, RC=0). */
|
||||
void MM_STATUS(TPSVCINFO *p)
|
||||
{
|
||||
UBFH *b = (UBFH *)p->data;
|
||||
EXEC SQL BEGIN DECLARE SECTION;
|
||||
long h_cnt;
|
||||
EXEC SQL END DECLARE SECTION;
|
||||
EXEC SQL SELECT count(*) INTO :h_cnt FROM mm_merchant_ext;
|
||||
if (sqlca.sqlcode < 0) FAIL(b);
|
||||
setl(b, T_COUNT, h_cnt);
|
||||
setl(b, T_RC, 0);
|
||||
Bchg(b, T_MSG, 0, "mm 마스터 서비스 정상", 0L);
|
||||
OK(b);
|
||||
}
|
||||
|
||||
/* 30. MM_CONFIRM - 가맹점 승인 확정 (상태 ACTIVE + 이력). */
|
||||
void MM_CONFIRM(TPSVCINFO *p)
|
||||
{
|
||||
UBFH *b = (UBFH *)p->data;
|
||||
char merch[64];
|
||||
gets_(b, T_MERCHANT, merch, sizeof(merch));
|
||||
if (mmdb_update_merchant_status(merch, "ACTIVE") < 0) FAIL(b);
|
||||
if (mmdb_insert_merch_hist(merch, "STATUS", "HOLD", "ACTIVE", "승인 확정") < 0) FAIL(b);
|
||||
Bchg(b, T_STATUS, 0, "ACTIVE", 0L);
|
||||
OK(b);
|
||||
}
|
||||
|
||||
/* vim: set ts=4 sw=4 et smartindent: */
|
||||
77
app/src/py/batch/py_filegen_batch.pgc
Normal file
77
app/src/py/batch/py_filegen_batch.pgc
Normal file
|
|
@ -0,0 +1,77 @@
|
|||
/*
|
||||
* py_filegen_batch.pgc - py 지급파일 생성 배치 (커서 -> 고정길이 파일 + XA).
|
||||
*
|
||||
* ATMI client: opens the ECPG XA RM, starts ONE global transaction, inserts a
|
||||
* py_file header, then cursors승인(A)된 지급건 for a biz_date writing one
|
||||
* fixed-length record per payout into PAYOUT_<bizdate>.dat and stamping each
|
||||
* payout with the file_id (status 'F') via the dbio layer, then tpcommit drives
|
||||
* XA 2PC. Usage: py_filegen_batch [YYYY-MM-DD]
|
||||
*/
|
||||
#include <stdio.h>
|
||||
#include <stdlib.h>
|
||||
#include <string.h>
|
||||
#include <atmi.h>
|
||||
#include "py_dbio.h"
|
||||
|
||||
int main(int argc, char **argv)
|
||||
{
|
||||
const char *bizdate = (argc > 1) ? argv[1] : "2026-07-19";
|
||||
char path[128], rec[128];
|
||||
FILE *fp;
|
||||
long rows = 0;
|
||||
|
||||
EXEC SQL BEGIN DECLARE SECTION;
|
||||
char h_bizdate[16], h_fname[64], h_acct[32];
|
||||
long h_file, h_pay, h_net, h_cnt, h_sum;
|
||||
EXEC SQL END DECLARE SECTION;
|
||||
|
||||
strncpy(h_bizdate, bizdate, sizeof(h_bizdate)-1); h_bizdate[sizeof(h_bizdate)-1] = 0;
|
||||
snprintf(h_fname, sizeof(h_fname), "PAYOUT_%s.dat", bizdate);
|
||||
snprintf(path, sizeof(path), "/tmp/%s", h_fname);
|
||||
|
||||
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 SELECT count(*), coalesce(sum(net),0) INTO :h_cnt, :h_sum
|
||||
FROM py_payout WHERE biz_date = :h_bizdate AND status = 'A';
|
||||
if (sqlca.sqlcode < 0) { fprintf(stderr, "COUNT FAIL [%d] %s\n", sqlca.sqlcode, sqlca.sqlerrm.sqlerrmc); tpabort(0); return 1; }
|
||||
|
||||
EXEC SQL SELECT nextval('py_file_seq') INTO :h_file;
|
||||
if (sqlca.sqlcode < 0) { tpabort(0); return 1; }
|
||||
EXEC SQL INSERT INTO py_file (file_id, biz_date, file_name, record_count, total_amount, status)
|
||||
VALUES (:h_file, :h_bizdate, :h_fname, :h_cnt, :h_sum, 'G');
|
||||
if (sqlca.sqlcode < 0) { fprintf(stderr, "FILE INS FAIL [%d] %s\n", sqlca.sqlcode, sqlca.sqlerrm.sqlerrmc); tpabort(0); return 1; }
|
||||
|
||||
fp = fopen(path, "w");
|
||||
if (fp == NULL) { fprintf(stderr, "fopen FAIL: %s\n", path); tpabort(0); return 1; }
|
||||
|
||||
EXEC SQL DECLARE fgc CURSOR FOR
|
||||
SELECT payout_id, account_no, net FROM py_payout
|
||||
WHERE biz_date = :h_bizdate AND status = 'A'
|
||||
ORDER BY payout_id;
|
||||
EXEC SQL OPEN fgc;
|
||||
if (sqlca.sqlcode < 0) { fprintf(stderr, "OPEN FAIL [%d] %s\n", sqlca.sqlcode, sqlca.sqlerrm.sqlerrmc); fclose(fp); tpabort(0); return 1; }
|
||||
|
||||
for (;;) {
|
||||
EXEC SQL FETCH fgc INTO :h_pay, :h_acct, :h_net;
|
||||
if (sqlca.sqlcode == 100) break;
|
||||
if (sqlca.sqlcode < 0) { fprintf(stderr, "FETCH FAIL [%d] %s\n", sqlca.sqlcode, sqlca.sqlerrm.sqlerrmc); EXEC SQL CLOSE fgc; fclose(fp); tpabort(0); return 1; }
|
||||
/* 고정길이 레코드: payout_id(12) account_no(20) net(15) = 47 bytes + NL */
|
||||
snprintf(rec, sizeof(rec), "%012ld%-20.20s%015ld\n", h_pay, h_acct, h_net);
|
||||
fputs(rec, fp);
|
||||
if (pydb_set_file(h_pay, h_file, "F") < 0) {
|
||||
EXEC SQL CLOSE fgc; fclose(fp); tpabort(0); return 1;
|
||||
}
|
||||
rows++;
|
||||
}
|
||||
EXEC SQL CLOSE fgc;
|
||||
fclose(fp);
|
||||
|
||||
if (tpcommit(0) < 0) { fprintf(stderr, "tpcommit FAIL: %s\n", tpstrerror(tperrno)); tpabort(0); return 1; }
|
||||
printf(">>> py_filegen_batch COMMIT: file=%s file_id=%ld records=%ld total=%ld\n",
|
||||
path, h_file, rows, h_sum);
|
||||
|
||||
tpclose(); tpterm();
|
||||
return 0;
|
||||
}
|
||||
52
app/src/py/batch/py_result_batch.pgc
Normal file
52
app/src/py/batch/py_result_batch.pgc
Normal file
|
|
@ -0,0 +1,52 @@
|
|||
/*
|
||||
* py_result_batch.pgc - py 지급결과 반영 배치 (커서 F->P + 집계 + XA).
|
||||
*
|
||||
* Cursors over payouts that were filed (status='F') for a biz_date, flips each
|
||||
* to 지급완료(status='P') and rolls a per-merchant payout summary via the dbio
|
||||
* layer - all under ONE global XA transaction. Usage: py_result_batch [YYYY-MM-DD]
|
||||
*/
|
||||
#include <stdio.h>
|
||||
#include <stdlib.h>
|
||||
#include <string.h>
|
||||
#include <atmi.h>
|
||||
#include "py_dbio.h"
|
||||
|
||||
int main(int argc, char **argv)
|
||||
{
|
||||
const char *bizdate = (argc > 1) ? argv[1] : "2026-07-19";
|
||||
long rows = 0;
|
||||
|
||||
EXEC SQL BEGIN DECLARE SECTION;
|
||||
char h_bizdate[16], h_merch[64];
|
||||
long h_pay, h_net;
|
||||
EXEC SQL END DECLARE SECTION;
|
||||
|
||||
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 prc CURSOR FOR
|
||||
SELECT payout_id, merchant_id, net FROM py_payout
|
||||
WHERE biz_date = :h_bizdate AND status = 'F'
|
||||
ORDER BY payout_id;
|
||||
EXEC SQL OPEN prc;
|
||||
if (sqlca.sqlcode < 0) { fprintf(stderr, "OPEN FAIL [%d] %s\n", sqlca.sqlcode, sqlca.sqlerrm.sqlerrmc); tpabort(0); return 1; }
|
||||
|
||||
for (;;) {
|
||||
EXEC SQL FETCH prc INTO :h_pay, :h_merch, :h_net;
|
||||
if (sqlca.sqlcode == 100) break;
|
||||
if (sqlca.sqlcode < 0) { fprintf(stderr, "FETCH FAIL [%d] %s\n", sqlca.sqlcode, sqlca.sqlerrm.sqlerrmc); EXEC SQL CLOSE prc; tpabort(0); return 1; }
|
||||
if (pydb_update_status(h_pay, "P") < 0) { EXEC SQL CLOSE prc; tpabort(0); return 1; }
|
||||
if (pydb_upsert_summary(h_merch, bizdate, 1, h_net) < 0) { EXEC SQL CLOSE prc; tpabort(0); return 1; }
|
||||
rows++;
|
||||
}
|
||||
EXEC SQL CLOSE prc;
|
||||
|
||||
if (tpcommit(0) < 0) { fprintf(stderr, "tpcommit FAIL: %s\n", tpstrerror(tperrno)); tpabort(0); return 1; }
|
||||
printf(">>> py_result_batch COMMIT: bizdate=%s paid(F->P)=%ld\n", bizdate, rows);
|
||||
|
||||
tpclose(); tpterm();
|
||||
return 0;
|
||||
}
|
||||
44
app/src/py/dbio/py_account_dbio.pgc
Normal file
44
app/src/py/dbio/py_account_dbio.pgc
Normal file
|
|
@ -0,0 +1,44 @@
|
|||
/*
|
||||
* py_account_dbio.pgc - 지급계좌(py_account) DB 접근 (libpydbio.a).
|
||||
*/
|
||||
#include <string.h>
|
||||
#include <userlog.h>
|
||||
#include "py_dbio.h"
|
||||
|
||||
int pydb_account_check(const char *acct, char *status_out, long *balance_out)
|
||||
{
|
||||
EXEC SQL BEGIN DECLARE SECTION;
|
||||
long h_bal;
|
||||
char h_acct[32], h_status[8];
|
||||
EXEC SQL END DECLARE SECTION;
|
||||
|
||||
strncpy(h_acct, acct, sizeof(h_acct) - 1); h_acct[sizeof(h_acct)-1] = 0;
|
||||
|
||||
EXEC SQL SELECT status, balance INTO :h_status, :h_bal
|
||||
FROM py_account WHERE account_no = :h_acct;
|
||||
if (sqlca.sqlcode < 0 || sqlca.sqlcode == 100) {
|
||||
userlog("pydb_account_check FAIL [%d] %s", sqlca.sqlcode, sqlca.sqlerrm.sqlerrmc);
|
||||
return -1;
|
||||
}
|
||||
strncpy(status_out, h_status, 7); status_out[7] = 0;
|
||||
*balance_out = h_bal;
|
||||
return 0;
|
||||
}
|
||||
|
||||
int pydb_account_add(const char *acct, long delta)
|
||||
{
|
||||
EXEC SQL BEGIN DECLARE SECTION;
|
||||
long h_delta = delta;
|
||||
char h_acct[32];
|
||||
EXEC SQL END DECLARE SECTION;
|
||||
|
||||
strncpy(h_acct, acct, sizeof(h_acct) - 1); h_acct[sizeof(h_acct)-1] = 0;
|
||||
|
||||
EXEC SQL UPDATE py_account SET balance = balance + :h_delta, updated_at = now()
|
||||
WHERE account_no = :h_acct;
|
||||
if (sqlca.sqlcode < 0 || sqlca.sqlcode == 100) {
|
||||
userlog("pydb_account_add FAIL [%d] %s", sqlca.sqlcode, sqlca.sqlerrm.sqlerrmc);
|
||||
return -1;
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
26
app/src/py/dbio/py_dbio.h
Normal file
26
app/src/py/dbio/py_dbio.h
Normal file
|
|
@ -0,0 +1,26 @@
|
|||
/*
|
||||
* py_dbio.h - py 모듈 (지급) DB 접근 계층 (libpydbio.a).
|
||||
* ECPG (EXEC SQL) functions compiled into a static library and linked into
|
||||
* py_svr and the py batches. They run on the caller's XA branch/connection
|
||||
* (no EXEC SQL CONNECT); sqlca is the shared per-thread ECPG SQLCA.
|
||||
* Return convention: 0 = ok, -1 = SQL error (caller may also inspect sqlca).
|
||||
*/
|
||||
#ifndef PY_DBIO_H
|
||||
#define PY_DBIO_H
|
||||
|
||||
/* payout */
|
||||
long pydb_next_payout_id(void);
|
||||
int pydb_insert_payout(long payout_id, const char *merch, const char *acct, long amount,
|
||||
long fee, long net, const char *status, const char *channel,
|
||||
const char *bizdate);
|
||||
int pydb_update_status(long payout_id, const char *status);
|
||||
int pydb_get_status(long payout_id, char *out /* >= 2 bytes */);
|
||||
int pydb_set_file(long payout_id, long file_id, const char *status);
|
||||
int pydb_upsert_summary(const char *merch, const char *bizdate, long cnt, long amount);
|
||||
long pydb_insert_adjust(long payout_id, long delta, const char *reason, const char *bizdate);
|
||||
|
||||
/* account */
|
||||
int pydb_account_check(const char *acct, char *status_out /* >= 8 */, long *balance_out);
|
||||
int pydb_account_add(const char *acct, long delta);
|
||||
|
||||
#endif /* PY_DBIO_H */
|
||||
144
app/src/py/dbio/py_payout_dbio.pgc
Normal file
144
app/src/py/dbio/py_payout_dbio.pgc
Normal file
|
|
@ -0,0 +1,144 @@
|
|||
/*
|
||||
* py_payout_dbio.pgc - 지급(payout)/조정/집계 DB 접근 (libpydbio.a).
|
||||
* ECPG functions; run on the caller's XA branch. No EXEC SQL CONNECT.
|
||||
*/
|
||||
#include <string.h>
|
||||
#include <userlog.h>
|
||||
#include "py_dbio.h"
|
||||
|
||||
long pydb_next_payout_id(void)
|
||||
{
|
||||
EXEC SQL BEGIN DECLARE SECTION;
|
||||
long h_id;
|
||||
EXEC SQL END DECLARE SECTION;
|
||||
|
||||
EXEC SQL SELECT nextval('py_payout_seq') INTO :h_id;
|
||||
if (sqlca.sqlcode < 0) {
|
||||
userlog("pydb_next_payout_id FAIL [%d] %s", sqlca.sqlcode, sqlca.sqlerrm.sqlerrmc);
|
||||
return -1;
|
||||
}
|
||||
return h_id;
|
||||
}
|
||||
|
||||
int pydb_insert_payout(long payout_id, const char *merch, const char *acct, long amount,
|
||||
long fee, long net, const char *status, const char *channel,
|
||||
const char *bizdate)
|
||||
{
|
||||
EXEC SQL BEGIN DECLARE SECTION;
|
||||
long h_id = payout_id, h_amount = amount, h_fee = fee, h_net = net;
|
||||
char h_merch[64], h_acct[32], h_status[8], h_channel[16], h_bizdate[16];
|
||||
EXEC SQL END DECLARE SECTION;
|
||||
|
||||
strncpy(h_merch, merch, sizeof(h_merch) - 1); h_merch[sizeof(h_merch)-1] = 0;
|
||||
strncpy(h_acct, acct, sizeof(h_acct) - 1); h_acct[sizeof(h_acct)-1] = 0;
|
||||
strncpy(h_status, status, sizeof(h_status) - 1); h_status[sizeof(h_status)-1] = 0;
|
||||
strncpy(h_channel, channel, sizeof(h_channel) - 1); h_channel[sizeof(h_channel)-1] = 0;
|
||||
strncpy(h_bizdate, bizdate, sizeof(h_bizdate) - 1); h_bizdate[sizeof(h_bizdate)-1] = 0;
|
||||
|
||||
EXEC SQL INSERT INTO py_payout
|
||||
(payout_id, merchant_id, account_no, amount, fee, net, status, channel, biz_date)
|
||||
VALUES (:h_id, :h_merch, :h_acct, :h_amount, :h_fee, :h_net, :h_status, :h_channel, :h_bizdate);
|
||||
if (sqlca.sqlcode < 0) {
|
||||
userlog("pydb_insert_payout FAIL [%d] %s", sqlca.sqlcode, sqlca.sqlerrm.sqlerrmc);
|
||||
return -1;
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
int pydb_update_status(long payout_id, const char *status)
|
||||
{
|
||||
EXEC SQL BEGIN DECLARE SECTION;
|
||||
long h_id = payout_id;
|
||||
char h_status[8];
|
||||
EXEC SQL END DECLARE SECTION;
|
||||
|
||||
strncpy(h_status, status, sizeof(h_status) - 1); h_status[sizeof(h_status)-1] = 0;
|
||||
|
||||
EXEC SQL UPDATE py_payout SET status = :h_status, updated_at = now()
|
||||
WHERE payout_id = :h_id;
|
||||
if (sqlca.sqlcode < 0 || sqlca.sqlcode == 100) {
|
||||
userlog("pydb_update_status FAIL [%d] %s", sqlca.sqlcode, sqlca.sqlerrm.sqlerrmc);
|
||||
return -1;
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
int pydb_get_status(long payout_id, char *out)
|
||||
{
|
||||
EXEC SQL BEGIN DECLARE SECTION;
|
||||
long h_id = payout_id;
|
||||
char h_status[8];
|
||||
EXEC SQL END DECLARE SECTION;
|
||||
|
||||
EXEC SQL SELECT status INTO :h_status FROM py_payout WHERE payout_id = :h_id;
|
||||
if (sqlca.sqlcode < 0 || sqlca.sqlcode == 100) {
|
||||
userlog("pydb_get_status FAIL [%d] %s", sqlca.sqlcode, sqlca.sqlerrm.sqlerrmc);
|
||||
return -1;
|
||||
}
|
||||
out[0] = h_status[0];
|
||||
out[1] = 0;
|
||||
return 0;
|
||||
}
|
||||
|
||||
int pydb_set_file(long payout_id, long file_id, const char *status)
|
||||
{
|
||||
EXEC SQL BEGIN DECLARE SECTION;
|
||||
long h_id = payout_id, h_file = file_id;
|
||||
char h_status[8];
|
||||
EXEC SQL END DECLARE SECTION;
|
||||
|
||||
strncpy(h_status, status, sizeof(h_status) - 1); h_status[sizeof(h_status)-1] = 0;
|
||||
|
||||
EXEC SQL UPDATE py_payout SET file_id = :h_file, status = :h_status, updated_at = now()
|
||||
WHERE payout_id = :h_id;
|
||||
if (sqlca.sqlcode < 0 || sqlca.sqlcode == 100) {
|
||||
userlog("pydb_set_file FAIL [%d] %s", sqlca.sqlcode, sqlca.sqlerrm.sqlerrmc);
|
||||
return -1;
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
int pydb_upsert_summary(const char *merch, const char *bizdate, long cnt, long amount)
|
||||
{
|
||||
EXEC SQL BEGIN DECLARE SECTION;
|
||||
long h_cnt = cnt, h_amt = amount;
|
||||
char h_merch[64], h_bizdate[16];
|
||||
EXEC SQL END DECLARE SECTION;
|
||||
|
||||
strncpy(h_merch, merch, sizeof(h_merch) - 1); h_merch[sizeof(h_merch)-1] = 0;
|
||||
strncpy(h_bizdate, bizdate, sizeof(h_bizdate) - 1); h_bizdate[sizeof(h_bizdate)-1] = 0;
|
||||
|
||||
EXEC SQL INSERT INTO py_summary (merchant_id, biz_date, payout_count, payout_amount)
|
||||
VALUES (:h_merch, :h_bizdate, :h_cnt, :h_amt)
|
||||
ON CONFLICT (merchant_id, biz_date) DO UPDATE
|
||||
SET payout_count = py_summary.payout_count + EXCLUDED.payout_count,
|
||||
payout_amount = py_summary.payout_amount + EXCLUDED.payout_amount,
|
||||
updated_at = now();
|
||||
if (sqlca.sqlcode < 0) {
|
||||
userlog("pydb_upsert_summary FAIL [%d] %s", sqlca.sqlcode, sqlca.sqlerrm.sqlerrmc);
|
||||
return -1;
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
long pydb_insert_adjust(long payout_id, long delta, const char *reason, const char *bizdate)
|
||||
{
|
||||
EXEC SQL BEGIN DECLARE SECTION;
|
||||
long h_id, h_pay = payout_id, h_delta = delta;
|
||||
char h_reason[128], h_bizdate[16];
|
||||
EXEC SQL END DECLARE SECTION;
|
||||
|
||||
strncpy(h_reason, reason, sizeof(h_reason) - 1); h_reason[sizeof(h_reason)-1] = 0;
|
||||
strncpy(h_bizdate, bizdate, sizeof(h_bizdate) - 1); h_bizdate[sizeof(h_bizdate)-1] = 0;
|
||||
|
||||
EXEC SQL SELECT nextval('py_adjust_seq') INTO :h_id;
|
||||
if (sqlca.sqlcode < 0) return -1;
|
||||
|
||||
EXEC SQL INSERT INTO py_adjust (adj_id, payout_id, delta, reason, biz_date)
|
||||
VALUES (:h_id, :h_pay, :h_delta, :h_reason, :h_bizdate);
|
||||
if (sqlca.sqlcode < 0) {
|
||||
userlog("pydb_insert_adjust FAIL [%d] %s", sqlca.sqlcode, sqlca.sqlerrm.sqlerrmc);
|
||||
return -1;
|
||||
}
|
||||
return h_id;
|
||||
}
|
||||
621
app/src/py/py_svr.pgc
Normal file
621
app/src/py/py_svr.pgc
Normal file
|
|
@ -0,0 +1,621 @@
|
|||
/*
|
||||
* py_svr.pgc - py (지급) MODULE SERVER.
|
||||
*
|
||||
* ONE binary that tpadvertise()s ALL ~30 online py services (the real Tuxedo
|
||||
* "one module server, many services" pattern). Every service is a distinct
|
||||
* 지급 operation with genuine EXEC SQL (directly, or via the linked libpydbio.a
|
||||
* dbio layer) and common math from libacqcommon.a.
|
||||
*
|
||||
* XA: the connection is opened once by tpopen() (ECPG XA switch); there is NO
|
||||
* EXEC SQL CONNECT. Owner-writes discipline: services in this server never
|
||||
* tpcall each other; each owns its tables on this process's XA branch.
|
||||
*/
|
||||
#include <stdio.h>
|
||||
#include <stdlib.h>
|
||||
#include <string.h>
|
||||
#include <atmi.h>
|
||||
#include <ubf.h>
|
||||
#include <userlog.h>
|
||||
#include <ndebug.h>
|
||||
#include "acq.fd.h"
|
||||
#include "py_dbio.h"
|
||||
#include "acq_common.h"
|
||||
|
||||
/* ----- small UBF helpers ------------------------------------------------- */
|
||||
static long getl(UBFH *b, BFLDID f) { long v = 0; Bget(b, f, 0, (char *)&v, 0L); return v; }
|
||||
static void gets_(UBFH *b, BFLDID f, char *out, int cap)
|
||||
{ BFLDLEN l = cap; out[0] = 0; Bget(b, f, 0, out, &l); }
|
||||
static void setl(UBFH *b, BFLDID f, long v) { Bchg(b, f, 0, (char *)&v, 0L); }
|
||||
|
||||
#define FAIL(b) do { tpreturn(TPFAIL, 0, (char *)(b), 0L, 0L); return; } while (0)
|
||||
#define OK(b) do { tpreturn(TPSUCCESS, 0, (char *)(b), 0L, 0L); return; } while (0)
|
||||
|
||||
/* ----- service prototypes ------------------------------------------------ */
|
||||
void PAY_FILEGEN(TPSVCINFO *p); void PAY_RESULT(TPSVCINFO *p);
|
||||
void PAY_REPROC(TPSVCINFO *p); void PAY_HOLD(TPSVCINFO *p);
|
||||
void PAY_RELEASE(TPSVCINFO *p); void PAY_CANCEL(TPSVCINFO *p);
|
||||
void PAY_ACCTCHK(TPSVCINFO *p); void PAY_SPLIT(TPSVCINFO *p);
|
||||
void PAY_INT(TPSVCINFO *p); void PAY_XFERLINK(TPSVCINFO *p);
|
||||
void PAY_STATUS(TPSVCINFO *p); void PAY_SCHEDULE(TPSVCINFO *p);
|
||||
void PAY_MERCHSUM(TPSVCINFO *p); void PAY_NETOFF(TPSVCINFO *p);
|
||||
void PAY_ADVANCE(TPSVCINFO *p); void PAY_RETRY(TPSVCINFO *p);
|
||||
void PAY_APPROVE(TPSVCINFO *p); void PAY_REJECT(TPSVCINFO *p);
|
||||
void PAY_RECON(TPSVCINFO *p); void PAY_STATEMENT(TPSVCINFO *p);
|
||||
void PAY_FEEDED(TPSVCINFO *p); void PAY_ADJUST(TPSVCINFO *p);
|
||||
void PAY_BULK(TPSVCINFO *p); void PAY_INQUIRY(TPSVCINFO *p);
|
||||
void PAY_HISTORY(TPSVCINFO *p); void PAY_REVERSE(TPSVCINFO *p);
|
||||
void PAY_CONFIRM(TPSVCINFO *p); void PAY_REMIT(TPSVCINFO *p);
|
||||
void PAY_CLOSE(TPSVCINFO *p); void PAY_SUMMARY(TPSVCINFO *p);
|
||||
|
||||
/* ----- advertise table (this is what "scales to 30" cleanly) ------------- */
|
||||
static struct { const char *name; void (*fn)(TPSVCINFO *); } SVCS[] = {
|
||||
{"PAY_FILEGEN", PAY_FILEGEN}, {"PAY_RESULT", PAY_RESULT},
|
||||
{"PAY_REPROC", PAY_REPROC}, {"PAY_HOLD", PAY_HOLD},
|
||||
{"PAY_RELEASE", PAY_RELEASE}, {"PAY_CANCEL", PAY_CANCEL},
|
||||
{"PAY_ACCTCHK", PAY_ACCTCHK}, {"PAY_SPLIT", PAY_SPLIT},
|
||||
{"PAY_INT", PAY_INT}, {"PAY_XFERLINK", PAY_XFERLINK},
|
||||
{"PAY_STATUS", PAY_STATUS}, {"PAY_SCHEDULE", PAY_SCHEDULE},
|
||||
{"PAY_MERCHSUM", PAY_MERCHSUM}, {"PAY_NETOFF", PAY_NETOFF},
|
||||
{"PAY_ADVANCE", PAY_ADVANCE}, {"PAY_RETRY", PAY_RETRY},
|
||||
{"PAY_APPROVE", PAY_APPROVE}, {"PAY_REJECT", PAY_REJECT},
|
||||
{"PAY_RECON", PAY_RECON}, {"PAY_STATEMENT", PAY_STATEMENT},
|
||||
{"PAY_FEEDED", PAY_FEEDED}, {"PAY_ADJUST", PAY_ADJUST},
|
||||
{"PAY_BULK", PAY_BULK}, {"PAY_INQUIRY", PAY_INQUIRY},
|
||||
{"PAY_HISTORY", PAY_HISTORY}, {"PAY_REVERSE", PAY_REVERSE},
|
||||
{"PAY_CONFIRM", PAY_CONFIRM}, {"PAY_REMIT", PAY_REMIT},
|
||||
{"PAY_CLOSE", PAY_CLOSE}, {"PAY_SUMMARY", PAY_SUMMARY},
|
||||
{NULL, NULL}
|
||||
};
|
||||
|
||||
int tpsvrinit(int argc, char **argv)
|
||||
{
|
||||
int i;
|
||||
if (tpopen() < 0) {
|
||||
userlog("py_svr: tpopen FAIL: %s", tpstrerror(tperrno));
|
||||
return -1;
|
||||
}
|
||||
for (i = 0; SVCS[i].name != NULL; i++) {
|
||||
if (tpadvertise((char *)SVCS[i].name, SVCS[i].fn) < 0) {
|
||||
userlog("py_svr: tpadvertise(%s) FAIL: %s", SVCS[i].name, tpstrerror(tperrno));
|
||||
return -1;
|
||||
}
|
||||
}
|
||||
userlog("py_svr: %d py(지급) 서비스 광고 완료, RM 오픈", i);
|
||||
return 0;
|
||||
}
|
||||
|
||||
void tpsvrdone(void)
|
||||
{
|
||||
tpclose();
|
||||
userlog("py_svr: tpsvrdone");
|
||||
}
|
||||
|
||||
/* ========================================================================= */
|
||||
/* 1. PAY_FILEGEN - 지급 파일 헤더 생성: 당일 승인(A) 지급건 집계 -> py_file. */
|
||||
/* ========================================================================= */
|
||||
void PAY_FILEGEN(TPSVCINFO *p)
|
||||
{
|
||||
UBFH *b = (UBFH *)p->data;
|
||||
char bizdate[16], fname[64];
|
||||
EXEC SQL BEGIN DECLARE SECTION;
|
||||
long h_id, h_cnt, h_sum;
|
||||
char h_bizdate[16], h_fname[64];
|
||||
EXEC SQL END DECLARE SECTION;
|
||||
|
||||
gets_(b, T_BIZDATE, bizdate, sizeof(bizdate));
|
||||
strncpy(h_bizdate, bizdate, sizeof(h_bizdate)-1); h_bizdate[sizeof(h_bizdate)-1] = 0;
|
||||
|
||||
EXEC SQL SELECT count(*), coalesce(sum(net),0) INTO :h_cnt, :h_sum
|
||||
FROM py_payout WHERE biz_date = :h_bizdate AND status = 'A';
|
||||
if (sqlca.sqlcode < 0) FAIL(b);
|
||||
snprintf(fname, sizeof(fname), "PAYOUT_%s.dat", bizdate);
|
||||
strncpy(h_fname, fname, sizeof(h_fname)-1); h_fname[sizeof(h_fname)-1] = 0;
|
||||
EXEC SQL SELECT nextval('py_file_seq') INTO :h_id;
|
||||
if (sqlca.sqlcode < 0) FAIL(b);
|
||||
EXEC SQL INSERT INTO py_file (file_id, biz_date, file_name, record_count, total_amount, status)
|
||||
VALUES (:h_id, :h_bizdate, :h_fname, :h_cnt, :h_sum, 'G');
|
||||
if (sqlca.sqlcode < 0) FAIL(b);
|
||||
setl(b, T_ID2, h_id);
|
||||
setl(b, T_COUNT, h_cnt);
|
||||
setl(b, T_GROSS, h_sum);
|
||||
Bchg(b, T_STR1, 0, fname, 0L);
|
||||
userlog("PAY_FILEGEN file_id=%ld name=%s recs=%ld amt=%ld", h_id, fname, h_cnt, h_sum);
|
||||
OK(b);
|
||||
}
|
||||
|
||||
/* 2. PAY_RESULT - 지급 결과 반영: 지급건 상태 P(지급완료). */
|
||||
void PAY_RESULT(TPSVCINFO *p)
|
||||
{
|
||||
UBFH *b = (UBFH *)p->data;
|
||||
long payout_id = getl(b, T_ID1);
|
||||
if (pydb_update_status(payout_id, "P") < 0) FAIL(b);
|
||||
Bchg(b, T_STATUS, 0, "P", 0L);
|
||||
OK(b);
|
||||
}
|
||||
|
||||
/* 3. PAY_REPROC - 재처리: 상태 R(요청)으로 되돌림. */
|
||||
void PAY_REPROC(TPSVCINFO *p)
|
||||
{
|
||||
UBFH *b = (UBFH *)p->data;
|
||||
long payout_id = getl(b, T_ID1);
|
||||
if (pydb_update_status(payout_id, "R") < 0) FAIL(b);
|
||||
Bchg(b, T_STATUS, 0, "R", 0L);
|
||||
OK(b);
|
||||
}
|
||||
|
||||
/* 4. PAY_HOLD - 지급 보류. */
|
||||
void PAY_HOLD(TPSVCINFO *p)
|
||||
{
|
||||
UBFH *b = (UBFH *)p->data;
|
||||
long payout_id = getl(b, T_ID1);
|
||||
if (pydb_update_status(payout_id, "H") < 0) FAIL(b);
|
||||
Bchg(b, T_STATUS, 0, "H", 0L);
|
||||
OK(b);
|
||||
}
|
||||
|
||||
/* 5. PAY_RELEASE - 보류 해제(요청 상태로). */
|
||||
void PAY_RELEASE(TPSVCINFO *p)
|
||||
{
|
||||
UBFH *b = (UBFH *)p->data;
|
||||
long payout_id = getl(b, T_ID1);
|
||||
if (pydb_update_status(payout_id, "R") < 0) FAIL(b);
|
||||
Bchg(b, T_STATUS, 0, "R", 0L);
|
||||
OK(b);
|
||||
}
|
||||
|
||||
/* 6. PAY_CANCEL - 지급 취소. */
|
||||
void PAY_CANCEL(TPSVCINFO *p)
|
||||
{
|
||||
UBFH *b = (UBFH *)p->data;
|
||||
char bizdate[16], reason[128];
|
||||
long payout_id, amt, adj;
|
||||
payout_id = getl(b, T_ID1);
|
||||
amt = getl(b, T_AMOUNT);
|
||||
gets_(b, T_BIZDATE, bizdate, sizeof(bizdate));
|
||||
gets_(b, T_REASON, reason, sizeof(reason)); if (reason[0] == 0) strcpy(reason, "CANCEL");
|
||||
adj = pydb_insert_adjust(payout_id, -amt, reason, bizdate);
|
||||
if (adj < 0) FAIL(b);
|
||||
if (pydb_update_status(payout_id, "C") < 0) FAIL(b);
|
||||
setl(b, T_ADJ, adj);
|
||||
Bchg(b, T_STATUS, 0, "C", 0L);
|
||||
OK(b);
|
||||
}
|
||||
|
||||
/* 7. PAY_ACCTCHK - 지급계좌 유효성 점검 (dbio). */
|
||||
void PAY_ACCTCHK(TPSVCINFO *p)
|
||||
{
|
||||
UBFH *b = (UBFH *)p->data;
|
||||
char acct[32], st[8];
|
||||
long bal = 0;
|
||||
gets_(b, T_KEY1, acct, sizeof(acct));
|
||||
if (pydb_account_check(acct, st, &bal) < 0) { setl(b, T_RC, 1); FAIL(b); }
|
||||
setl(b, T_AMT1, bal);
|
||||
Bchg(b, T_STR1, 0, st, 0L);
|
||||
setl(b, T_RC, (strcmp(st, "ACTIVE") == 0) ? 0 : 1);
|
||||
OK(b);
|
||||
}
|
||||
|
||||
/* 8. PAY_SPLIT - 지급 분할: 금액을 N건으로 나눠 지급건 생성. */
|
||||
void PAY_SPLIT(TPSVCINFO *p)
|
||||
{
|
||||
UBFH *b = (UBFH *)p->data;
|
||||
char merch[64], acct[32], bizdate[16];
|
||||
long n, amount, i, part, payout_id;
|
||||
gets_(b, T_MERCHANT, merch, sizeof(merch));
|
||||
gets_(b, T_KEY1, acct, sizeof(acct));
|
||||
amount = getl(b, T_AMOUNT);
|
||||
n = getl(b, T_COUNT); if (n < 1) n = 2;
|
||||
gets_(b, T_BIZDATE, bizdate, sizeof(bizdate));
|
||||
part = amount / n;
|
||||
for (i = 0; i < n; i++) {
|
||||
payout_id = pydb_next_payout_id();
|
||||
if (payout_id < 0) FAIL(b);
|
||||
if (pydb_insert_payout(payout_id, merch, acct, part, 0, part, "R", "SPLIT", bizdate) < 0) FAIL(b);
|
||||
}
|
||||
setl(b, T_COUNT, n);
|
||||
OK(b);
|
||||
}
|
||||
|
||||
/* 9. PAY_INT - 선지급 이자 계산 (일할, bps). */
|
||||
void PAY_INT(TPSVCINFO *p)
|
||||
{
|
||||
UBFH *b = (UBFH *)p->data;
|
||||
long principal, days, bps, interest;
|
||||
principal = getl(b, T_AMOUNT);
|
||||
days = getl(b, T_ARG1); if (days < 1) days = 1;
|
||||
bps = getl(b, T_ARG2); if (bps <= 0) bps = 15; /* 0.15%/일 */
|
||||
interest = principal * bps * days / 10000;
|
||||
setl(b, T_AMT1, interest);
|
||||
setl(b, T_NET, principal - interest);
|
||||
OK(b);
|
||||
}
|
||||
|
||||
/* 10. PAY_XFERLINK - 이체 연계 원장 기록(py_ledger). */
|
||||
void PAY_XFERLINK(TPSVCINFO *p)
|
||||
{
|
||||
UBFH *b = (UBFH *)p->data;
|
||||
char bizdate[16];
|
||||
EXEC SQL BEGIN DECLARE SECTION;
|
||||
long h_pay, h_amt;
|
||||
char h_bizdate[16];
|
||||
EXEC SQL END DECLARE SECTION;
|
||||
h_pay = getl(b, T_ID1); h_amt = getl(b, T_AMOUNT);
|
||||
gets_(b, T_BIZDATE, bizdate, sizeof(bizdate));
|
||||
strncpy(h_bizdate, bizdate, sizeof(h_bizdate)-1); h_bizdate[sizeof(h_bizdate)-1] = 0;
|
||||
EXEC SQL INSERT INTO py_ledger (ledger_id, payout_id, entry_type, amount, biz_date)
|
||||
VALUES (nextval('py_ledger_seq'), :h_pay, 'XFER', :h_amt, :h_bizdate);
|
||||
if (sqlca.sqlcode < 0) FAIL(b);
|
||||
OK(b);
|
||||
}
|
||||
|
||||
/* 11. PAY_STATUS - 지급 상태 조회 (dbio). */
|
||||
void PAY_STATUS(TPSVCINFO *p)
|
||||
{
|
||||
UBFH *b = (UBFH *)p->data;
|
||||
long payout_id = getl(b, T_ID1);
|
||||
char st[4];
|
||||
if (pydb_get_status(payout_id, st) < 0) FAIL(b);
|
||||
Bchg(b, T_STATUS, 0, st, 0L);
|
||||
OK(b);
|
||||
}
|
||||
|
||||
/* 12. PAY_SCHEDULE - 지급 예약 등록(py_schedule). */
|
||||
void PAY_SCHEDULE(TPSVCINFO *p)
|
||||
{
|
||||
UBFH *b = (UBFH *)p->data;
|
||||
char rundate[16];
|
||||
EXEC SQL BEGIN DECLARE SECTION;
|
||||
long h_id, h_pay;
|
||||
char h_rundate[16];
|
||||
EXEC SQL END DECLARE SECTION;
|
||||
h_pay = getl(b, T_ID1);
|
||||
gets_(b, T_BIZDATE, rundate, sizeof(rundate));
|
||||
strncpy(h_rundate, rundate, sizeof(h_rundate)-1); h_rundate[sizeof(h_rundate)-1] = 0;
|
||||
EXEC SQL SELECT nextval('py_schedule_seq') INTO :h_id;
|
||||
if (sqlca.sqlcode < 0) FAIL(b);
|
||||
EXEC SQL INSERT INTO py_schedule (schedule_id, payout_id, run_date, status)
|
||||
VALUES (:h_id, :h_pay, :h_rundate, 'S');
|
||||
if (sqlca.sqlcode < 0) FAIL(b);
|
||||
setl(b, T_ID2, h_id);
|
||||
OK(b);
|
||||
}
|
||||
|
||||
/* 13. PAY_MERCHSUM - 가맹점 당일 지급 집계 upsert (dbio). */
|
||||
void PAY_MERCHSUM(TPSVCINFO *p)
|
||||
{
|
||||
UBFH *b = (UBFH *)p->data;
|
||||
char merch[64], bizdate[16];
|
||||
EXEC SQL BEGIN DECLARE SECTION;
|
||||
long h_cnt, h_amt;
|
||||
char h_merch[64], h_bizdate[16];
|
||||
EXEC SQL END DECLARE SECTION;
|
||||
gets_(b, T_MERCHANT, merch, sizeof(merch));
|
||||
gets_(b, T_BIZDATE, bizdate, sizeof(bizdate));
|
||||
strncpy(h_merch, merch, sizeof(h_merch)-1); h_merch[sizeof(h_merch)-1] = 0;
|
||||
strncpy(h_bizdate, bizdate, sizeof(h_bizdate)-1); h_bizdate[sizeof(h_bizdate)-1] = 0;
|
||||
EXEC SQL SELECT count(*), coalesce(sum(net),0) INTO :h_cnt, :h_amt
|
||||
FROM py_payout WHERE merchant_id = :h_merch AND biz_date = :h_bizdate;
|
||||
if (sqlca.sqlcode < 0) FAIL(b);
|
||||
if (pydb_upsert_summary(merch, bizdate, h_cnt, h_amt) < 0) FAIL(b);
|
||||
setl(b, T_COUNT, h_cnt);
|
||||
setl(b, T_GROSS, h_amt);
|
||||
OK(b);
|
||||
}
|
||||
|
||||
/* 14. PAY_NETOFF - 상계(net-off): 지급 금액에서 차감액 반영. */
|
||||
void PAY_NETOFF(TPSVCINFO *p)
|
||||
{
|
||||
UBFH *b = (UBFH *)p->data;
|
||||
EXEC SQL BEGIN DECLARE SECTION;
|
||||
long h_pay, h_off, h_new;
|
||||
EXEC SQL END DECLARE SECTION;
|
||||
h_pay = getl(b, T_ID1);
|
||||
h_off = getl(b, T_AMOUNT);
|
||||
EXEC SQL SELECT net INTO :h_new FROM py_payout WHERE payout_id = :h_pay;
|
||||
if (sqlca.sqlcode < 0 || sqlca.sqlcode == 100) FAIL(b);
|
||||
h_new = h_new - h_off;
|
||||
EXEC SQL UPDATE py_payout SET net = :h_new, updated_at = now() WHERE payout_id = :h_pay;
|
||||
if (sqlca.sqlcode < 0) FAIL(b);
|
||||
setl(b, T_NET, h_new);
|
||||
OK(b);
|
||||
}
|
||||
|
||||
/* 15. PAY_ADVANCE - 선지급(advance) 지급건 생성. */
|
||||
void PAY_ADVANCE(TPSVCINFO *p)
|
||||
{
|
||||
UBFH *b = (UBFH *)p->data;
|
||||
char merch[64], acct[32], bizdate[16];
|
||||
long amount, fee, net, payout_id;
|
||||
gets_(b, T_MERCHANT, merch, sizeof(merch));
|
||||
gets_(b, T_KEY1, acct, sizeof(acct));
|
||||
amount = getl(b, T_AMOUNT);
|
||||
gets_(b, T_BIZDATE, bizdate, sizeof(bizdate));
|
||||
fee = acq_fee(amount, 100); /* 선지급 수수료 1% */
|
||||
net = amount - fee;
|
||||
payout_id = pydb_next_payout_id();
|
||||
if (payout_id < 0) FAIL(b);
|
||||
if (pydb_insert_payout(payout_id, merch, acct, amount, fee, net, "A", "ADVANCE", bizdate) < 0) FAIL(b);
|
||||
setl(b, T_ID1, payout_id);
|
||||
setl(b, T_FEE, fee);
|
||||
setl(b, T_NET, net);
|
||||
OK(b);
|
||||
}
|
||||
|
||||
/* 16. PAY_RETRY - 실패 지급 재시도(J/H -> R). */
|
||||
void PAY_RETRY(TPSVCINFO *p)
|
||||
{
|
||||
UBFH *b = (UBFH *)p->data;
|
||||
EXEC SQL BEGIN DECLARE SECTION;
|
||||
long h_pay;
|
||||
EXEC SQL END DECLARE SECTION;
|
||||
h_pay = getl(b, T_ID1);
|
||||
EXEC SQL UPDATE py_payout SET status = 'R', retry_count = retry_count + 1, updated_at = now()
|
||||
WHERE payout_id = :h_pay AND status IN ('J', 'H');
|
||||
if (sqlca.sqlcode < 0) FAIL(b);
|
||||
if (sqlca.sqlcode == 100) { setl(b, T_RC, 1); FAIL(b); }
|
||||
setl(b, T_RC, 0);
|
||||
Bchg(b, T_STATUS, 0, "R", 0L);
|
||||
OK(b);
|
||||
}
|
||||
|
||||
/* 17. PAY_APPROVE - 지급 승인. */
|
||||
void PAY_APPROVE(TPSVCINFO *p)
|
||||
{
|
||||
UBFH *b = (UBFH *)p->data;
|
||||
long payout_id = getl(b, T_ID1);
|
||||
if (pydb_update_status(payout_id, "A") < 0) FAIL(b);
|
||||
Bchg(b, T_STATUS, 0, "A", 0L);
|
||||
OK(b);
|
||||
}
|
||||
|
||||
/* 18. PAY_REJECT - 지급 반려 + 사유 기록. */
|
||||
void PAY_REJECT(TPSVCINFO *p)
|
||||
{
|
||||
UBFH *b = (UBFH *)p->data;
|
||||
char reason[128];
|
||||
EXEC SQL BEGIN DECLARE SECTION;
|
||||
long h_pay;
|
||||
char h_reason[128];
|
||||
EXEC SQL END DECLARE SECTION;
|
||||
h_pay = getl(b, T_ID1);
|
||||
gets_(b, T_REASON, reason, sizeof(reason)); if (reason[0] == 0) strcpy(reason, "REJECT");
|
||||
strncpy(h_reason, reason, sizeof(h_reason)-1); h_reason[sizeof(h_reason)-1] = 0;
|
||||
EXEC SQL UPDATE py_payout SET status = 'J', reject_reason = :h_reason, updated_at = now()
|
||||
WHERE payout_id = :h_pay;
|
||||
if (sqlca.sqlcode < 0 || sqlca.sqlcode == 100) FAIL(b);
|
||||
Bchg(b, T_STATUS, 0, "J", 0L);
|
||||
OK(b);
|
||||
}
|
||||
|
||||
/* 19. PAY_RECON - 지급-파일 대사: 파일에 편성된 건수/합계 확인. */
|
||||
void PAY_RECON(TPSVCINFO *p)
|
||||
{
|
||||
UBFH *b = (UBFH *)p->data;
|
||||
EXEC SQL BEGIN DECLARE SECTION;
|
||||
long h_file, h_cnt, h_sum;
|
||||
EXEC SQL END DECLARE SECTION;
|
||||
h_file = getl(b, T_ID2);
|
||||
EXEC SQL SELECT count(*), coalesce(sum(net),0) INTO :h_cnt, :h_sum
|
||||
FROM py_payout WHERE file_id = :h_file;
|
||||
if (sqlca.sqlcode < 0) FAIL(b);
|
||||
setl(b, T_COUNT, h_cnt);
|
||||
setl(b, T_GROSS, h_sum);
|
||||
OK(b);
|
||||
}
|
||||
|
||||
/* 20. PAY_STATEMENT - 가맹점 지급 명세: 기간(당일) 지급 합계 집계. */
|
||||
void PAY_STATEMENT(TPSVCINFO *p)
|
||||
{
|
||||
UBFH *b = (UBFH *)p->data;
|
||||
char merch[64], bizdate[16];
|
||||
EXEC SQL BEGIN DECLARE SECTION;
|
||||
long h_cnt, h_amt, h_fee, h_net;
|
||||
char h_merch[64], h_bizdate[16];
|
||||
EXEC SQL END DECLARE SECTION;
|
||||
gets_(b, T_MERCHANT, merch, sizeof(merch));
|
||||
gets_(b, T_BIZDATE, bizdate, sizeof(bizdate));
|
||||
strncpy(h_merch, merch, sizeof(h_merch)-1); h_merch[sizeof(h_merch)-1] = 0;
|
||||
strncpy(h_bizdate, bizdate, sizeof(h_bizdate)-1); h_bizdate[sizeof(h_bizdate)-1] = 0;
|
||||
EXEC SQL SELECT count(*), coalesce(sum(amount),0), coalesce(sum(fee),0), coalesce(sum(net),0)
|
||||
INTO :h_cnt, :h_amt, :h_fee, :h_net FROM py_payout
|
||||
WHERE merchant_id = :h_merch AND biz_date = :h_bizdate AND status = 'P';
|
||||
if (sqlca.sqlcode < 0) FAIL(b);
|
||||
setl(b, T_COUNT, h_cnt);
|
||||
setl(b, T_GROSS, h_amt);
|
||||
setl(b, T_FEE, h_fee);
|
||||
setl(b, T_NET, h_net);
|
||||
OK(b);
|
||||
}
|
||||
|
||||
/* 21. PAY_FEEDED - 수수료 공제(fee deduction): net 재계산. */
|
||||
void PAY_FEEDED(TPSVCINFO *p)
|
||||
{
|
||||
UBFH *b = (UBFH *)p->data;
|
||||
long amount, bps, fee, net;
|
||||
EXEC SQL BEGIN DECLARE SECTION;
|
||||
long h_pay, h_fee, h_net;
|
||||
EXEC SQL END DECLARE SECTION;
|
||||
h_pay = getl(b, T_ID1);
|
||||
amount = getl(b, T_AMOUNT);
|
||||
bps = getl(b, T_ARG1); if (bps <= 0) bps = 30;
|
||||
fee = acq_fee(amount, bps); net = amount - fee;
|
||||
h_fee = fee; h_net = net;
|
||||
EXEC SQL UPDATE py_payout SET fee = :h_fee, net = :h_net, updated_at = now()
|
||||
WHERE payout_id = :h_pay;
|
||||
if (sqlca.sqlcode < 0 || sqlca.sqlcode == 100) FAIL(b);
|
||||
setl(b, T_FEE, fee); setl(b, T_NET, net);
|
||||
OK(b);
|
||||
}
|
||||
|
||||
/* 22. PAY_ADJUST - 지급 조정 기록 + net 반영 (dbio + 직접 SQL). */
|
||||
void PAY_ADJUST(TPSVCINFO *p)
|
||||
{
|
||||
UBFH *b = (UBFH *)p->data;
|
||||
char bizdate[16], reason[128];
|
||||
long payout_id, delta, adj;
|
||||
EXEC SQL BEGIN DECLARE SECTION;
|
||||
long e_pay, e_delta;
|
||||
EXEC SQL END DECLARE SECTION;
|
||||
payout_id = getl(b, T_ID1);
|
||||
delta = getl(b, T_DELTA);
|
||||
gets_(b, T_BIZDATE, bizdate, sizeof(bizdate));
|
||||
gets_(b, T_REASON, reason, sizeof(reason)); if (reason[0] == 0) strcpy(reason, "ADJ");
|
||||
adj = pydb_insert_adjust(payout_id, delta, reason, bizdate);
|
||||
if (adj < 0) FAIL(b);
|
||||
e_pay = payout_id; e_delta = delta;
|
||||
EXEC SQL UPDATE py_payout SET net = net + :e_delta, updated_at = now()
|
||||
WHERE payout_id = :e_pay;
|
||||
if (sqlca.sqlcode < 0 || sqlca.sqlcode == 100) FAIL(b);
|
||||
setl(b, T_ADJ, adj);
|
||||
OK(b);
|
||||
}
|
||||
|
||||
/* 23. PAY_BULK - 대량 지급 생성: N건 반복 등록. */
|
||||
void PAY_BULK(TPSVCINFO *p)
|
||||
{
|
||||
UBFH *b = (UBFH *)p->data;
|
||||
char merch[64], acct[32], bizdate[16];
|
||||
long n, amount, i, payout_id;
|
||||
gets_(b, T_MERCHANT, merch, sizeof(merch));
|
||||
gets_(b, T_KEY1, acct, sizeof(acct));
|
||||
amount = getl(b, T_AMOUNT);
|
||||
n = getl(b, T_COUNT); if (n < 1) n = 1;
|
||||
gets_(b, T_BIZDATE, bizdate, sizeof(bizdate));
|
||||
for (i = 0; i < n; i++) {
|
||||
payout_id = pydb_next_payout_id();
|
||||
if (payout_id < 0) FAIL(b);
|
||||
if (pydb_insert_payout(payout_id, merch, acct, amount, 0, amount, "R", "BULK", bizdate) < 0) FAIL(b);
|
||||
}
|
||||
setl(b, T_COUNT, n);
|
||||
OK(b);
|
||||
}
|
||||
|
||||
/* 24. PAY_INQUIRY - 지급 단건 상세 조회. */
|
||||
void PAY_INQUIRY(TPSVCINFO *p)
|
||||
{
|
||||
UBFH *b = (UBFH *)p->data;
|
||||
char merch[64], acct[32], st[8];
|
||||
EXEC SQL BEGIN DECLARE SECTION;
|
||||
long h_pay, h_amt, h_fee, h_net;
|
||||
char h_merch[64], h_acct[32], h_status[8];
|
||||
EXEC SQL END DECLARE SECTION;
|
||||
h_pay = getl(b, T_ID1);
|
||||
EXEC SQL SELECT merchant_id, account_no, amount, fee, net, status
|
||||
INTO :h_merch, :h_acct, :h_amt, :h_fee, :h_net, :h_status
|
||||
FROM py_payout WHERE payout_id = :h_pay;
|
||||
if (sqlca.sqlcode < 0 || sqlca.sqlcode == 100) FAIL(b);
|
||||
strncpy(merch, h_merch, sizeof(merch)-1); merch[sizeof(merch)-1] = 0;
|
||||
strncpy(acct, h_acct, sizeof(acct)-1); acct[sizeof(acct)-1] = 0;
|
||||
strncpy(st, h_status, sizeof(st)-1); st[sizeof(st)-1] = 0;
|
||||
Bchg(b, T_MERCHANT, 0, merch, 0L);
|
||||
Bchg(b, T_KEY1, 0, acct, 0L);
|
||||
Bchg(b, T_STATUS, 0, st, 0L);
|
||||
setl(b, T_AMOUNT, h_amt);
|
||||
setl(b, T_FEE, h_fee);
|
||||
setl(b, T_NET, h_net);
|
||||
OK(b);
|
||||
}
|
||||
|
||||
/* 25. PAY_HISTORY - 가맹점 지급 이력 건수 조회. */
|
||||
void PAY_HISTORY(TPSVCINFO *p)
|
||||
{
|
||||
UBFH *b = (UBFH *)p->data;
|
||||
char merch[64];
|
||||
EXEC SQL BEGIN DECLARE SECTION;
|
||||
long h_cnt;
|
||||
char h_merch[64];
|
||||
EXEC SQL END DECLARE SECTION;
|
||||
gets_(b, T_MERCHANT, merch, sizeof(merch));
|
||||
strncpy(h_merch, merch, sizeof(h_merch)-1); h_merch[sizeof(h_merch)-1] = 0;
|
||||
EXEC SQL SELECT count(*) INTO :h_cnt FROM py_payout WHERE merchant_id = :h_merch;
|
||||
if (sqlca.sqlcode < 0) FAIL(b);
|
||||
setl(b, T_COUNT, h_cnt);
|
||||
OK(b);
|
||||
}
|
||||
|
||||
/* 26. PAY_REVERSE - 지급 역분개(reverse) + 계좌 잔액 원복. */
|
||||
void PAY_REVERSE(TPSVCINFO *p)
|
||||
{
|
||||
UBFH *b = (UBFH *)p->data;
|
||||
char acct[32];
|
||||
long payout_id = getl(b, T_ID1), amount = getl(b, T_AMOUNT);
|
||||
gets_(b, T_KEY1, acct, sizeof(acct));
|
||||
if (pydb_update_status(payout_id, "V") < 0) FAIL(b);
|
||||
if (acct[0] && pydb_account_add(acct, amount) < 0) FAIL(b);
|
||||
Bchg(b, T_STATUS, 0, "V", 0L);
|
||||
OK(b);
|
||||
}
|
||||
|
||||
/* 27. PAY_CONFIRM - 지급 확정(confirm): 파일 상태 C(확정). */
|
||||
void PAY_CONFIRM(TPSVCINFO *p)
|
||||
{
|
||||
UBFH *b = (UBFH *)p->data;
|
||||
EXEC SQL BEGIN DECLARE SECTION;
|
||||
long h_file;
|
||||
EXEC SQL END DECLARE SECTION;
|
||||
h_file = getl(b, T_ID2);
|
||||
EXEC SQL UPDATE py_file SET status = 'C', confirmed_at = now() WHERE file_id = :h_file;
|
||||
if (sqlca.sqlcode < 0 || sqlca.sqlcode == 100) FAIL(b);
|
||||
OK(b);
|
||||
}
|
||||
|
||||
/* 28. PAY_REMIT - 실지급 송금: 상태 P + 계좌 잔액 차감. */
|
||||
void PAY_REMIT(TPSVCINFO *p)
|
||||
{
|
||||
UBFH *b = (UBFH *)p->data;
|
||||
char acct[32], st[8];
|
||||
long payout_id, net, bal = 0;
|
||||
payout_id = getl(b, T_ID1);
|
||||
net = getl(b, T_NET);
|
||||
gets_(b, T_KEY1, acct, sizeof(acct));
|
||||
if (pydb_account_check(acct, st, &bal) < 0) FAIL(b);
|
||||
if (bal < net) { setl(b, T_RC, 1); FAIL(b); }
|
||||
if (pydb_account_add(acct, -net) < 0) FAIL(b);
|
||||
if (pydb_update_status(payout_id, "P") < 0) FAIL(b);
|
||||
setl(b, T_RC, 0);
|
||||
Bchg(b, T_STATUS, 0, "P", 0L);
|
||||
OK(b);
|
||||
}
|
||||
|
||||
/* 29. PAY_CLOSE - 일마감: 당일 지급완료(P)건 상태 X(마감)로 일괄 전환. */
|
||||
void PAY_CLOSE(TPSVCINFO *p)
|
||||
{
|
||||
UBFH *b = (UBFH *)p->data;
|
||||
char bizdate[16];
|
||||
EXEC SQL BEGIN DECLARE SECTION;
|
||||
char h_bizdate[16];
|
||||
long h_cnt;
|
||||
EXEC SQL END DECLARE SECTION;
|
||||
gets_(b, T_BIZDATE, bizdate, sizeof(bizdate));
|
||||
strncpy(h_bizdate, bizdate, sizeof(h_bizdate)-1); h_bizdate[sizeof(h_bizdate)-1] = 0;
|
||||
EXEC SQL UPDATE py_payout SET status = 'X', updated_at = now()
|
||||
WHERE biz_date = :h_bizdate AND status = 'P';
|
||||
if (sqlca.sqlcode < 0) FAIL(b);
|
||||
h_cnt = sqlca.sqlerrd[2];
|
||||
setl(b, T_COUNT, h_cnt);
|
||||
OK(b);
|
||||
}
|
||||
|
||||
/* 30. PAY_SUMMARY - 가맹점 일별 지급 집계 조회(py_summary). */
|
||||
void PAY_SUMMARY(TPSVCINFO *p)
|
||||
{
|
||||
UBFH *b = (UBFH *)p->data;
|
||||
char merch[64], bizdate[16];
|
||||
EXEC SQL BEGIN DECLARE SECTION;
|
||||
long h_cnt, h_amt;
|
||||
char h_merch[64], h_bizdate[16];
|
||||
EXEC SQL END DECLARE SECTION;
|
||||
gets_(b, T_MERCHANT, merch, sizeof(merch));
|
||||
gets_(b, T_BIZDATE, bizdate, sizeof(bizdate));
|
||||
strncpy(h_merch, merch, sizeof(h_merch)-1); h_merch[sizeof(h_merch)-1] = 0;
|
||||
strncpy(h_bizdate, bizdate, sizeof(h_bizdate)-1); h_bizdate[sizeof(h_bizdate)-1] = 0;
|
||||
EXEC SQL SELECT coalesce(payout_count,0), coalesce(payout_amount,0) INTO :h_cnt, :h_amt
|
||||
FROM py_summary WHERE merchant_id = :h_merch AND biz_date = :h_bizdate;
|
||||
if (sqlca.sqlcode < 0) FAIL(b);
|
||||
if (sqlca.sqlcode == 100) { h_cnt = 0; h_amt = 0; }
|
||||
setl(b, T_COUNT, h_cnt);
|
||||
setl(b, T_GROSS, h_amt);
|
||||
OK(b);
|
||||
}
|
||||
|
||||
/* vim: set ts=4 sw=4 et smartindent: */
|
||||
59
app/src/rc/batch/rc_diff_batch.pgc
Normal file
59
app/src/rc/batch/rc_diff_batch.pgc
Normal file
|
|
@ -0,0 +1,59 @@
|
|||
/*
|
||||
* rc_diff_batch.pgc - rc 금액 차이 배치 (커서 + XA).
|
||||
*
|
||||
* 당일 매입 중 정산 원장(SETTLE) 합계가 net 과 다른 건을 커서로 순회하며 rc_diff
|
||||
* 에 AMT 차이 라인을 기록한다 (ONE global XA tx). 사용법: rc_diff_batch [YYYY-MM-DD]
|
||||
*/
|
||||
#include <stdio.h>
|
||||
#include <stdlib.h>
|
||||
#include <string.h>
|
||||
#include <atmi.h>
|
||||
#include "rc_dbio.h"
|
||||
|
||||
int main(int argc, char **argv)
|
||||
{
|
||||
const char *bizdate = (argc > 1) ? argv[1] : "2026-07-19";
|
||||
long rows = 0;
|
||||
|
||||
EXEC SQL BEGIN DECLARE SECTION;
|
||||
char h_bizdate[16];
|
||||
long h_id, h_pid, h_exp, h_act, h_diff;
|
||||
EXEC SQL END DECLARE SECTION;
|
||||
|
||||
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 rdc CURSOR FOR
|
||||
SELECT p.purchase_id, p.net,
|
||||
coalesce((SELECT sum(l.amount) FROM ledger l
|
||||
WHERE l.purchase_id = p.purchase_id AND l.entry_type = 'SETTLE'), 0)
|
||||
FROM purchase p
|
||||
WHERE p.biz_date = :h_bizdate
|
||||
ORDER BY p.purchase_id;
|
||||
EXEC SQL OPEN rdc;
|
||||
if (sqlca.sqlcode < 0) { fprintf(stderr, "OPEN FAIL [%d] %s\n", sqlca.sqlcode, sqlca.sqlerrm.sqlerrmc); tpabort(0); return 1; }
|
||||
|
||||
for (;;) {
|
||||
EXEC SQL FETCH rdc INTO :h_pid, :h_exp, :h_act;
|
||||
if (sqlca.sqlcode == 100) break;
|
||||
if (sqlca.sqlcode < 0) { fprintf(stderr, "FETCH FAIL [%d] %s\n", sqlca.sqlcode, sqlca.sqlerrm.sqlerrmc); EXEC SQL CLOSE rdc; tpabort(0); return 1; }
|
||||
if (h_exp == h_act) continue; /* 일치 건은 건너뜀 */
|
||||
h_diff = h_exp - h_act;
|
||||
EXEC SQL SELECT nextval('rc_diff_seq') INTO :h_id;
|
||||
if (sqlca.sqlcode < 0) { EXEC SQL CLOSE rdc; tpabort(0); return 1; }
|
||||
EXEC SQL INSERT INTO rc_diff (diff_id, purchase_id, expected_amt, actual_amt, diff_amt, diff_kind, biz_date)
|
||||
VALUES (:h_id, :h_pid, :h_exp, :h_act, :h_diff, 'AMT', :h_bizdate);
|
||||
if (sqlca.sqlcode < 0) { fprintf(stderr, "INSERT FAIL [%d] %s\n", sqlca.sqlcode, sqlca.sqlerrm.sqlerrmc); EXEC SQL CLOSE rdc; tpabort(0); return 1; }
|
||||
rows++;
|
||||
}
|
||||
EXEC SQL CLOSE rdc;
|
||||
|
||||
if (tpcommit(0) < 0) { fprintf(stderr, "tpcommit FAIL: %s\n", tpstrerror(tperrno)); tpabort(0); return 1; }
|
||||
printf(">>> rc_diff_batch COMMIT: bizdate=%s diff_rows=%ld\n", bizdate, rows);
|
||||
|
||||
tpclose(); tpterm();
|
||||
return 0;
|
||||
}
|
||||
64
app/src/rc/batch/rc_match_batch.pgc
Normal file
64
app/src/rc/batch/rc_match_batch.pgc
Normal file
|
|
@ -0,0 +1,64 @@
|
|||
/*
|
||||
* rc_match_batch.pgc - rc 3-way 대사 배치 (커서 + XA).
|
||||
*
|
||||
* ATMI client: tpinit/tpopen 로 ECPG XA RM 를 열고, tpbegin 으로 ONE global tx
|
||||
* 를 시작한 뒤, 승인(approval)과 원장(ledger)이 모두 존재하는 당일 매입을 커서로
|
||||
* 순회하며 rc_match_log 에 3WAY MATCHED 를 (dbio 로) 기록하고, 나머지는 EXCEPTION
|
||||
* 으로 남긴다. 마지막에 tpcommit 이 XA 2PC 를 구동. 사용법: rc_match_batch [YYYY-MM-DD]
|
||||
*/
|
||||
#include <stdio.h>
|
||||
#include <stdlib.h>
|
||||
#include <string.h>
|
||||
#include <atmi.h>
|
||||
#include "rc_dbio.h"
|
||||
|
||||
int main(int argc, char **argv)
|
||||
{
|
||||
const char *bizdate = (argc > 1) ? argv[1] : "2026-07-19";
|
||||
long rows = 0, exc = 0;
|
||||
|
||||
EXEC SQL BEGIN DECLARE SECTION;
|
||||
char h_bizdate[16];
|
||||
long h_pid, h_net, h_appr, h_led;
|
||||
EXEC SQL END DECLARE SECTION;
|
||||
|
||||
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 rmc CURSOR FOR
|
||||
SELECT p.purchase_id, p.net,
|
||||
(SELECT count(*) FROM approval a WHERE a.purchase_id = p.purchase_id),
|
||||
(SELECT count(*) FROM ledger l WHERE l.purchase_id = p.purchase_id)
|
||||
FROM purchase p
|
||||
WHERE p.biz_date = :h_bizdate
|
||||
ORDER BY p.purchase_id;
|
||||
EXEC SQL OPEN rmc;
|
||||
if (sqlca.sqlcode < 0) { fprintf(stderr, "OPEN FAIL [%d] %s\n", sqlca.sqlcode, sqlca.sqlerrm.sqlerrmc); tpabort(0); return 1; }
|
||||
|
||||
for (;;) {
|
||||
EXEC SQL FETCH rmc INTO :h_pid, :h_net, :h_appr, :h_led;
|
||||
if (sqlca.sqlcode == 100) break;
|
||||
if (sqlca.sqlcode < 0) { fprintf(stderr, "FETCH FAIL [%d] %s\n", sqlca.sqlcode, sqlca.sqlerrm.sqlerrmc); EXEC SQL CLOSE rmc; tpabort(0); return 1; }
|
||||
if (h_appr > 0 && h_led > 0) {
|
||||
if (rcdb_insert_match(h_pid, "3WAY", h_net, "MATCHED", bizdate) < 0) {
|
||||
EXEC SQL CLOSE rmc; tpabort(0); return 1;
|
||||
}
|
||||
rows++;
|
||||
} else {
|
||||
if (rcdb_insert_match(h_pid, "3WAY", h_net, "EXCEPTION", bizdate) < 0) {
|
||||
EXEC SQL CLOSE rmc; tpabort(0); return 1;
|
||||
}
|
||||
exc++;
|
||||
}
|
||||
}
|
||||
EXEC SQL CLOSE rmc;
|
||||
|
||||
if (tpcommit(0) < 0) { fprintf(stderr, "tpcommit FAIL: %s\n", tpstrerror(tperrno)); tpabort(0); return 1; }
|
||||
printf(">>> rc_match_batch COMMIT: bizdate=%s matched=%ld exception=%ld\n", bizdate, rows, exc);
|
||||
|
||||
tpclose(); tpterm();
|
||||
return 0;
|
||||
}
|
||||
26
app/src/rc/dbio/rc_dbio.h
Normal file
26
app/src/rc/dbio/rc_dbio.h
Normal file
|
|
@ -0,0 +1,26 @@
|
|||
/*
|
||||
* rc_dbio.h - rc (대사) 모듈 DB 접근 계층 (librcdbio.a).
|
||||
* ECPG (EXEC SQL) 함수들을 정적 라이브러리로 묶어 rc_svr / rc 배치에 링크한다.
|
||||
* 호출자의 XA 브랜치/커넥션에서 수행되며 EXEC SQL CONNECT 는 없다.
|
||||
* 반환 규약: 0 = ok, -1 = SQL error (호출자가 sqlca 도 확인 가능).
|
||||
*/
|
||||
#ifndef RC_DBIO_H
|
||||
#define RC_DBIO_H
|
||||
|
||||
/* 대사 로그 */
|
||||
long rcdb_next_match_id(void);
|
||||
long rcdb_insert_match(long pid, const char *mtype, long amount,
|
||||
const char *status, const char *bizdate);
|
||||
int rcdb_update_match_status(long pid, const char *from_status, const char *to_status);
|
||||
long rcdb_count_match(const char *bizdate, const char *status);
|
||||
|
||||
/* 미대사 / 예외 */
|
||||
long rcdb_count_unmatched(const char *bizdate);
|
||||
long rcdb_insert_exception(long pid, const char *reason, const char *bizdate);
|
||||
int rcdb_resolve_exception(long pid);
|
||||
|
||||
/* 원장 연계 + 요약 upsert */
|
||||
long rcdb_insert_ledger(long pid, const char *type, long amount, const char *bizdate);
|
||||
int rcdb_upsert_summary(const char *bizdate, long matched, long unmatched, long gross);
|
||||
|
||||
#endif /* RC_DBIO_H */
|
||||
68
app/src/rc/dbio/rc_ledger_dbio.pgc
Normal file
68
app/src/rc/dbio/rc_ledger_dbio.pgc
Normal file
|
|
@ -0,0 +1,68 @@
|
|||
/*
|
||||
* rc_ledger_dbio.pgc - 대사 원장 연계 + 요약 upsert DB 접근 (librcdbio.a).
|
||||
* ECPG 함수; 호출자의 XA 브랜치에서 수행. EXEC SQL CONNECT 없음.
|
||||
*/
|
||||
#include <string.h>
|
||||
#include <userlog.h>
|
||||
#include "rc_dbio.h"
|
||||
|
||||
long rcdb_count_unmatched(const char *bizdate)
|
||||
{
|
||||
EXEC SQL BEGIN DECLARE SECTION;
|
||||
long h_cnt;
|
||||
char h_bizdate[16];
|
||||
EXEC SQL END DECLARE SECTION;
|
||||
|
||||
strncpy(h_bizdate, bizdate, sizeof(h_bizdate) - 1); h_bizdate[sizeof(h_bizdate)-1] = 0;
|
||||
|
||||
EXEC SQL SELECT count(*) INTO :h_cnt FROM purchase p
|
||||
WHERE p.biz_date = :h_bizdate
|
||||
AND NOT EXISTS (SELECT 1 FROM ledger l WHERE l.purchase_id = p.purchase_id);
|
||||
if (sqlca.sqlcode < 0) {
|
||||
userlog("rcdb_count_unmatched FAIL [%d] %s", sqlca.sqlcode, sqlca.sqlerrm.sqlerrmc);
|
||||
return -1;
|
||||
}
|
||||
return h_cnt;
|
||||
}
|
||||
|
||||
long rcdb_insert_ledger(long pid, const char *type, long amount, const char *bizdate)
|
||||
{
|
||||
EXEC SQL BEGIN DECLARE SECTION;
|
||||
long h_pid = pid, h_amount = amount;
|
||||
char h_type[16], h_bizdate[16];
|
||||
EXEC SQL END DECLARE SECTION;
|
||||
|
||||
strncpy(h_type, type, sizeof(h_type) - 1); h_type[sizeof(h_type)-1] = 0;
|
||||
strncpy(h_bizdate, bizdate, sizeof(h_bizdate) - 1); h_bizdate[sizeof(h_bizdate)-1] = 0;
|
||||
|
||||
EXEC SQL INSERT INTO ledger (ledger_id, purchase_id, entry_type, amount, biz_date)
|
||||
VALUES (nextval('ledger_seq'), :h_pid, :h_type, :h_amount, :h_bizdate);
|
||||
if (sqlca.sqlcode < 0) {
|
||||
userlog("rcdb_insert_ledger FAIL [%d] %s", sqlca.sqlcode, sqlca.sqlerrm.sqlerrmc);
|
||||
return -1;
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
int rcdb_upsert_summary(const char *bizdate, long matched, long unmatched, long gross)
|
||||
{
|
||||
EXEC SQL BEGIN DECLARE SECTION;
|
||||
long h_matched = matched, h_unmatched = unmatched, h_gross = gross;
|
||||
char h_bizdate[16];
|
||||
EXEC SQL END DECLARE SECTION;
|
||||
|
||||
strncpy(h_bizdate, bizdate, sizeof(h_bizdate) - 1); h_bizdate[sizeof(h_bizdate)-1] = 0;
|
||||
|
||||
EXEC SQL INSERT INTO rc_summary (biz_date, matched_cnt, unmatched_cnt, gross_amount)
|
||||
VALUES (:h_bizdate, :h_matched, :h_unmatched, :h_gross)
|
||||
ON CONFLICT (biz_date) DO UPDATE
|
||||
SET matched_cnt = EXCLUDED.matched_cnt,
|
||||
unmatched_cnt = EXCLUDED.unmatched_cnt,
|
||||
gross_amount = EXCLUDED.gross_amount,
|
||||
updated_at = now();
|
||||
if (sqlca.sqlcode < 0) {
|
||||
userlog("rcdb_upsert_summary FAIL [%d] %s", sqlca.sqlcode, sqlca.sqlerrm.sqlerrmc);
|
||||
return -1;
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
117
app/src/rc/dbio/rc_match_dbio.pgc
Normal file
117
app/src/rc/dbio/rc_match_dbio.pgc
Normal file
|
|
@ -0,0 +1,117 @@
|
|||
/*
|
||||
* rc_match_dbio.pgc - 대사 로그/예외 DB 접근 (librcdbio.a).
|
||||
* ECPG 함수; 호출자의 XA 브랜치에서 수행. EXEC SQL CONNECT 없음.
|
||||
*/
|
||||
#include <string.h>
|
||||
#include <userlog.h>
|
||||
#include "rc_dbio.h"
|
||||
|
||||
long rcdb_next_match_id(void)
|
||||
{
|
||||
EXEC SQL BEGIN DECLARE SECTION;
|
||||
long h_id;
|
||||
EXEC SQL END DECLARE SECTION;
|
||||
|
||||
EXEC SQL SELECT nextval('rc_match_seq') INTO :h_id;
|
||||
if (sqlca.sqlcode < 0) {
|
||||
userlog("rcdb_next_match_id FAIL [%d] %s", sqlca.sqlcode, sqlca.sqlerrm.sqlerrmc);
|
||||
return -1;
|
||||
}
|
||||
return h_id;
|
||||
}
|
||||
|
||||
long rcdb_insert_match(long pid, const char *mtype, long amount,
|
||||
const char *status, const char *bizdate)
|
||||
{
|
||||
EXEC SQL BEGIN DECLARE SECTION;
|
||||
long h_id, h_pid = pid, h_amt = amount;
|
||||
char h_type[24], h_status[16], h_bizdate[16];
|
||||
EXEC SQL END DECLARE SECTION;
|
||||
|
||||
strncpy(h_type, mtype, sizeof(h_type) - 1); h_type[sizeof(h_type)-1] = 0;
|
||||
strncpy(h_status, status, sizeof(h_status) - 1); h_status[sizeof(h_status)-1] = 0;
|
||||
strncpy(h_bizdate, bizdate, sizeof(h_bizdate) - 1); h_bizdate[sizeof(h_bizdate)-1] = 0;
|
||||
|
||||
EXEC SQL SELECT nextval('rc_match_seq') INTO :h_id;
|
||||
if (sqlca.sqlcode < 0) return -1;
|
||||
|
||||
EXEC SQL INSERT INTO rc_match_log (match_id, purchase_id, match_type, amount, status, biz_date)
|
||||
VALUES (:h_id, :h_pid, :h_type, :h_amt, :h_status, :h_bizdate);
|
||||
if (sqlca.sqlcode < 0) {
|
||||
userlog("rcdb_insert_match FAIL [%d] %s", sqlca.sqlcode, sqlca.sqlerrm.sqlerrmc);
|
||||
return -1;
|
||||
}
|
||||
return h_id;
|
||||
}
|
||||
|
||||
int rcdb_update_match_status(long pid, const char *from_status, const char *to_status)
|
||||
{
|
||||
EXEC SQL BEGIN DECLARE SECTION;
|
||||
long h_pid = pid;
|
||||
char h_from[16], h_to[16];
|
||||
EXEC SQL END DECLARE SECTION;
|
||||
|
||||
strncpy(h_from, from_status, sizeof(h_from) - 1); h_from[sizeof(h_from)-1] = 0;
|
||||
strncpy(h_to, to_status, sizeof(h_to) - 1); h_to[sizeof(h_to)-1] = 0;
|
||||
|
||||
EXEC SQL UPDATE rc_match_log SET status = :h_to, updated_at = now()
|
||||
WHERE purchase_id = :h_pid AND status = :h_from;
|
||||
if (sqlca.sqlcode < 0) {
|
||||
userlog("rcdb_update_match_status FAIL [%d] %s", sqlca.sqlcode, sqlca.sqlerrm.sqlerrmc);
|
||||
return -1;
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
long rcdb_count_match(const char *bizdate, const char *status)
|
||||
{
|
||||
EXEC SQL BEGIN DECLARE SECTION;
|
||||
long h_cnt;
|
||||
char h_bizdate[16], h_status[16];
|
||||
EXEC SQL END DECLARE SECTION;
|
||||
|
||||
strncpy(h_bizdate, bizdate, sizeof(h_bizdate) - 1); h_bizdate[sizeof(h_bizdate)-1] = 0;
|
||||
strncpy(h_status, status, sizeof(h_status) - 1); h_status[sizeof(h_status)-1] = 0;
|
||||
|
||||
EXEC SQL SELECT count(*) INTO :h_cnt FROM rc_match_log
|
||||
WHERE biz_date = :h_bizdate AND status = :h_status;
|
||||
if (sqlca.sqlcode < 0) return -1;
|
||||
return h_cnt;
|
||||
}
|
||||
|
||||
long rcdb_insert_exception(long pid, const char *reason, const char *bizdate)
|
||||
{
|
||||
EXEC SQL BEGIN DECLARE SECTION;
|
||||
long h_id, h_pid = pid;
|
||||
char h_reason[128], h_bizdate[16];
|
||||
EXEC SQL END DECLARE SECTION;
|
||||
|
||||
strncpy(h_reason, reason, sizeof(h_reason) - 1); h_reason[sizeof(h_reason)-1] = 0;
|
||||
strncpy(h_bizdate, bizdate, sizeof(h_bizdate) - 1); h_bizdate[sizeof(h_bizdate)-1] = 0;
|
||||
|
||||
EXEC SQL SELECT nextval('rc_exc_seq') INTO :h_id;
|
||||
if (sqlca.sqlcode < 0) return -1;
|
||||
|
||||
EXEC SQL INSERT INTO rc_exception (exc_id, purchase_id, reason, biz_date)
|
||||
VALUES (:h_id, :h_pid, :h_reason, :h_bizdate);
|
||||
if (sqlca.sqlcode < 0) {
|
||||
userlog("rcdb_insert_exception FAIL [%d] %s", sqlca.sqlcode, sqlca.sqlerrm.sqlerrmc);
|
||||
return -1;
|
||||
}
|
||||
return h_id;
|
||||
}
|
||||
|
||||
int rcdb_resolve_exception(long pid)
|
||||
{
|
||||
EXEC SQL BEGIN DECLARE SECTION;
|
||||
long h_pid = pid;
|
||||
EXEC SQL END DECLARE SECTION;
|
||||
|
||||
EXEC SQL UPDATE rc_exception SET resolved = true
|
||||
WHERE purchase_id = :h_pid AND resolved = false;
|
||||
if (sqlca.sqlcode < 0) {
|
||||
userlog("rcdb_resolve_exception FAIL [%d] %s", sqlca.sqlcode, sqlca.sqlerrm.sqlerrmc);
|
||||
return -1;
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
|
@ -1,9 +1,16 @@
|
|||
/*
|
||||
* rc_svr.pgc - rc (대사) MODULE SERVER.
|
||||
*
|
||||
* Advertises the reconcile services. RECONCILE keeps the proven behavior: it is
|
||||
* a DISTINCT XA branch (own DB connection) from ACQUIRE, so it writes only its
|
||||
* own ledger recon row, then tpcall()s SETTLE (st_svr) inside the same global tx.
|
||||
* ONE binary that tpadvertise()s ALL ~30 online rc(대사) services — the real
|
||||
* Tuxedo/Enduro-X "one module server, many services" pattern, modeled on ac_svr.
|
||||
* Every service is a distinct 대사 operation with genuine EXEC SQL (directly, or
|
||||
* via the linked librcdbio.a dbio layer).
|
||||
*
|
||||
* XA: the connection is opened once by tpopen() (ECPG XA switch); there is NO
|
||||
* EXEC SQL CONNECT. RECONCILE keeps the proven chain behavior: it is a DISTINCT
|
||||
* XA branch from ACQUIRE, writes its own recon ledger row, then tpcall()s SETTLE
|
||||
* (st_svr) inside the SAME global tx. Owner-writes: rc services never tpcall each
|
||||
* other.
|
||||
*/
|
||||
#include <stdio.h>
|
||||
#include <stdlib.h>
|
||||
|
|
@ -13,22 +20,51 @@
|
|||
#include <userlog.h>
|
||||
#include <ndebug.h>
|
||||
#include "acq.fd.h"
|
||||
#include "rc_dbio.h"
|
||||
|
||||
/* ----- small UBF helpers ------------------------------------------------- */
|
||||
static long getl(UBFH *b, BFLDID f) { long v = 0; Bget(b, f, 0, (char *)&v, 0L); return v; }
|
||||
static void gets_(UBFH *b, BFLDID f, char *out, int cap)
|
||||
{ BFLDLEN l = cap; out[0] = 0; Bget(b, f, 0, out, &l); }
|
||||
static void setl(UBFH *b, BFLDID f, long v) { Bchg(b, f, 0, (char *)&v, 0L); }
|
||||
|
||||
#define FAIL(b) do { tpreturn(TPFAIL, 0, (char *)(b), 0L, 0L); return; } while (0)
|
||||
#define OK(b) do { tpreturn(TPSUCCESS, 0, (char *)(b), 0L, 0L); return; } while (0)
|
||||
|
||||
void RECONCILE(TPSVCINFO *p);
|
||||
void RC_REMATCH(TPSVCINFO *p);
|
||||
void RC_UNMATCH(TPSVCINFO *p);
|
||||
void RC_HOLD(TPSVCINFO *p);
|
||||
/* ----- service prototypes ------------------------------------------------ */
|
||||
void RECONCILE(TPSVCINFO *p); void RC_APPR_ACQ(TPSVCINFO *p);
|
||||
void RC_ACQ_DEP(TPSVCINFO *p); void RC_RETURN(TPSVCINFO *p);
|
||||
void RC_AMTDIFF(TPSVCINFO *p); void RC_CNTDIFF(TPSVCINFO *p);
|
||||
void RC_3WAY(TPSVCINFO *p); void RC_TOLERANCE(TPSVCINFO *p);
|
||||
void RC_REMATCH(TPSVCINFO *p); void RC_HOLD(TPSVCINFO *p);
|
||||
void RC_UNMATCH(TPSVCINFO *p); void RC_RELEASE(TPSVCINFO *p);
|
||||
void RC_EXCEPTION(TPSVCINFO *p); void RC_AUTOFIX(TPSVCINFO *p);
|
||||
void RC_BYISSUER(TPSVCINFO *p); void RC_BYDATE(TPSVCINFO *p);
|
||||
void RC_BYMERCH(TPSVCINFO *p); void RC_PARTIAL(TPSVCINFO *p);
|
||||
void RC_DIFFRPT(TPSVCINFO *p); void RC_STATS(TPSVCINFO *p);
|
||||
void RC_PROGRESS(TPSVCINFO *p); void RC_SUMMARY(TPSVCINFO *p);
|
||||
void RC_REPROC(TPSVCINFO *p); void RC_DELAY(TPSVCINFO *p);
|
||||
void RC_LEDGERLINK(TPSVCINFO *p);void RC_ALERT(TPSVCINFO *p);
|
||||
void RC_MANUAL(TPSVCINFO *p); void RC_ROLLBACK(TPSVCINFO *p);
|
||||
void RC_CONFIRM(TPSVCINFO *p); void RC_STATUS(TPSVCINFO *p);
|
||||
|
||||
/* ----- advertise table --------------------------------------------------- */
|
||||
static struct { const char *name; void (*fn)(TPSVCINFO *); } SVCS[] = {
|
||||
{"RECONCILE", RECONCILE}, {"RC_REMATCH", RC_REMATCH},
|
||||
{"RC_UNMATCH", RC_UNMATCH}, {"RC_HOLD", RC_HOLD},
|
||||
{"RECONCILE", RECONCILE}, {"RC_APPR_ACQ", RC_APPR_ACQ},
|
||||
{"RC_ACQ_DEP", RC_ACQ_DEP}, {"RC_RETURN", RC_RETURN},
|
||||
{"RC_AMTDIFF", RC_AMTDIFF}, {"RC_CNTDIFF", RC_CNTDIFF},
|
||||
{"RC_3WAY", RC_3WAY}, {"RC_TOLERANCE", RC_TOLERANCE},
|
||||
{"RC_REMATCH", RC_REMATCH}, {"RC_HOLD", RC_HOLD},
|
||||
{"RC_UNMATCH", RC_UNMATCH}, {"RC_RELEASE", RC_RELEASE},
|
||||
{"RC_EXCEPTION", RC_EXCEPTION}, {"RC_AUTOFIX", RC_AUTOFIX},
|
||||
{"RC_BYISSUER", RC_BYISSUER}, {"RC_BYDATE", RC_BYDATE},
|
||||
{"RC_BYMERCH", RC_BYMERCH}, {"RC_PARTIAL", RC_PARTIAL},
|
||||
{"RC_DIFFRPT", RC_DIFFRPT}, {"RC_STATS", RC_STATS},
|
||||
{"RC_PROGRESS", RC_PROGRESS}, {"RC_SUMMARY", RC_SUMMARY},
|
||||
{"RC_REPROC", RC_REPROC}, {"RC_DELAY", RC_DELAY},
|
||||
{"RC_LEDGERLINK", RC_LEDGERLINK},{"RC_ALERT", RC_ALERT},
|
||||
{"RC_MANUAL", RC_MANUAL}, {"RC_ROLLBACK", RC_ROLLBACK},
|
||||
{"RC_CONFIRM", RC_CONFIRM}, {"RC_STATUS", RC_STATUS},
|
||||
{NULL, NULL}
|
||||
};
|
||||
|
||||
|
|
@ -47,6 +83,9 @@ int tpsvrinit(int argc, char **argv)
|
|||
|
||||
void tpsvrdone(void) { tpclose(); userlog("rc_svr: tpsvrdone"); }
|
||||
|
||||
/* ========================================================================= */
|
||||
/* 1. RECONCILE - 대사 접수 (own branch), then XA chain -> SETTLE. (KEEP) */
|
||||
/* ========================================================================= */
|
||||
void RECONCILE(TPSVCINFO *p)
|
||||
{
|
||||
UBFH *b = (UBFH *)p->data;
|
||||
|
|
@ -70,7 +109,7 @@ void RECONCILE(TPSVCINFO *p)
|
|||
FAIL(b);
|
||||
}
|
||||
Bchg(b, T_STATUS, 0, "M", 0L);
|
||||
userlog("RECONCILE pid=%ld matched -> SETTLE", h_pid);
|
||||
userlog("RECONCILE pid=%ld 대사완료 -> SETTLE", h_pid);
|
||||
|
||||
if (tpcall("SETTLE", (char *)b, 0L, (char **)&b, &rlen, 0L) < 0) {
|
||||
userlog("RECONCILE: tpcall(SETTLE) FAIL: %s", tpstrerror(tperrno));
|
||||
|
|
@ -79,7 +118,165 @@ void RECONCILE(TPSVCINFO *p)
|
|||
OK(b);
|
||||
}
|
||||
|
||||
/* RC_REMATCH - 재대사 원장 기록. */
|
||||
/* 2. RC_APPR_ACQ - 승인-매입 대사 (승인 존재 여부 확인 후 대사 로그). */
|
||||
void RC_APPR_ACQ(TPSVCINFO *p)
|
||||
{
|
||||
UBFH *b = (UBFH *)p->data;
|
||||
char bizdate[16];
|
||||
long mid;
|
||||
EXEC SQL BEGIN DECLARE SECTION;
|
||||
long h_pid, h_cnt, h_net;
|
||||
EXEC SQL END DECLARE SECTION;
|
||||
|
||||
h_pid = getl(b, T_PURCHASE_ID);
|
||||
h_net = getl(b, T_NET);
|
||||
gets_(b, T_BIZDATE, bizdate, sizeof(bizdate));
|
||||
EXEC SQL SELECT count(*) INTO :h_cnt FROM approval WHERE purchase_id = :h_pid;
|
||||
if (sqlca.sqlcode < 0) FAIL(b);
|
||||
if (h_cnt > 0)
|
||||
mid = rcdb_insert_match(h_pid, "APPR_ACQ", h_net, "MATCHED", bizdate);
|
||||
else
|
||||
mid = rcdb_insert_match(h_pid, "APPR_ACQ", h_net, "EXCEPTION", bizdate);
|
||||
if (mid < 0) FAIL(b);
|
||||
setl(b, T_ID1, mid);
|
||||
setl(b, T_RC, h_cnt > 0 ? 0 : 1);
|
||||
OK(b);
|
||||
}
|
||||
|
||||
/* 3. RC_ACQ_DEP - 매입-입금(원장) 대사: 원장 합계와 net 비교. */
|
||||
void RC_ACQ_DEP(TPSVCINFO *p)
|
||||
{
|
||||
UBFH *b = (UBFH *)p->data;
|
||||
char bizdate[16];
|
||||
long mid;
|
||||
EXEC SQL BEGIN DECLARE SECTION;
|
||||
long h_pid, h_net, h_dep;
|
||||
EXEC SQL END DECLARE SECTION;
|
||||
|
||||
h_pid = getl(b, T_PURCHASE_ID);
|
||||
h_net = getl(b, T_NET);
|
||||
gets_(b, T_BIZDATE, bizdate, sizeof(bizdate));
|
||||
EXEC SQL SELECT coalesce(sum(amount),0) INTO :h_dep FROM ledger
|
||||
WHERE purchase_id = :h_pid AND entry_type IN ('RECON','SETTLE');
|
||||
if (sqlca.sqlcode < 0) FAIL(b);
|
||||
mid = rcdb_insert_match(h_pid, "ACQ_DEP", h_dep,
|
||||
(h_dep == h_net) ? "MATCHED" : "EXCEPTION", bizdate);
|
||||
if (mid < 0) FAIL(b);
|
||||
setl(b, T_GROSS, h_dep);
|
||||
setl(b, T_RC, (h_dep == h_net) ? 0 : 1);
|
||||
OK(b);
|
||||
}
|
||||
|
||||
/* 4. RC_RETURN - 반품 대사: 반품 원장 + 음수 대사 로그. */
|
||||
void RC_RETURN(TPSVCINFO *p)
|
||||
{
|
||||
UBFH *b = (UBFH *)p->data;
|
||||
char bizdate[16];
|
||||
long pid = getl(b, T_PURCHASE_ID), amt = getl(b, T_AMOUNT), mid;
|
||||
gets_(b, T_BIZDATE, bizdate, sizeof(bizdate));
|
||||
if (rcdb_insert_ledger(pid, "RETURN", -amt, bizdate) < 0) FAIL(b);
|
||||
mid = rcdb_insert_match(pid, "RETURN", -amt, "MATCHED", bizdate);
|
||||
if (mid < 0) FAIL(b);
|
||||
setl(b, T_ID1, mid);
|
||||
OK(b);
|
||||
}
|
||||
|
||||
/* 5. RC_AMTDIFF - 금액 차이 대사: 기대(net) vs 실제(원장합) 차이 기록. */
|
||||
void RC_AMTDIFF(TPSVCINFO *p)
|
||||
{
|
||||
UBFH *b = (UBFH *)p->data;
|
||||
char bizdate[16];
|
||||
EXEC SQL BEGIN DECLARE SECTION;
|
||||
long h_id, h_pid, h_exp, h_act, h_diff;
|
||||
char h_bizdate[16];
|
||||
EXEC SQL END DECLARE SECTION;
|
||||
|
||||
h_pid = getl(b, T_PURCHASE_ID);
|
||||
h_exp = getl(b, T_NET);
|
||||
gets_(b, T_BIZDATE, bizdate, sizeof(bizdate));
|
||||
strncpy(h_bizdate, bizdate, sizeof(h_bizdate)-1); h_bizdate[sizeof(h_bizdate)-1] = 0;
|
||||
EXEC SQL SELECT coalesce(sum(amount),0) INTO :h_act FROM ledger
|
||||
WHERE purchase_id = :h_pid AND entry_type = 'SETTLE';
|
||||
if (sqlca.sqlcode < 0) FAIL(b);
|
||||
h_diff = h_exp - h_act;
|
||||
EXEC SQL SELECT nextval('rc_diff_seq') INTO :h_id;
|
||||
if (sqlca.sqlcode < 0) FAIL(b);
|
||||
EXEC SQL INSERT INTO rc_diff (diff_id, purchase_id, expected_amt, actual_amt, diff_amt, diff_kind, biz_date)
|
||||
VALUES (:h_id, :h_pid, :h_exp, :h_act, :h_diff, 'AMT', :h_bizdate);
|
||||
if (sqlca.sqlcode < 0) { userlog("RC_AMTDIFF INSERT FAIL [%d] %s", sqlca.sqlcode, sqlca.sqlerrm.sqlerrmc); FAIL(b); }
|
||||
setl(b, T_DELTA, h_diff);
|
||||
OK(b);
|
||||
}
|
||||
|
||||
/* 6. RC_CNTDIFF - 건수 차이 대사: 매입 건수 vs 대사 로그 건수. */
|
||||
void RC_CNTDIFF(TPSVCINFO *p)
|
||||
{
|
||||
UBFH *b = (UBFH *)p->data;
|
||||
char bizdate[16];
|
||||
EXEC SQL BEGIN DECLARE SECTION;
|
||||
long h_id, h_pcnt, h_mcnt, h_diff;
|
||||
char h_bizdate[16];
|
||||
EXEC SQL END DECLARE SECTION;
|
||||
|
||||
gets_(b, T_BIZDATE, bizdate, sizeof(bizdate));
|
||||
strncpy(h_bizdate, bizdate, sizeof(h_bizdate)-1); h_bizdate[sizeof(h_bizdate)-1] = 0;
|
||||
EXEC SQL SELECT count(*) INTO :h_pcnt FROM purchase WHERE biz_date = :h_bizdate;
|
||||
if (sqlca.sqlcode < 0) FAIL(b);
|
||||
EXEC SQL SELECT count(*) INTO :h_mcnt FROM rc_match_log WHERE biz_date = :h_bizdate;
|
||||
if (sqlca.sqlcode < 0) FAIL(b);
|
||||
h_diff = h_pcnt - h_mcnt;
|
||||
EXEC SQL SELECT nextval('rc_diff_seq') INTO :h_id;
|
||||
if (sqlca.sqlcode < 0) FAIL(b);
|
||||
EXEC SQL INSERT INTO rc_diff (diff_id, purchase_id, expected_amt, actual_amt, diff_amt, diff_kind, biz_date)
|
||||
VALUES (:h_id, 0, :h_pcnt, :h_mcnt, :h_diff, 'CNT', :h_bizdate);
|
||||
if (sqlca.sqlcode < 0) FAIL(b);
|
||||
setl(b, T_COUNT, h_diff);
|
||||
OK(b);
|
||||
}
|
||||
|
||||
/* 7. RC_3WAY - 3-way 대사: 승인+매입+원장 모두 존재 시 MATCHED. */
|
||||
void RC_3WAY(TPSVCINFO *p)
|
||||
{
|
||||
UBFH *b = (UBFH *)p->data;
|
||||
char bizdate[16];
|
||||
long mid;
|
||||
EXEC SQL BEGIN DECLARE SECTION;
|
||||
long h_pid, h_appr, h_led, h_net;
|
||||
EXEC SQL END DECLARE SECTION;
|
||||
|
||||
h_pid = getl(b, T_PURCHASE_ID);
|
||||
h_net = getl(b, T_NET);
|
||||
gets_(b, T_BIZDATE, bizdate, sizeof(bizdate));
|
||||
EXEC SQL SELECT count(*) INTO :h_appr FROM approval WHERE purchase_id = :h_pid;
|
||||
if (sqlca.sqlcode < 0) FAIL(b);
|
||||
EXEC SQL SELECT count(*) INTO :h_led FROM ledger WHERE purchase_id = :h_pid;
|
||||
if (sqlca.sqlcode < 0) FAIL(b);
|
||||
mid = rcdb_insert_match(h_pid, "3WAY", h_net,
|
||||
(h_appr > 0 && h_led > 0) ? "MATCHED" : "EXCEPTION", bizdate);
|
||||
if (mid < 0) FAIL(b);
|
||||
setl(b, T_RC, (h_appr > 0 && h_led > 0) ? 0 : 1);
|
||||
OK(b);
|
||||
}
|
||||
|
||||
/* 8. RC_TOLERANCE - 허용오차 대사: |차이| <= 허용치면 MATCHED. */
|
||||
void RC_TOLERANCE(TPSVCINFO *p)
|
||||
{
|
||||
UBFH *b = (UBFH *)p->data;
|
||||
char bizdate[16];
|
||||
long pid, tol, diff, adiff, mid;
|
||||
pid = getl(b, T_PURCHASE_ID);
|
||||
diff = getl(b, T_DELTA);
|
||||
tol = getl(b, T_ARG1); if (tol <= 0) tol = 100; /* 기본 허용치 100원 */
|
||||
gets_(b, T_BIZDATE, bizdate, sizeof(bizdate));
|
||||
adiff = diff < 0 ? -diff : diff;
|
||||
mid = rcdb_insert_match(pid, "TOLERANCE", diff,
|
||||
(adiff <= tol) ? "MATCHED" : "EXCEPTION", bizdate);
|
||||
if (mid < 0) FAIL(b);
|
||||
setl(b, T_RC, (adiff <= tol) ? 0 : 1);
|
||||
OK(b);
|
||||
}
|
||||
|
||||
/* 9. RC_REMATCH - 재대사 원장 기록. (KEEP) */
|
||||
void RC_REMATCH(TPSVCINFO *p)
|
||||
{
|
||||
UBFH *b = (UBFH *)p->data;
|
||||
|
|
@ -97,7 +294,25 @@ void RC_REMATCH(TPSVCINFO *p)
|
|||
OK(b);
|
||||
}
|
||||
|
||||
/* RC_UNMATCH - 미대사 건수 조회. */
|
||||
/* 10. RC_HOLD - 대사 보류 원장 기록. (KEEP) */
|
||||
void RC_HOLD(TPSVCINFO *p)
|
||||
{
|
||||
UBFH *b = (UBFH *)p->data;
|
||||
char bizdate[16];
|
||||
EXEC SQL BEGIN DECLARE SECTION;
|
||||
long h_pid, h_net;
|
||||
char h_bizdate[16];
|
||||
EXEC SQL END DECLARE SECTION;
|
||||
h_pid = getl(b, T_PURCHASE_ID); h_net = getl(b, T_NET);
|
||||
gets_(b, T_BIZDATE, bizdate, sizeof(bizdate));
|
||||
strncpy(h_bizdate, bizdate, sizeof(h_bizdate)-1); h_bizdate[sizeof(h_bizdate)-1] = 0;
|
||||
EXEC SQL INSERT INTO ledger (ledger_id, purchase_id, entry_type, amount, biz_date)
|
||||
VALUES (nextval('ledger_seq'), :h_pid, 'RC_HOLD', :h_net, :h_bizdate);
|
||||
if (sqlca.sqlcode < 0) FAIL(b);
|
||||
OK(b);
|
||||
}
|
||||
|
||||
/* 11. RC_UNMATCH - 미대사 건수 조회. (KEEP) */
|
||||
void RC_UNMATCH(TPSVCINFO *p)
|
||||
{
|
||||
UBFH *b = (UBFH *)p->data;
|
||||
|
|
@ -116,21 +331,298 @@ void RC_UNMATCH(TPSVCINFO *p)
|
|||
OK(b);
|
||||
}
|
||||
|
||||
/* RC_HOLD - 대사 보류 원장 기록. */
|
||||
void RC_HOLD(TPSVCINFO *p)
|
||||
/* 12. RC_RELEASE - 대사 보류 해제 (HOLD -> RELEASED). */
|
||||
void RC_RELEASE(TPSVCINFO *p)
|
||||
{
|
||||
UBFH *b = (UBFH *)p->data;
|
||||
long pid = getl(b, T_PURCHASE_ID);
|
||||
if (rcdb_update_match_status(pid, "HOLD", "RELEASED") < 0) FAIL(b);
|
||||
Bchg(b, T_STATUS, 0, "R", 0L);
|
||||
OK(b);
|
||||
}
|
||||
|
||||
/* 13. RC_EXCEPTION - 대사 예외 등록. */
|
||||
void RC_EXCEPTION(TPSVCINFO *p)
|
||||
{
|
||||
UBFH *b = (UBFH *)p->data;
|
||||
char bizdate[16], reason[128];
|
||||
long pid = getl(b, T_PURCHASE_ID), eid;
|
||||
gets_(b, T_BIZDATE, bizdate, sizeof(bizdate));
|
||||
gets_(b, T_REASON, reason, sizeof(reason)); if (reason[0] == 0) strcpy(reason, "AMT_MISMATCH");
|
||||
eid = rcdb_insert_exception(pid, reason, bizdate);
|
||||
if (eid < 0) FAIL(b);
|
||||
setl(b, T_ID1, eid);
|
||||
OK(b);
|
||||
}
|
||||
|
||||
/* 14. RC_AUTOFIX - 자동 보정: 예외 해소 + 보정 원장. */
|
||||
void RC_AUTOFIX(TPSVCINFO *p)
|
||||
{
|
||||
UBFH *b = (UBFH *)p->data;
|
||||
char bizdate[16];
|
||||
long pid = getl(b, T_PURCHASE_ID), amt = getl(b, T_AMOUNT);
|
||||
gets_(b, T_BIZDATE, bizdate, sizeof(bizdate));
|
||||
if (rcdb_resolve_exception(pid) < 0) FAIL(b);
|
||||
if (rcdb_insert_ledger(pid, "AUTOFIX", amt, bizdate) < 0) FAIL(b);
|
||||
if (rcdb_update_match_status(pid, "EXCEPTION", "MATCHED") < 0) FAIL(b);
|
||||
OK(b);
|
||||
}
|
||||
|
||||
/* 15. RC_BYISSUER - 발급사별 매입 건수 집계. */
|
||||
void RC_BYISSUER(TPSVCINFO *p)
|
||||
{
|
||||
UBFH *b = (UBFH *)p->data;
|
||||
char bizdate[16], issuer[32];
|
||||
EXEC SQL BEGIN DECLARE SECTION;
|
||||
long h_cnt;
|
||||
char h_bizdate[16], h_issuer[32];
|
||||
EXEC SQL END DECLARE SECTION;
|
||||
gets_(b, T_BIZDATE, bizdate, sizeof(bizdate));
|
||||
gets_(b, T_ISSUER, issuer, sizeof(issuer)); if (issuer[0] == 0) strcpy(issuer, "BC");
|
||||
strncpy(h_bizdate, bizdate, sizeof(h_bizdate)-1); h_bizdate[sizeof(h_bizdate)-1] = 0;
|
||||
strncpy(h_issuer, issuer, sizeof(h_issuer)-1); h_issuer[sizeof(h_issuer)-1] = 0;
|
||||
EXEC SQL SELECT count(*) INTO :h_cnt FROM purchase
|
||||
WHERE biz_date = :h_bizdate AND issuer = :h_issuer;
|
||||
if (sqlca.sqlcode < 0) FAIL(b);
|
||||
setl(b, T_COUNT, h_cnt);
|
||||
OK(b);
|
||||
}
|
||||
|
||||
/* 16. RC_BYDATE - 일자별 대사(MATCHED) 집계. */
|
||||
void RC_BYDATE(TPSVCINFO *p)
|
||||
{
|
||||
UBFH *b = (UBFH *)p->data;
|
||||
char bizdate[16];
|
||||
EXEC SQL BEGIN DECLARE SECTION;
|
||||
long h_pid, h_net;
|
||||
long h_cnt, h_sum;
|
||||
char h_bizdate[16];
|
||||
EXEC SQL END DECLARE SECTION;
|
||||
h_pid = getl(b, T_PURCHASE_ID); h_net = getl(b, T_NET);
|
||||
gets_(b, T_BIZDATE, bizdate, sizeof(bizdate));
|
||||
strncpy(h_bizdate, bizdate, sizeof(h_bizdate)-1); h_bizdate[sizeof(h_bizdate)-1] = 0;
|
||||
EXEC SQL INSERT INTO ledger (ledger_id, purchase_id, entry_type, amount, biz_date)
|
||||
VALUES (nextval('ledger_seq'), :h_pid, 'RC_HOLD', :h_net, :h_bizdate);
|
||||
EXEC SQL SELECT count(*), coalesce(sum(amount),0) INTO :h_cnt, :h_sum
|
||||
FROM rc_match_log WHERE biz_date = :h_bizdate AND status = 'MATCHED';
|
||||
if (sqlca.sqlcode < 0) FAIL(b);
|
||||
setl(b, T_COUNT, h_cnt);
|
||||
setl(b, T_GROSS, h_sum);
|
||||
OK(b);
|
||||
}
|
||||
|
||||
/* 17. RC_BYMERCH - 가맹점별 net 합계 조회. */
|
||||
void RC_BYMERCH(TPSVCINFO *p)
|
||||
{
|
||||
UBFH *b = (UBFH *)p->data;
|
||||
char bizdate[16], merch[64];
|
||||
EXEC SQL BEGIN DECLARE SECTION;
|
||||
long h_sum;
|
||||
char h_bizdate[16], h_merch[64];
|
||||
EXEC SQL END DECLARE SECTION;
|
||||
gets_(b, T_BIZDATE, bizdate, sizeof(bizdate));
|
||||
gets_(b, T_MERCHANT, merch, sizeof(merch));
|
||||
strncpy(h_bizdate, bizdate, sizeof(h_bizdate)-1); h_bizdate[sizeof(h_bizdate)-1] = 0;
|
||||
strncpy(h_merch, merch, sizeof(h_merch)-1); h_merch[sizeof(h_merch)-1] = 0;
|
||||
EXEC SQL SELECT coalesce(sum(net),0) INTO :h_sum FROM purchase
|
||||
WHERE biz_date = :h_bizdate AND merchant_id = :h_merch;
|
||||
if (sqlca.sqlcode < 0) FAIL(b);
|
||||
setl(b, T_GROSS, h_sum);
|
||||
OK(b);
|
||||
}
|
||||
|
||||
/* 18. RC_PARTIAL - 부분 대사 로그. */
|
||||
void RC_PARTIAL(TPSVCINFO *p)
|
||||
{
|
||||
UBFH *b = (UBFH *)p->data;
|
||||
char bizdate[16];
|
||||
long pid = getl(b, T_PURCHASE_ID), part = getl(b, T_AMOUNT), mid;
|
||||
gets_(b, T_BIZDATE, bizdate, sizeof(bizdate));
|
||||
mid = rcdb_insert_match(pid, "PARTIAL", part, "MATCHED", bizdate);
|
||||
if (mid < 0) FAIL(b);
|
||||
setl(b, T_ID1, mid);
|
||||
OK(b);
|
||||
}
|
||||
|
||||
/* 19. RC_DIFFRPT - 당일 차이 라인 건수 조회. */
|
||||
void RC_DIFFRPT(TPSVCINFO *p)
|
||||
{
|
||||
UBFH *b = (UBFH *)p->data;
|
||||
char bizdate[16];
|
||||
EXEC SQL BEGIN DECLARE SECTION;
|
||||
long h_cnt, h_sum;
|
||||
char h_bizdate[16];
|
||||
EXEC SQL END DECLARE SECTION;
|
||||
gets_(b, T_BIZDATE, bizdate, sizeof(bizdate));
|
||||
strncpy(h_bizdate, bizdate, sizeof(h_bizdate)-1); h_bizdate[sizeof(h_bizdate)-1] = 0;
|
||||
EXEC SQL SELECT count(*), coalesce(sum(diff_amt),0) INTO :h_cnt, :h_sum
|
||||
FROM rc_diff WHERE biz_date = :h_bizdate;
|
||||
if (sqlca.sqlcode < 0) FAIL(b);
|
||||
setl(b, T_COUNT, h_cnt);
|
||||
setl(b, T_DELTA, h_sum);
|
||||
OK(b);
|
||||
}
|
||||
|
||||
/* 20. RC_STATS - MATCHED/EXCEPTION 통계 (dbio count). */
|
||||
void RC_STATS(TPSVCINFO *p)
|
||||
{
|
||||
UBFH *b = (UBFH *)p->data;
|
||||
char bizdate[16];
|
||||
long matched, exc;
|
||||
gets_(b, T_BIZDATE, bizdate, sizeof(bizdate));
|
||||
matched = rcdb_count_match(bizdate, "MATCHED");
|
||||
exc = rcdb_count_match(bizdate, "EXCEPTION");
|
||||
if (matched < 0 || exc < 0) FAIL(b);
|
||||
setl(b, T_COUNT, matched);
|
||||
setl(b, T_RC, exc);
|
||||
OK(b);
|
||||
}
|
||||
|
||||
/* 21. RC_PROGRESS - 진행률(%) = MATCHED / 전체매입 * 100. */
|
||||
void RC_PROGRESS(TPSVCINFO *p)
|
||||
{
|
||||
UBFH *b = (UBFH *)p->data;
|
||||
char bizdate[16];
|
||||
EXEC SQL BEGIN DECLARE SECTION;
|
||||
long h_total, h_matched;
|
||||
char h_bizdate[16];
|
||||
EXEC SQL END DECLARE SECTION;
|
||||
gets_(b, T_BIZDATE, bizdate, sizeof(bizdate));
|
||||
strncpy(h_bizdate, bizdate, sizeof(h_bizdate)-1); h_bizdate[sizeof(h_bizdate)-1] = 0;
|
||||
EXEC SQL SELECT count(*) INTO :h_total FROM purchase WHERE biz_date = :h_bizdate;
|
||||
if (sqlca.sqlcode < 0) FAIL(b);
|
||||
EXEC SQL SELECT count(*) INTO :h_matched FROM rc_match_log
|
||||
WHERE biz_date = :h_bizdate AND status = 'MATCHED';
|
||||
if (sqlca.sqlcode < 0) FAIL(b);
|
||||
setl(b, T_RC, h_total > 0 ? (h_matched * 100 / h_total) : 0);
|
||||
OK(b);
|
||||
}
|
||||
|
||||
/* 22. RC_SUMMARY - 대사 요약 upsert (dbio). */
|
||||
void RC_SUMMARY(TPSVCINFO *p)
|
||||
{
|
||||
UBFH *b = (UBFH *)p->data;
|
||||
char bizdate[16];
|
||||
long matched, unmatched;
|
||||
EXEC SQL BEGIN DECLARE SECTION;
|
||||
long h_gross;
|
||||
char h_bizdate[16];
|
||||
EXEC SQL END DECLARE SECTION;
|
||||
gets_(b, T_BIZDATE, bizdate, sizeof(bizdate));
|
||||
strncpy(h_bizdate, bizdate, sizeof(h_bizdate)-1); h_bizdate[sizeof(h_bizdate)-1] = 0;
|
||||
matched = rcdb_count_match(bizdate, "MATCHED");
|
||||
unmatched = rcdb_count_unmatched(bizdate);
|
||||
if (matched < 0 || unmatched < 0) FAIL(b);
|
||||
EXEC SQL SELECT coalesce(sum(net),0) INTO :h_gross FROM purchase WHERE biz_date = :h_bizdate;
|
||||
if (sqlca.sqlcode < 0) FAIL(b);
|
||||
if (rcdb_upsert_summary(bizdate, matched, unmatched, h_gross) < 0) FAIL(b);
|
||||
setl(b, T_COUNT, matched);
|
||||
setl(b, T_GROSS, h_gross);
|
||||
OK(b);
|
||||
}
|
||||
|
||||
/* 23. RC_REPROC - 재처리: EXCEPTION 대사 로그를 MATCHED 로 되돌림. */
|
||||
void RC_REPROC(TPSVCINFO *p)
|
||||
{
|
||||
UBFH *b = (UBFH *)p->data;
|
||||
long pid = getl(b, T_PURCHASE_ID);
|
||||
if (rcdb_update_match_status(pid, "EXCEPTION", "MATCHED") < 0) FAIL(b);
|
||||
Bchg(b, T_STATUS, 0, "M", 0L);
|
||||
OK(b);
|
||||
}
|
||||
|
||||
/* 24. RC_DELAY - 지연 대사 로그(익일 반영). */
|
||||
void RC_DELAY(TPSVCINFO *p)
|
||||
{
|
||||
UBFH *b = (UBFH *)p->data;
|
||||
char bizdate[16];
|
||||
long pid = getl(b, T_PURCHASE_ID), net = getl(b, T_NET), mid;
|
||||
gets_(b, T_BIZDATE, bizdate, sizeof(bizdate));
|
||||
mid = rcdb_insert_match(pid, "DELAY", net, "HOLD", bizdate);
|
||||
if (mid < 0) FAIL(b);
|
||||
setl(b, T_ID1, mid);
|
||||
OK(b);
|
||||
}
|
||||
|
||||
/* 25. RC_LEDGERLINK - 대사 원장 연계 기록 (dbio). */
|
||||
void RC_LEDGERLINK(TPSVCINFO *p)
|
||||
{
|
||||
UBFH *b = (UBFH *)p->data;
|
||||
char bizdate[16];
|
||||
long pid = getl(b, T_PURCHASE_ID), net = getl(b, T_NET);
|
||||
gets_(b, T_BIZDATE, bizdate, sizeof(bizdate));
|
||||
if (rcdb_insert_ledger(pid, "RC_LINK", net, bizdate) < 0) FAIL(b);
|
||||
OK(b);
|
||||
}
|
||||
|
||||
/* 26. RC_ALERT - 대사 경보 등록. */
|
||||
void RC_ALERT(TPSVCINFO *p)
|
||||
{
|
||||
UBFH *b = (UBFH *)p->data;
|
||||
char bizdate[16], msg[128], lvl[16];
|
||||
EXEC SQL BEGIN DECLARE SECTION;
|
||||
long h_id, h_pid;
|
||||
char h_bizdate[16], h_msg[128], h_lvl[16];
|
||||
EXEC SQL END DECLARE SECTION;
|
||||
h_pid = getl(b, T_PURCHASE_ID);
|
||||
gets_(b, T_BIZDATE, bizdate, sizeof(bizdate));
|
||||
gets_(b, T_MSG, msg, sizeof(msg)); if (msg[0] == 0) strcpy(msg, "대사 지연 경보");
|
||||
gets_(b, T_STR1, lvl, sizeof(lvl)); if (lvl[0] == 0) strcpy(lvl, "WARN");
|
||||
strncpy(h_bizdate, bizdate, sizeof(h_bizdate)-1); h_bizdate[sizeof(h_bizdate)-1] = 0;
|
||||
strncpy(h_msg, msg, sizeof(h_msg)-1); h_msg[sizeof(h_msg)-1] = 0;
|
||||
strncpy(h_lvl, lvl, sizeof(h_lvl)-1); h_lvl[sizeof(h_lvl)-1] = 0;
|
||||
EXEC SQL SELECT nextval('rc_alert_seq') INTO :h_id;
|
||||
if (sqlca.sqlcode < 0) FAIL(b);
|
||||
EXEC SQL INSERT INTO rc_alert (alert_id, purchase_id, lvl, message, biz_date)
|
||||
VALUES (:h_id, :h_pid, :h_lvl, :h_msg, :h_bizdate);
|
||||
if (sqlca.sqlcode < 0) { userlog("RC_ALERT INSERT FAIL [%d] %s", sqlca.sqlcode, sqlca.sqlerrm.sqlerrmc); FAIL(b); }
|
||||
setl(b, T_ID1, h_id);
|
||||
OK(b);
|
||||
}
|
||||
|
||||
/* 27. RC_MANUAL - 수동 대사 로그. */
|
||||
void RC_MANUAL(TPSVCINFO *p)
|
||||
{
|
||||
UBFH *b = (UBFH *)p->data;
|
||||
char bizdate[16];
|
||||
long pid = getl(b, T_PURCHASE_ID), net = getl(b, T_NET), mid;
|
||||
gets_(b, T_BIZDATE, bizdate, sizeof(bizdate));
|
||||
mid = rcdb_insert_match(pid, "MANUAL", net, "MATCHED", bizdate);
|
||||
if (mid < 0) FAIL(b);
|
||||
setl(b, T_ID1, mid);
|
||||
OK(b);
|
||||
}
|
||||
|
||||
/* 28. RC_ROLLBACK - 대사 롤백 (-> ROLLBACK 상태). */
|
||||
void RC_ROLLBACK(TPSVCINFO *p)
|
||||
{
|
||||
UBFH *b = (UBFH *)p->data;
|
||||
long pid = getl(b, T_PURCHASE_ID);
|
||||
if (rcdb_update_match_status(pid, "MATCHED", "ROLLBACK") < 0) FAIL(b);
|
||||
Bchg(b, T_STATUS, 0, "X", 0L);
|
||||
OK(b);
|
||||
}
|
||||
|
||||
/* 29. RC_CONFIRM - 대사 확정 (MATCHED -> CONFIRMED). */
|
||||
void RC_CONFIRM(TPSVCINFO *p)
|
||||
{
|
||||
UBFH *b = (UBFH *)p->data;
|
||||
long pid = getl(b, T_PURCHASE_ID);
|
||||
if (rcdb_update_match_status(pid, "MATCHED", "CONFIRMED") < 0) FAIL(b);
|
||||
Bchg(b, T_STATUS, 0, "C", 0L);
|
||||
OK(b);
|
||||
}
|
||||
|
||||
/* 30. RC_STATUS - 최근 대사 상태 조회. */
|
||||
void RC_STATUS(TPSVCINFO *p)
|
||||
{
|
||||
UBFH *b = (UBFH *)p->data;
|
||||
EXEC SQL BEGIN DECLARE SECTION;
|
||||
long h_pid;
|
||||
char h_status[16];
|
||||
EXEC SQL END DECLARE SECTION;
|
||||
h_pid = getl(b, T_PURCHASE_ID);
|
||||
EXEC SQL SELECT status INTO :h_status FROM rc_match_log
|
||||
WHERE purchase_id = :h_pid ORDER BY match_id DESC LIMIT 1;
|
||||
if (sqlca.sqlcode < 0) FAIL(b);
|
||||
if (sqlca.sqlcode == 100) strcpy(h_status, "NONE");
|
||||
Bchg(b, T_STATUS, 0, h_status, 0L);
|
||||
OK(b);
|
||||
}
|
||||
|
||||
|
|
|
|||
59
app/src/st/batch/st_feecalc_batch.pgc
Normal file
59
app/src/st/batch/st_feecalc_batch.pgc
Normal file
|
|
@ -0,0 +1,59 @@
|
|||
/*
|
||||
* st_feecalc_batch.pgc - st 수수료 계산 배치 (커서 + XA).
|
||||
*
|
||||
* 당일 정산 각 건에 대해 매입 금액 기준 구간요율(dbio)을 적용해 MDR 수수료를
|
||||
* 계산하고 st_settle_dtl 에 정산 상세를 (dbio 로) 기록한다 (ONE global XA tx).
|
||||
* 사용법: st_feecalc_batch [YYYY-MM-DD]
|
||||
*/
|
||||
#include <stdio.h>
|
||||
#include <stdlib.h>
|
||||
#include <string.h>
|
||||
#include <atmi.h>
|
||||
#include "st_dbio.h"
|
||||
#include "acq_common.h"
|
||||
|
||||
int main(int argc, char **argv)
|
||||
{
|
||||
const char *bizdate = (argc > 1) ? argv[1] : "2026-07-19";
|
||||
long rows = 0;
|
||||
|
||||
EXEC SQL BEGIN DECLARE SECTION;
|
||||
char h_bizdate[16], h_merch[64];
|
||||
long h_sid, h_pid, h_amount;
|
||||
EXEC SQL END DECLARE SECTION;
|
||||
|
||||
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 sfc CURSOR FOR
|
||||
SELECT s.settlement_id, s.purchase_id, s.merchant_id, p.amount
|
||||
FROM settlement s JOIN purchase p ON p.purchase_id = s.purchase_id
|
||||
WHERE s.biz_date = :h_bizdate
|
||||
ORDER BY s.settlement_id;
|
||||
EXEC SQL OPEN sfc;
|
||||
if (sqlca.sqlcode < 0) { fprintf(stderr, "OPEN FAIL [%d] %s\n", sqlca.sqlcode, sqlca.sqlerrm.sqlerrmc); tpabort(0); return 1; }
|
||||
|
||||
for (;;) {
|
||||
long bps, fee;
|
||||
EXEC SQL FETCH sfc INTO :h_sid, :h_pid, :h_merch, :h_amount;
|
||||
if (sqlca.sqlcode == 100) break;
|
||||
if (sqlca.sqlcode < 0) { fprintf(stderr, "FETCH FAIL [%d] %s\n", sqlca.sqlcode, sqlca.sqlerrm.sqlerrmc); EXEC SQL CLOSE sfc; tpabort(0); return 1; }
|
||||
bps = stdb_lookup_rate("MDR", h_amount);
|
||||
if (bps < 0) { EXEC SQL CLOSE sfc; tpabort(0); return 1; }
|
||||
fee = acq_fee(h_amount, bps);
|
||||
if (stdb_insert_fee_dtl(h_sid, h_pid, h_merch, "MDR", fee, bizdate) < 0) {
|
||||
EXEC SQL CLOSE sfc; tpabort(0); return 1;
|
||||
}
|
||||
rows++;
|
||||
}
|
||||
EXEC SQL CLOSE sfc;
|
||||
|
||||
if (tpcommit(0) < 0) { fprintf(stderr, "tpcommit FAIL: %s\n", tpstrerror(tperrno)); tpabort(0); return 1; }
|
||||
printf(">>> st_feecalc_batch COMMIT: bizdate=%s fee_rows=%ld\n", bizdate, rows);
|
||||
|
||||
tpclose(); tpterm();
|
||||
return 0;
|
||||
}
|
||||
56
app/src/st/batch/st_settle_batch.pgc
Normal file
56
app/src/st/batch/st_settle_batch.pgc
Normal file
|
|
@ -0,0 +1,56 @@
|
|||
/*
|
||||
* st_settle_batch.pgc - st 가맹점 정산 네팅 배치 (GROUP BY 커서 + XA).
|
||||
*
|
||||
* ATMI client: tpinit/tpopen 로 ECPG XA RM 를 열고 tpbegin 으로 ONE global tx 를
|
||||
* 시작한 뒤, 당일 정산(settlement)을 가맹점별로 GROUP BY 하여 net 합계를 구하고
|
||||
* st_merch_settle 에 upsert(dbio) 한다. 마지막 tpcommit 이 XA 2PC 구동.
|
||||
* 사용법: st_settle_batch [YYYY-MM-DD]
|
||||
*/
|
||||
#include <stdio.h>
|
||||
#include <stdlib.h>
|
||||
#include <string.h>
|
||||
#include <atmi.h>
|
||||
#include "st_dbio.h"
|
||||
|
||||
int main(int argc, char **argv)
|
||||
{
|
||||
const char *bizdate = (argc > 1) ? argv[1] : "2026-07-19";
|
||||
long rows = 0;
|
||||
|
||||
EXEC SQL BEGIN DECLARE SECTION;
|
||||
char h_bizdate[16], h_merch[64];
|
||||
long h_cnt, h_gross, h_fee, h_net;
|
||||
EXEC SQL END DECLARE SECTION;
|
||||
|
||||
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 ssc CURSOR FOR
|
||||
SELECT s.merchant_id, count(*), coalesce(sum(p.amount),0),
|
||||
coalesce(sum(p.fee),0), coalesce(sum(s.net),0)
|
||||
FROM settlement s JOIN purchase p ON p.purchase_id = s.purchase_id
|
||||
WHERE s.biz_date = :h_bizdate
|
||||
GROUP BY s.merchant_id ORDER BY s.merchant_id;
|
||||
EXEC SQL OPEN ssc;
|
||||
if (sqlca.sqlcode < 0) { fprintf(stderr, "OPEN FAIL [%d] %s\n", sqlca.sqlcode, sqlca.sqlerrm.sqlerrmc); tpabort(0); return 1; }
|
||||
|
||||
for (;;) {
|
||||
EXEC SQL FETCH ssc INTO :h_merch, :h_cnt, :h_gross, :h_fee, :h_net;
|
||||
if (sqlca.sqlcode == 100) break;
|
||||
if (sqlca.sqlcode < 0) { fprintf(stderr, "FETCH FAIL [%d] %s\n", sqlca.sqlcode, sqlca.sqlerrm.sqlerrmc); EXEC SQL CLOSE ssc; tpabort(0); return 1; }
|
||||
if (stdb_upsert_merch_settle(h_merch, bizdate, h_gross, h_fee, h_net, h_cnt) < 0) {
|
||||
EXEC SQL CLOSE ssc; tpabort(0); return 1;
|
||||
}
|
||||
rows++;
|
||||
}
|
||||
EXEC SQL CLOSE ssc;
|
||||
|
||||
if (tpcommit(0) < 0) { fprintf(stderr, "tpcommit FAIL: %s\n", tpstrerror(tperrno)); tpabort(0); return 1; }
|
||||
printf(">>> st_settle_batch COMMIT: bizdate=%s merchants=%ld\n", bizdate, rows);
|
||||
|
||||
tpclose(); tpterm();
|
||||
return 0;
|
||||
}
|
||||
25
app/src/st/dbio/st_dbio.h
Normal file
25
app/src/st/dbio/st_dbio.h
Normal file
|
|
@ -0,0 +1,25 @@
|
|||
/*
|
||||
* st_dbio.h - st (정산/수수료) 모듈 DB 접근 계층 (libstdbio.a).
|
||||
* ECPG (EXEC SQL) 함수들을 정적 라이브러리로 묶어 st_svr / st 배치에 링크한다.
|
||||
* 호출자의 XA 브랜치/커넥션에서 수행되며 EXEC SQL CONNECT 는 없다.
|
||||
* 반환 규약: 0 = ok, -1 = SQL error (음수 ID/rate 는 오류).
|
||||
*/
|
||||
#ifndef ST_DBIO_H
|
||||
#define ST_DBIO_H
|
||||
|
||||
/* 정산 상세 / 지급 / 가맹점 집계 (st_settle_dbio.pgc) */
|
||||
long stdb_insert_fee_dtl(long sid, long pid, const char *merch,
|
||||
const char *fee_type, long amount, const char *bizdate);
|
||||
long stdb_insert_payment(const char *merch, long net, const char *paydate,
|
||||
const char *status, const char *bizdate);
|
||||
int stdb_update_payment_status(const char *merch, const char *from_status,
|
||||
const char *to_status, const char *bizdate);
|
||||
int stdb_upsert_merch_settle(const char *merch, const char *bizdate, long gross,
|
||||
long fee, long net, long cnt);
|
||||
|
||||
/* 수수료 요율 / 조정 (st_fee_dbio.pgc) */
|
||||
long stdb_lookup_rate(const char *fee_type, long amount); /* 구간 요율(bps), -1=오류 */
|
||||
long stdb_insert_adjust(long sid, long pid, long delta, const char *reason,
|
||||
const char *bizdate);
|
||||
|
||||
#endif /* ST_DBIO_H */
|
||||
51
app/src/st/dbio/st_fee_dbio.pgc
Normal file
51
app/src/st/dbio/st_fee_dbio.pgc
Normal file
|
|
@ -0,0 +1,51 @@
|
|||
/*
|
||||
* st_fee_dbio.pgc - 수수료 구간요율 조회 + 정산 조정 DB 접근 (libstdbio.a).
|
||||
* ECPG 함수; 호출자의 XA 브랜치에서 수행. EXEC SQL CONNECT 없음.
|
||||
*/
|
||||
#include <string.h>
|
||||
#include <userlog.h>
|
||||
#include "st_dbio.h"
|
||||
|
||||
/* 금액에 해당하는 최상위 구간 요율(bps)을 반환. 없으면 0, SQL 오류면 -1. */
|
||||
long stdb_lookup_rate(const char *fee_type, long amount)
|
||||
{
|
||||
EXEC SQL BEGIN DECLARE SECTION;
|
||||
long h_bps, h_amt = amount;
|
||||
char h_type[16];
|
||||
EXEC SQL END DECLARE SECTION;
|
||||
|
||||
strncpy(h_type, fee_type, sizeof(h_type) - 1); h_type[sizeof(h_type)-1] = 0;
|
||||
|
||||
EXEC SQL SELECT rate_bps INTO :h_bps FROM st_fee_rate
|
||||
WHERE fee_type = :h_type AND band_min <= :h_amt
|
||||
ORDER BY band_min DESC LIMIT 1;
|
||||
if (sqlca.sqlcode == 100) return 0;
|
||||
if (sqlca.sqlcode < 0) {
|
||||
userlog("stdb_lookup_rate FAIL [%d] %s", sqlca.sqlcode, sqlca.sqlerrm.sqlerrmc);
|
||||
return -1;
|
||||
}
|
||||
return h_bps;
|
||||
}
|
||||
|
||||
long stdb_insert_adjust(long sid, long pid, long delta, const char *reason,
|
||||
const char *bizdate)
|
||||
{
|
||||
EXEC SQL BEGIN DECLARE SECTION;
|
||||
long h_id, h_sid = sid, h_pid = pid, h_delta = delta;
|
||||
char h_reason[128], h_bizdate[16];
|
||||
EXEC SQL END DECLARE SECTION;
|
||||
|
||||
strncpy(h_reason, reason, sizeof(h_reason) - 1); h_reason[sizeof(h_reason)-1] = 0;
|
||||
strncpy(h_bizdate, bizdate, sizeof(h_bizdate) - 1); h_bizdate[sizeof(h_bizdate)-1] = 0;
|
||||
|
||||
EXEC SQL SELECT nextval('st_adj_seq') INTO :h_id;
|
||||
if (sqlca.sqlcode < 0) return -1;
|
||||
|
||||
EXEC SQL INSERT INTO st_adjust (adj_id, settlement_id, purchase_id, delta, reason, biz_date)
|
||||
VALUES (:h_id, :h_sid, :h_pid, :h_delta, :h_reason, :h_bizdate);
|
||||
if (sqlca.sqlcode < 0) {
|
||||
userlog("stdb_insert_adjust FAIL [%d] %s", sqlca.sqlcode, sqlca.sqlerrm.sqlerrmc);
|
||||
return -1;
|
||||
}
|
||||
return h_id;
|
||||
}
|
||||
105
app/src/st/dbio/st_settle_dbio.pgc
Normal file
105
app/src/st/dbio/st_settle_dbio.pgc
Normal file
|
|
@ -0,0 +1,105 @@
|
|||
/*
|
||||
* st_settle_dbio.pgc - 정산 상세/지급/가맹점 집계 DB 접근 (libstdbio.a).
|
||||
* ECPG 함수; 호출자의 XA 브랜치에서 수행. EXEC SQL CONNECT 없음.
|
||||
*/
|
||||
#include <string.h>
|
||||
#include <userlog.h>
|
||||
#include "st_dbio.h"
|
||||
|
||||
long stdb_insert_fee_dtl(long sid, long pid, const char *merch,
|
||||
const char *fee_type, long amount, const char *bizdate)
|
||||
{
|
||||
EXEC SQL BEGIN DECLARE SECTION;
|
||||
long h_id, h_sid = sid, h_pid = pid, h_amt = amount;
|
||||
char h_merch[64], h_type[16], h_bizdate[16];
|
||||
EXEC SQL END DECLARE SECTION;
|
||||
|
||||
strncpy(h_merch, merch, sizeof(h_merch) - 1); h_merch[sizeof(h_merch)-1] = 0;
|
||||
strncpy(h_type, fee_type, sizeof(h_type) - 1); h_type[sizeof(h_type)-1] = 0;
|
||||
strncpy(h_bizdate, bizdate, sizeof(h_bizdate) - 1); h_bizdate[sizeof(h_bizdate)-1] = 0;
|
||||
|
||||
EXEC SQL SELECT nextval('st_dtl_seq') INTO :h_id;
|
||||
if (sqlca.sqlcode < 0) return -1;
|
||||
|
||||
EXEC SQL INSERT INTO st_settle_dtl
|
||||
(dtl_id, settlement_id, purchase_id, merchant_id, fee_type, amount, biz_date)
|
||||
VALUES (:h_id, :h_sid, :h_pid, :h_merch, :h_type, :h_amt, :h_bizdate);
|
||||
if (sqlca.sqlcode < 0) {
|
||||
userlog("stdb_insert_fee_dtl FAIL [%d] %s", sqlca.sqlcode, sqlca.sqlerrm.sqlerrmc);
|
||||
return -1;
|
||||
}
|
||||
return h_id;
|
||||
}
|
||||
|
||||
long stdb_insert_payment(const char *merch, long net, const char *paydate,
|
||||
const char *status, const char *bizdate)
|
||||
{
|
||||
EXEC SQL BEGIN DECLARE SECTION;
|
||||
long h_id, h_net = net;
|
||||
char h_merch[64], h_paydate[16], h_status[16], h_bizdate[16];
|
||||
EXEC SQL END DECLARE SECTION;
|
||||
|
||||
strncpy(h_merch, merch, sizeof(h_merch) - 1); h_merch[sizeof(h_merch)-1] = 0;
|
||||
strncpy(h_paydate, paydate, sizeof(h_paydate) - 1); h_paydate[sizeof(h_paydate)-1] = 0;
|
||||
strncpy(h_status, status, sizeof(h_status) - 1); h_status[sizeof(h_status)-1] = 0;
|
||||
strncpy(h_bizdate, bizdate, sizeof(h_bizdate) - 1); h_bizdate[sizeof(h_bizdate)-1] = 0;
|
||||
|
||||
EXEC SQL SELECT nextval('st_pay_seq') INTO :h_id;
|
||||
if (sqlca.sqlcode < 0) return -1;
|
||||
|
||||
EXEC SQL INSERT INTO st_payment (pay_id, merchant_id, net_amount, pay_date, status, biz_date)
|
||||
VALUES (:h_id, :h_merch, :h_net, :h_paydate, :h_status, :h_bizdate);
|
||||
if (sqlca.sqlcode < 0) {
|
||||
userlog("stdb_insert_payment FAIL [%d] %s", sqlca.sqlcode, sqlca.sqlerrm.sqlerrmc);
|
||||
return -1;
|
||||
}
|
||||
return h_id;
|
||||
}
|
||||
|
||||
int stdb_update_payment_status(const char *merch, const char *from_status,
|
||||
const char *to_status, const char *bizdate)
|
||||
{
|
||||
EXEC SQL BEGIN DECLARE SECTION;
|
||||
char h_merch[64], h_from[16], h_to[16], h_bizdate[16];
|
||||
EXEC SQL END DECLARE SECTION;
|
||||
|
||||
strncpy(h_merch, merch, sizeof(h_merch) - 1); h_merch[sizeof(h_merch)-1] = 0;
|
||||
strncpy(h_from, from_status, sizeof(h_from) - 1); h_from[sizeof(h_from)-1] = 0;
|
||||
strncpy(h_to, to_status, sizeof(h_to) - 1); h_to[sizeof(h_to)-1] = 0;
|
||||
strncpy(h_bizdate, bizdate, sizeof(h_bizdate) - 1); h_bizdate[sizeof(h_bizdate)-1] = 0;
|
||||
|
||||
EXEC SQL UPDATE st_payment SET status = :h_to
|
||||
WHERE merchant_id = :h_merch AND status = :h_from AND biz_date = :h_bizdate;
|
||||
if (sqlca.sqlcode < 0) {
|
||||
userlog("stdb_update_payment_status FAIL [%d] %s", sqlca.sqlcode, sqlca.sqlerrm.sqlerrmc);
|
||||
return -1;
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
int stdb_upsert_merch_settle(const char *merch, const char *bizdate, long gross,
|
||||
long fee, long net, long cnt)
|
||||
{
|
||||
EXEC SQL BEGIN DECLARE SECTION;
|
||||
long h_gross = gross, h_fee = fee, h_net = net, h_cnt = cnt;
|
||||
char h_merch[64], h_bizdate[16];
|
||||
EXEC SQL END DECLARE SECTION;
|
||||
|
||||
strncpy(h_merch, merch, sizeof(h_merch) - 1); h_merch[sizeof(h_merch)-1] = 0;
|
||||
strncpy(h_bizdate, bizdate, sizeof(h_bizdate) - 1); h_bizdate[sizeof(h_bizdate)-1] = 0;
|
||||
|
||||
EXEC SQL INSERT INTO st_merch_settle
|
||||
(merchant_id, biz_date, txn_count, gross_amount, fee_amount, net_amount)
|
||||
VALUES (:h_merch, :h_bizdate, :h_cnt, :h_gross, :h_fee, :h_net)
|
||||
ON CONFLICT (merchant_id, biz_date) DO UPDATE
|
||||
SET txn_count = st_merch_settle.txn_count + EXCLUDED.txn_count,
|
||||
gross_amount = st_merch_settle.gross_amount + EXCLUDED.gross_amount,
|
||||
fee_amount = st_merch_settle.fee_amount + EXCLUDED.fee_amount,
|
||||
net_amount = st_merch_settle.net_amount + EXCLUDED.net_amount,
|
||||
updated_at = now();
|
||||
if (sqlca.sqlcode < 0) {
|
||||
userlog("stdb_upsert_merch_settle FAIL [%d] %s", sqlca.sqlcode, sqlca.sqlerrm.sqlerrmc);
|
||||
return -1;
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
|
@ -1,9 +1,16 @@
|
|||
/*
|
||||
* st_svr.pgc - st (정산/수수료) MODULE SERVER.
|
||||
*
|
||||
* Advertises the settlement services. SETTLE keeps the proven behavior: its own
|
||||
* XA branch writes the settlement + settle ledger rows (the purchase status
|
||||
* transition is owned by the ACQUIRE branch). Terminal service of the 매입 chain.
|
||||
* ONE binary that tpadvertise()s ALL ~30 online st(정산/수수료) services — the
|
||||
* real Enduro-X "one module server, many services" pattern, modeled on ac_svr.
|
||||
* Every service is a distinct 정산/수수료 operation with genuine EXEC SQL (directly,
|
||||
* or via the linked libstdbio.a dbio layer) plus fee math from libacqcommon.a.
|
||||
*
|
||||
* XA: the connection is opened once by tpopen() (ECPG XA switch); there is NO
|
||||
* EXEC SQL CONNECT. SETTLE keeps the proven behavior: its own XA branch writes
|
||||
* the settlement + settle ledger rows (the purchase status transition is owned by
|
||||
* the ACQUIRE branch). Terminal service of the 매입 chain. Owner-writes: st
|
||||
* services never tpcall each other.
|
||||
*/
|
||||
#include <stdio.h>
|
||||
#include <stdlib.h>
|
||||
|
|
@ -13,7 +20,10 @@
|
|||
#include <userlog.h>
|
||||
#include <ndebug.h>
|
||||
#include "acq.fd.h"
|
||||
#include "acq_common.h"
|
||||
#include "st_dbio.h"
|
||||
|
||||
/* ----- small UBF helpers ------------------------------------------------- */
|
||||
static long getl(UBFH *b, BFLDID f) { long v = 0; Bget(b, f, 0, (char *)&v, 0L); return v; }
|
||||
static void gets_(UBFH *b, BFLDID f, char *out, int cap)
|
||||
{ BFLDLEN l = cap; out[0] = 0; Bget(b, f, 0, out, &l); }
|
||||
|
|
@ -22,14 +32,40 @@ static void setl(UBFH *b, BFLDID f, long v) { Bchg(b, f, 0, (char *)&v, 0L); }
|
|||
#define FAIL(b) do { tpreturn(TPFAIL, 0, (char *)(b), 0L, 0L); return; } while (0)
|
||||
#define OK(b) do { tpreturn(TPSUCCESS, 0, (char *)(b), 0L, 0L); return; } while (0)
|
||||
|
||||
void SETTLE(TPSVCINFO *p);
|
||||
void ST_MDR(TPSVCINFO *p);
|
||||
void ST_NETTING(TPSVCINFO *p);
|
||||
void ST_VAT(TPSVCINFO *p);
|
||||
/* ----- service prototypes ------------------------------------------------ */
|
||||
void SETTLE(TPSVCINFO *p); void ST_MDR(TPSVCINFO *p);
|
||||
void ST_NETTING(TPSVCINFO *p); void ST_VAT(TPSVCINFO *p);
|
||||
void ST_VANFEE(TPSVCINFO *p); void ST_RELAYFEE(TPSVCINFO *p);
|
||||
void ST_WHT(TPSVCINFO *p); void ST_ADJUST(TPSVCINFO *p);
|
||||
void ST_ADVANCE(TPSVCINFO *p); void ST_DELAYINT(TPSVCINFO *p);
|
||||
void ST_HOLD(TPSVCINFO *p); void ST_RELEASE(TPSVCINFO *p);
|
||||
void ST_SPLIT(TPSVCINFO *p); void ST_PAYDATE(TPSVCINFO *p);
|
||||
void ST_BYMERCH(TPSVCINFO *p); void ST_MONTHLY(TPSVCINFO *p);
|
||||
void ST_OFFSET(TPSVCINFO *p); void ST_DIFFCHK(TPSVCINFO *p);
|
||||
void ST_RECALC(TPSVCINFO *p); void ST_RATEAPPLY(TPSVCINFO *p);
|
||||
void ST_REPORT(TPSVCINFO *p); void ST_XFERLINK(TPSVCINFO *p);
|
||||
void ST_STATS(TPSVCINFO *p); void ST_CANCEL(TPSVCINFO *p);
|
||||
void ST_LEDGERLINK(TPSVCINFO *p);void ST_BANDRATE(TPSVCINFO *p);
|
||||
void ST_CAP(TPSVCINFO *p); void ST_FXCONV(TPSVCINFO *p);
|
||||
void ST_CONFIRM(TPSVCINFO *p); void ST_STATUS(TPSVCINFO *p);
|
||||
|
||||
/* ----- advertise table --------------------------------------------------- */
|
||||
static struct { const char *name; void (*fn)(TPSVCINFO *); } SVCS[] = {
|
||||
{"SETTLE", SETTLE}, {"ST_MDR", ST_MDR},
|
||||
{"ST_NETTING", ST_NETTING}, {"ST_VAT", ST_VAT},
|
||||
{"SETTLE", SETTLE}, {"ST_MDR", ST_MDR},
|
||||
{"ST_NETTING", ST_NETTING}, {"ST_VAT", ST_VAT},
|
||||
{"ST_VANFEE", ST_VANFEE}, {"ST_RELAYFEE", ST_RELAYFEE},
|
||||
{"ST_WHT", ST_WHT}, {"ST_ADJUST", ST_ADJUST},
|
||||
{"ST_ADVANCE", ST_ADVANCE}, {"ST_DELAYINT", ST_DELAYINT},
|
||||
{"ST_HOLD", ST_HOLD}, {"ST_RELEASE", ST_RELEASE},
|
||||
{"ST_SPLIT", ST_SPLIT}, {"ST_PAYDATE", ST_PAYDATE},
|
||||
{"ST_BYMERCH", ST_BYMERCH}, {"ST_MONTHLY", ST_MONTHLY},
|
||||
{"ST_OFFSET", ST_OFFSET}, {"ST_DIFFCHK", ST_DIFFCHK},
|
||||
{"ST_RECALC", ST_RECALC}, {"ST_RATEAPPLY", ST_RATEAPPLY},
|
||||
{"ST_REPORT", ST_REPORT}, {"ST_XFERLINK", ST_XFERLINK},
|
||||
{"ST_STATS", ST_STATS}, {"ST_CANCEL", ST_CANCEL},
|
||||
{"ST_LEDGERLINK", ST_LEDGERLINK},{"ST_BANDRATE", ST_BANDRATE},
|
||||
{"ST_CAP", ST_CAP}, {"ST_FXCONV", ST_FXCONV},
|
||||
{"ST_CONFIRM", ST_CONFIRM}, {"ST_STATUS", ST_STATUS},
|
||||
{NULL, NULL}
|
||||
};
|
||||
|
||||
|
|
@ -48,6 +84,9 @@ int tpsvrinit(int argc, char **argv)
|
|||
|
||||
void tpsvrdone(void) { tpclose(); userlog("st_svr: tpsvrdone"); }
|
||||
|
||||
/* ========================================================================= */
|
||||
/* 1. SETTLE - 정산 + settle 원장 (own branch). 매입 체인 종단. (KEEP) */
|
||||
/* ========================================================================= */
|
||||
void SETTLE(TPSVCINFO *p)
|
||||
{
|
||||
UBFH *b = (UBFH *)p->data;
|
||||
|
|
@ -79,7 +118,7 @@ void SETTLE(TPSVCINFO *p)
|
|||
OK(b);
|
||||
}
|
||||
|
||||
/* ST_MDR - MDR 수수료 원장 기록. */
|
||||
/* 2. ST_MDR - MDR 수수료 원장 기록. (KEEP) */
|
||||
void ST_MDR(TPSVCINFO *p)
|
||||
{
|
||||
UBFH *b = (UBFH *)p->data;
|
||||
|
|
@ -97,7 +136,7 @@ void ST_MDR(TPSVCINFO *p)
|
|||
OK(b);
|
||||
}
|
||||
|
||||
/* ST_NETTING - 당일 net 합계 조회. */
|
||||
/* 3. ST_NETTING - 당일 net 합계 조회. (KEEP) */
|
||||
void ST_NETTING(TPSVCINFO *p)
|
||||
{
|
||||
UBFH *b = (UBFH *)p->data;
|
||||
|
|
@ -117,7 +156,7 @@ void ST_NETTING(TPSVCINFO *p)
|
|||
OK(b);
|
||||
}
|
||||
|
||||
/* ST_VAT - 정산 부가세 원장 기록. */
|
||||
/* 4. ST_VAT - 정산 부가세 원장 기록. (KEEP) */
|
||||
void ST_VAT(TPSVCINFO *p)
|
||||
{
|
||||
UBFH *b = (UBFH *)p->data;
|
||||
|
|
@ -136,4 +175,454 @@ void ST_VAT(TPSVCINFO *p)
|
|||
OK(b);
|
||||
}
|
||||
|
||||
/* 5. ST_VANFEE - VAN 수수료: 구간요율 조회 후 정산 상세 기록 (dbio). */
|
||||
void ST_VANFEE(TPSVCINFO *p)
|
||||
{
|
||||
UBFH *b = (UBFH *)p->data;
|
||||
char merch[64], bizdate[16];
|
||||
long sid = getl(b, T_SETTLE_ID), pid = getl(b, T_PURCHASE_ID);
|
||||
long amount = getl(b, T_AMOUNT), bps, fee, dtl;
|
||||
gets_(b, T_MERCHANT, merch, sizeof(merch));
|
||||
gets_(b, T_BIZDATE, bizdate, sizeof(bizdate));
|
||||
bps = stdb_lookup_rate("VAN", amount);
|
||||
if (bps < 0) FAIL(b);
|
||||
fee = acq_fee(amount, bps);
|
||||
dtl = stdb_insert_fee_dtl(sid, pid, merch, "VAN", fee, bizdate);
|
||||
if (dtl < 0) FAIL(b);
|
||||
setl(b, T_FEE, fee);
|
||||
OK(b);
|
||||
}
|
||||
|
||||
/* 6. ST_RELAYFEE - 중계 수수료: 구간요율 조회 후 정산 상세 기록 (dbio). */
|
||||
void ST_RELAYFEE(TPSVCINFO *p)
|
||||
{
|
||||
UBFH *b = (UBFH *)p->data;
|
||||
char merch[64], bizdate[16];
|
||||
long sid = getl(b, T_SETTLE_ID), pid = getl(b, T_PURCHASE_ID);
|
||||
long amount = getl(b, T_AMOUNT), bps, fee, dtl;
|
||||
gets_(b, T_MERCHANT, merch, sizeof(merch));
|
||||
gets_(b, T_BIZDATE, bizdate, sizeof(bizdate));
|
||||
bps = stdb_lookup_rate("RELAY", amount);
|
||||
if (bps < 0) FAIL(b);
|
||||
fee = acq_fee(amount, bps);
|
||||
dtl = stdb_insert_fee_dtl(sid, pid, merch, "RELAY", fee, bizdate);
|
||||
if (dtl < 0) FAIL(b);
|
||||
setl(b, T_FEE, fee);
|
||||
OK(b);
|
||||
}
|
||||
|
||||
/* 7. ST_WHT - 원천징수(수수료의 22%) 정산 상세 기록 (dbio). */
|
||||
void ST_WHT(TPSVCINFO *p)
|
||||
{
|
||||
UBFH *b = (UBFH *)p->data;
|
||||
char merch[64], bizdate[16];
|
||||
long sid = getl(b, T_SETTLE_ID), pid = getl(b, T_PURCHASE_ID);
|
||||
long fee = getl(b, T_FEE), tax, dtl;
|
||||
gets_(b, T_MERCHANT, merch, sizeof(merch));
|
||||
gets_(b, T_BIZDATE, bizdate, sizeof(bizdate));
|
||||
tax = acq_wht_amt(fee, 2200);
|
||||
dtl = stdb_insert_fee_dtl(sid, pid, merch, "WHT", tax, bizdate);
|
||||
if (dtl < 0) FAIL(b);
|
||||
setl(b, T_TAX, tax);
|
||||
OK(b);
|
||||
}
|
||||
|
||||
/* 8. ST_ADJUST - 정산 조정 (가감) 기록 (dbio). */
|
||||
void ST_ADJUST(TPSVCINFO *p)
|
||||
{
|
||||
UBFH *b = (UBFH *)p->data;
|
||||
char bizdate[16], reason[128];
|
||||
long sid = getl(b, T_SETTLE_ID), pid = getl(b, T_PURCHASE_ID);
|
||||
long delta = getl(b, T_DELTA), adj;
|
||||
gets_(b, T_BIZDATE, bizdate, sizeof(bizdate));
|
||||
gets_(b, T_REASON, reason, sizeof(reason)); if (reason[0] == 0) strcpy(reason, "ADJ");
|
||||
adj = stdb_insert_adjust(sid, pid, delta, reason, bizdate);
|
||||
if (adj < 0) FAIL(b);
|
||||
setl(b, T_ADJ, adj);
|
||||
OK(b);
|
||||
}
|
||||
|
||||
/* 9. ST_ADVANCE - 선지급 예약: 지급 레코드(SCHEDULED) 생성 (dbio). */
|
||||
void ST_ADVANCE(TPSVCINFO *p)
|
||||
{
|
||||
UBFH *b = (UBFH *)p->data;
|
||||
char merch[64], bizdate[16], paydate[16];
|
||||
long net = getl(b, T_NET), pay;
|
||||
gets_(b, T_MERCHANT, merch, sizeof(merch));
|
||||
gets_(b, T_BIZDATE, bizdate, sizeof(bizdate));
|
||||
gets_(b, T_KEY1, paydate, sizeof(paydate));
|
||||
if (paydate[0] == 0) { strncpy(paydate, bizdate, sizeof(paydate)-1); paydate[sizeof(paydate)-1] = 0; }
|
||||
pay = stdb_insert_payment(merch, net, paydate, "SCHEDULED", bizdate);
|
||||
if (pay < 0) FAIL(b);
|
||||
setl(b, T_ID1, pay);
|
||||
OK(b);
|
||||
}
|
||||
|
||||
/* 10. ST_DELAYINT - 지연이자(net * bps/10000 * 일수) 정산 상세 기록 (dbio). */
|
||||
void ST_DELAYINT(TPSVCINFO *p)
|
||||
{
|
||||
UBFH *b = (UBFH *)p->data;
|
||||
char merch[64], bizdate[16];
|
||||
long sid = getl(b, T_SETTLE_ID), pid = getl(b, T_PURCHASE_ID);
|
||||
long net = getl(b, T_NET), days = getl(b, T_ARG1), bps = getl(b, T_ARG2), intr, dtl;
|
||||
gets_(b, T_MERCHANT, merch, sizeof(merch));
|
||||
gets_(b, T_BIZDATE, bizdate, sizeof(bizdate));
|
||||
if (days <= 0) days = 1;
|
||||
if (bps <= 0) bps = 15; /* 일 0.15% 기본 */
|
||||
intr = acq_fee(net, bps) * days;
|
||||
dtl = stdb_insert_fee_dtl(sid, pid, merch, "DELAYINT", intr, bizdate);
|
||||
if (dtl < 0) FAIL(b);
|
||||
setl(b, T_AMOUNT, intr);
|
||||
OK(b);
|
||||
}
|
||||
|
||||
/* 11. ST_HOLD - 가맹점 지급 보류 (SCHEDULED -> HELD) (dbio). */
|
||||
void ST_HOLD(TPSVCINFO *p)
|
||||
{
|
||||
UBFH *b = (UBFH *)p->data;
|
||||
char merch[64], bizdate[16];
|
||||
gets_(b, T_MERCHANT, merch, sizeof(merch));
|
||||
gets_(b, T_BIZDATE, bizdate, sizeof(bizdate));
|
||||
if (stdb_update_payment_status(merch, "SCHEDULED", "HELD", bizdate) < 0) FAIL(b);
|
||||
Bchg(b, T_STATUS, 0, "H", 0L);
|
||||
OK(b);
|
||||
}
|
||||
|
||||
/* 12. ST_RELEASE - 지급 보류 해제 (HELD -> SCHEDULED) (dbio). */
|
||||
void ST_RELEASE(TPSVCINFO *p)
|
||||
{
|
||||
UBFH *b = (UBFH *)p->data;
|
||||
char merch[64], bizdate[16];
|
||||
gets_(b, T_MERCHANT, merch, sizeof(merch));
|
||||
gets_(b, T_BIZDATE, bizdate, sizeof(bizdate));
|
||||
if (stdb_update_payment_status(merch, "HELD", "SCHEDULED", bizdate) < 0) FAIL(b);
|
||||
Bchg(b, T_STATUS, 0, "R", 0L);
|
||||
OK(b);
|
||||
}
|
||||
|
||||
/* 13. ST_SPLIT - 분할 지급: net 을 n 회로 나눠 지급 레코드 생성 (dbio 루프). */
|
||||
void ST_SPLIT(TPSVCINFO *p)
|
||||
{
|
||||
UBFH *b = (UBFH *)p->data;
|
||||
char merch[64], bizdate[16];
|
||||
long net = getl(b, T_NET), n = getl(b, T_INSTALL_N), part, i, pay;
|
||||
gets_(b, T_MERCHANT, merch, sizeof(merch));
|
||||
gets_(b, T_BIZDATE, bizdate, sizeof(bizdate));
|
||||
if (n < 1) n = 2;
|
||||
part = net / n;
|
||||
for (i = 0; i < n; i++) {
|
||||
pay = stdb_insert_payment(merch, part, bizdate, "SCHEDULED", bizdate);
|
||||
if (pay < 0) FAIL(b);
|
||||
}
|
||||
setl(b, T_COUNT, n);
|
||||
OK(b);
|
||||
}
|
||||
|
||||
/* 14. ST_PAYDATE - 지급일 산정(익영업일) 후 지급일 갱신. */
|
||||
void ST_PAYDATE(TPSVCINFO *p)
|
||||
{
|
||||
UBFH *b = (UBFH *)p->data;
|
||||
char merch[64], bizdate[16], paydate[16];
|
||||
EXEC SQL BEGIN DECLARE SECTION;
|
||||
char h_merch[64], h_bizdate[16], h_paydate[16];
|
||||
EXEC SQL END DECLARE SECTION;
|
||||
gets_(b, T_MERCHANT, merch, sizeof(merch));
|
||||
gets_(b, T_BIZDATE, bizdate, sizeof(bizdate));
|
||||
if (acq_next_bizday(bizdate, paydate) < 0) FAIL(b);
|
||||
strncpy(h_merch, merch, sizeof(h_merch)-1); h_merch[sizeof(h_merch)-1] = 0;
|
||||
strncpy(h_bizdate, bizdate, sizeof(h_bizdate)-1); h_bizdate[sizeof(h_bizdate)-1] = 0;
|
||||
strncpy(h_paydate, paydate, sizeof(h_paydate)-1); h_paydate[sizeof(h_paydate)-1] = 0;
|
||||
EXEC SQL UPDATE st_payment SET pay_date = :h_paydate
|
||||
WHERE merchant_id = :h_merch AND biz_date = :h_bizdate AND status = 'SCHEDULED';
|
||||
if (sqlca.sqlcode < 0) FAIL(b);
|
||||
Bchg(b, T_KEY1, 0, paydate, 0L);
|
||||
OK(b);
|
||||
}
|
||||
|
||||
/* 15. ST_BYMERCH - 가맹점별 정산 집계 upsert (dbio). */
|
||||
void ST_BYMERCH(TPSVCINFO *p)
|
||||
{
|
||||
UBFH *b = (UBFH *)p->data;
|
||||
char merch[64], bizdate[16];
|
||||
EXEC SQL BEGIN DECLARE SECTION;
|
||||
long h_cnt, h_gross, h_fee, h_net;
|
||||
char h_merch[64], h_bizdate[16];
|
||||
EXEC SQL END DECLARE SECTION;
|
||||
gets_(b, T_MERCHANT, merch, sizeof(merch));
|
||||
gets_(b, T_BIZDATE, bizdate, sizeof(bizdate));
|
||||
strncpy(h_merch, merch, sizeof(h_merch)-1); h_merch[sizeof(h_merch)-1] = 0;
|
||||
strncpy(h_bizdate, bizdate, sizeof(h_bizdate)-1); h_bizdate[sizeof(h_bizdate)-1] = 0;
|
||||
EXEC SQL SELECT count(*), coalesce(sum(p.amount),0), coalesce(sum(p.fee),0), coalesce(sum(s.net),0)
|
||||
INTO :h_cnt, :h_gross, :h_fee, :h_net
|
||||
FROM settlement s JOIN purchase p ON p.purchase_id = s.purchase_id
|
||||
WHERE s.merchant_id = :h_merch AND s.biz_date = :h_bizdate;
|
||||
if (sqlca.sqlcode < 0) FAIL(b);
|
||||
if (stdb_upsert_merch_settle(merch, bizdate, h_gross, h_fee, h_net, h_cnt) < 0) FAIL(b);
|
||||
setl(b, T_COUNT, h_cnt); setl(b, T_GROSS, h_gross);
|
||||
OK(b);
|
||||
}
|
||||
|
||||
/* 16. ST_MONTHLY - 월별 정산 net 합계 (biz_date LIKE 'YYYY-MM%'). */
|
||||
void ST_MONTHLY(TPSVCINFO *p)
|
||||
{
|
||||
UBFH *b = (UBFH *)p->data;
|
||||
char merch[64], ym[16], like[16];
|
||||
EXEC SQL BEGIN DECLARE SECTION;
|
||||
long h_sum;
|
||||
char h_merch[64], h_like[16];
|
||||
EXEC SQL END DECLARE SECTION;
|
||||
gets_(b, T_MERCHANT, merch, sizeof(merch));
|
||||
gets_(b, T_KEY1, ym, sizeof(ym)); if (ym[0] == 0) strcpy(ym, "2026-07");
|
||||
snprintf(like, sizeof(like), "%s%%", ym);
|
||||
strncpy(h_merch, merch, sizeof(h_merch)-1); h_merch[sizeof(h_merch)-1] = 0;
|
||||
strncpy(h_like, like, sizeof(h_like)-1); h_like[sizeof(h_like)-1] = 0;
|
||||
EXEC SQL SELECT coalesce(sum(net),0) INTO :h_sum FROM settlement
|
||||
WHERE merchant_id = :h_merch AND to_char(biz_date, 'YYYY-MM-DD') LIKE :h_like;
|
||||
if (sqlca.sqlcode < 0) FAIL(b);
|
||||
setl(b, T_GROSS, h_sum);
|
||||
OK(b);
|
||||
}
|
||||
|
||||
/* 17. ST_OFFSET - 상계: 음수 조정으로 채권 상계 기록 (dbio). */
|
||||
void ST_OFFSET(TPSVCINFO *p)
|
||||
{
|
||||
UBFH *b = (UBFH *)p->data;
|
||||
char bizdate[16];
|
||||
long sid = getl(b, T_SETTLE_ID), pid = getl(b, T_PURCHASE_ID);
|
||||
long amt = getl(b, T_AMOUNT), adj;
|
||||
gets_(b, T_BIZDATE, bizdate, sizeof(bizdate));
|
||||
adj = stdb_insert_adjust(sid, pid, -amt, "OFFSET", bizdate);
|
||||
if (adj < 0) FAIL(b);
|
||||
setl(b, T_ADJ, adj);
|
||||
OK(b);
|
||||
}
|
||||
|
||||
/* 18. ST_DIFFCHK - 정산 net vs 매입 net 차이 점검. */
|
||||
void ST_DIFFCHK(TPSVCINFO *p)
|
||||
{
|
||||
UBFH *b = (UBFH *)p->data;
|
||||
EXEC SQL BEGIN DECLARE SECTION;
|
||||
long h_pid, h_snet, h_pnet, h_diff;
|
||||
EXEC SQL END DECLARE SECTION;
|
||||
h_pid = getl(b, T_PURCHASE_ID);
|
||||
EXEC SQL SELECT coalesce(sum(net),0) INTO :h_snet FROM settlement WHERE purchase_id = :h_pid;
|
||||
if (sqlca.sqlcode < 0) FAIL(b);
|
||||
EXEC SQL SELECT net INTO :h_pnet FROM purchase WHERE purchase_id = :h_pid;
|
||||
if (sqlca.sqlcode < 0 || sqlca.sqlcode == 100) FAIL(b);
|
||||
h_diff = h_pnet - h_snet;
|
||||
setl(b, T_DELTA, h_diff);
|
||||
setl(b, T_RC, h_diff == 0 ? 0 : 1);
|
||||
OK(b);
|
||||
}
|
||||
|
||||
/* 19. ST_RECALC - 매입 수수료 기준 정산 net 재계산 + 갱신. */
|
||||
void ST_RECALC(TPSVCINFO *p)
|
||||
{
|
||||
UBFH *b = (UBFH *)p->data;
|
||||
EXEC SQL BEGIN DECLARE SECTION;
|
||||
long h_pid, h_amount, h_fee, h_net;
|
||||
EXEC SQL END DECLARE SECTION;
|
||||
h_pid = getl(b, T_PURCHASE_ID);
|
||||
EXEC SQL SELECT amount, fee INTO :h_amount, :h_fee FROM purchase WHERE purchase_id = :h_pid;
|
||||
if (sqlca.sqlcode < 0 || sqlca.sqlcode == 100) FAIL(b);
|
||||
h_net = h_amount - h_fee;
|
||||
EXEC SQL UPDATE settlement SET net = :h_net WHERE purchase_id = :h_pid;
|
||||
if (sqlca.sqlcode < 0) FAIL(b);
|
||||
setl(b, T_NET, h_net);
|
||||
OK(b);
|
||||
}
|
||||
|
||||
/* 20. ST_RATEAPPLY - 구간요율 적용 수수료 계산 + 정산 상세 기록 (dbio). */
|
||||
void ST_RATEAPPLY(TPSVCINFO *p)
|
||||
{
|
||||
UBFH *b = (UBFH *)p->data;
|
||||
char merch[64], bizdate[16], ftype[16];
|
||||
long sid = getl(b, T_SETTLE_ID), pid = getl(b, T_PURCHASE_ID);
|
||||
long amount = getl(b, T_AMOUNT), bps, fee, dtl;
|
||||
gets_(b, T_MERCHANT, merch, sizeof(merch));
|
||||
gets_(b, T_BIZDATE, bizdate, sizeof(bizdate));
|
||||
gets_(b, T_STR1, ftype, sizeof(ftype)); if (ftype[0] == 0) strcpy(ftype, "MDR");
|
||||
bps = stdb_lookup_rate(ftype, amount);
|
||||
if (bps < 0) FAIL(b);
|
||||
fee = acq_fee(amount, bps);
|
||||
dtl = stdb_insert_fee_dtl(sid, pid, merch, ftype, fee, bizdate);
|
||||
if (dtl < 0) FAIL(b);
|
||||
setl(b, T_FEE, fee);
|
||||
setl(b, T_RC, bps);
|
||||
OK(b);
|
||||
}
|
||||
|
||||
/* 21. ST_REPORT - 당일 정산 건수/합계 리포트. */
|
||||
void ST_REPORT(TPSVCINFO *p)
|
||||
{
|
||||
UBFH *b = (UBFH *)p->data;
|
||||
char bizdate[16];
|
||||
EXEC SQL BEGIN DECLARE SECTION;
|
||||
long h_cnt, h_sum;
|
||||
char h_bizdate[16];
|
||||
EXEC SQL END DECLARE SECTION;
|
||||
gets_(b, T_BIZDATE, bizdate, sizeof(bizdate));
|
||||
strncpy(h_bizdate, bizdate, sizeof(h_bizdate)-1); h_bizdate[sizeof(h_bizdate)-1] = 0;
|
||||
EXEC SQL SELECT count(*), coalesce(sum(net),0) INTO :h_cnt, :h_sum
|
||||
FROM settlement WHERE biz_date = :h_bizdate;
|
||||
if (sqlca.sqlcode < 0) FAIL(b);
|
||||
setl(b, T_COUNT, h_cnt);
|
||||
setl(b, T_GROSS, h_sum);
|
||||
OK(b);
|
||||
}
|
||||
|
||||
/* 22. ST_XFERLINK - 이체 연계 원장 기록. */
|
||||
void ST_XFERLINK(TPSVCINFO *p)
|
||||
{
|
||||
UBFH *b = (UBFH *)p->data;
|
||||
char bizdate[16];
|
||||
EXEC SQL BEGIN DECLARE SECTION;
|
||||
long h_pid, h_net;
|
||||
char h_bizdate[16];
|
||||
EXEC SQL END DECLARE SECTION;
|
||||
h_pid = getl(b, T_PURCHASE_ID); h_net = getl(b, T_NET);
|
||||
gets_(b, T_BIZDATE, bizdate, sizeof(bizdate));
|
||||
strncpy(h_bizdate, bizdate, sizeof(h_bizdate)-1); h_bizdate[sizeof(h_bizdate)-1] = 0;
|
||||
EXEC SQL INSERT INTO ledger (ledger_id, purchase_id, entry_type, amount, biz_date)
|
||||
VALUES (nextval('ledger_seq'), :h_pid, 'ST_XFER', :h_net, :h_bizdate);
|
||||
if (sqlca.sqlcode < 0) FAIL(b);
|
||||
OK(b);
|
||||
}
|
||||
|
||||
/* 23. ST_STATS - 정산 통계(건수/net 합계) 조회. */
|
||||
void ST_STATS(TPSVCINFO *p)
|
||||
{
|
||||
UBFH *b = (UBFH *)p->data;
|
||||
char bizdate[16];
|
||||
EXEC SQL BEGIN DECLARE SECTION;
|
||||
long h_cnt, h_sum, h_max;
|
||||
char h_bizdate[16];
|
||||
EXEC SQL END DECLARE SECTION;
|
||||
gets_(b, T_BIZDATE, bizdate, sizeof(bizdate));
|
||||
strncpy(h_bizdate, bizdate, sizeof(h_bizdate)-1); h_bizdate[sizeof(h_bizdate)-1] = 0;
|
||||
EXEC SQL SELECT count(*), coalesce(sum(net),0), coalesce(max(net),0)
|
||||
INTO :h_cnt, :h_sum, :h_max FROM settlement WHERE biz_date = :h_bizdate;
|
||||
if (sqlca.sqlcode < 0) FAIL(b);
|
||||
setl(b, T_COUNT, h_cnt);
|
||||
setl(b, T_GROSS, h_sum);
|
||||
setl(b, T_AMT1, h_max);
|
||||
OK(b);
|
||||
}
|
||||
|
||||
/* 24. ST_CANCEL - 정산 취소: 상태 CANCELLED + 취소 조정 (dbio). */
|
||||
void ST_CANCEL(TPSVCINFO *p)
|
||||
{
|
||||
UBFH *b = (UBFH *)p->data;
|
||||
char bizdate[16];
|
||||
long adj;
|
||||
EXEC SQL BEGIN DECLARE SECTION;
|
||||
long h_pid, h_net;
|
||||
EXEC SQL END DECLARE SECTION;
|
||||
h_pid = getl(b, T_PURCHASE_ID);
|
||||
gets_(b, T_BIZDATE, bizdate, sizeof(bizdate));
|
||||
EXEC SQL SELECT coalesce(sum(net),0) INTO :h_net FROM settlement WHERE purchase_id = :h_pid;
|
||||
if (sqlca.sqlcode < 0) FAIL(b);
|
||||
EXEC SQL UPDATE settlement SET status = 'CANCELLED' WHERE purchase_id = :h_pid;
|
||||
if (sqlca.sqlcode < 0) FAIL(b);
|
||||
adj = stdb_insert_adjust(0, h_pid, -h_net, "SETTLE_CANCEL", bizdate);
|
||||
if (adj < 0) FAIL(b);
|
||||
Bchg(b, T_STATUS, 0, "C", 0L);
|
||||
OK(b);
|
||||
}
|
||||
|
||||
/* 25. ST_LEDGERLINK - 정산 연계 원장 기록. */
|
||||
void ST_LEDGERLINK(TPSVCINFO *p)
|
||||
{
|
||||
UBFH *b = (UBFH *)p->data;
|
||||
char bizdate[16];
|
||||
EXEC SQL BEGIN DECLARE SECTION;
|
||||
long h_pid, h_net;
|
||||
char h_bizdate[16];
|
||||
EXEC SQL END DECLARE SECTION;
|
||||
h_pid = getl(b, T_PURCHASE_ID); h_net = getl(b, T_NET);
|
||||
gets_(b, T_BIZDATE, bizdate, sizeof(bizdate));
|
||||
strncpy(h_bizdate, bizdate, sizeof(h_bizdate)-1); h_bizdate[sizeof(h_bizdate)-1] = 0;
|
||||
EXEC SQL INSERT INTO ledger (ledger_id, purchase_id, entry_type, amount, biz_date)
|
||||
VALUES (nextval('ledger_seq'), :h_pid, 'SETTLELINK', :h_net, :h_bizdate);
|
||||
if (sqlca.sqlcode < 0) FAIL(b);
|
||||
OK(b);
|
||||
}
|
||||
|
||||
/* 26. ST_BANDRATE - 금액 구간 요율(bps) 조회 (dbio). */
|
||||
void ST_BANDRATE(TPSVCINFO *p)
|
||||
{
|
||||
UBFH *b = (UBFH *)p->data;
|
||||
char ftype[16];
|
||||
long amount = getl(b, T_AMOUNT), bps;
|
||||
gets_(b, T_STR1, ftype, sizeof(ftype)); if (ftype[0] == 0) strcpy(ftype, "MDR");
|
||||
bps = stdb_lookup_rate(ftype, amount);
|
||||
if (bps < 0) FAIL(b);
|
||||
setl(b, T_RC, bps);
|
||||
OK(b);
|
||||
}
|
||||
|
||||
/* 27. ST_CAP - 수수료 상한 적용: 상한 초과분을 감액 조정 (dbio). */
|
||||
void ST_CAP(TPSVCINFO *p)
|
||||
{
|
||||
UBFH *b = (UBFH *)p->data;
|
||||
char bizdate[16];
|
||||
long sid = getl(b, T_SETTLE_ID), pid = getl(b, T_PURCHASE_ID);
|
||||
long fee = getl(b, T_FEE), cap = getl(b, T_ARG1), over, adj;
|
||||
gets_(b, T_BIZDATE, bizdate, sizeof(bizdate));
|
||||
if (cap <= 0) cap = 100000; /* 기본 상한 */
|
||||
if (fee > cap) {
|
||||
over = fee - cap;
|
||||
adj = stdb_insert_adjust(sid, pid, -over, "FEE_CAP", bizdate);
|
||||
if (adj < 0) FAIL(b);
|
||||
setl(b, T_FEE, cap);
|
||||
setl(b, T_DELTA, -over);
|
||||
} else {
|
||||
setl(b, T_DELTA, 0);
|
||||
}
|
||||
OK(b);
|
||||
}
|
||||
|
||||
/* 28. ST_FXCONV - 외화 환산: fx * rate/100 = KRW. */
|
||||
void ST_FXCONV(TPSVCINFO *p)
|
||||
{
|
||||
UBFH *b = (UBFH *)p->data;
|
||||
long fx = getl(b, T_FX_AMT), rate = getl(b, T_ARG1), krw;
|
||||
if (rate <= 0) rate = 1350; /* 13.50 KRW/unit (bps of 100) */
|
||||
krw = fx * rate / 100;
|
||||
setl(b, T_AMOUNT, krw);
|
||||
OK(b);
|
||||
}
|
||||
|
||||
/* 29. ST_CONFIRM - 정산 확정 (SETTLED -> CONFIRMED). */
|
||||
void ST_CONFIRM(TPSVCINFO *p)
|
||||
{
|
||||
UBFH *b = (UBFH *)p->data;
|
||||
EXEC SQL BEGIN DECLARE SECTION;
|
||||
long h_pid;
|
||||
EXEC SQL END DECLARE SECTION;
|
||||
h_pid = getl(b, T_PURCHASE_ID);
|
||||
EXEC SQL UPDATE settlement SET status = 'CONFIRMED'
|
||||
WHERE purchase_id = :h_pid AND status = 'SETTLED';
|
||||
if (sqlca.sqlcode < 0) FAIL(b);
|
||||
Bchg(b, T_STATUS, 0, "C", 0L);
|
||||
OK(b);
|
||||
}
|
||||
|
||||
/* 30. ST_STATUS - 정산 상태 조회. */
|
||||
void ST_STATUS(TPSVCINFO *p)
|
||||
{
|
||||
UBFH *b = (UBFH *)p->data;
|
||||
EXEC SQL BEGIN DECLARE SECTION;
|
||||
long h_pid;
|
||||
char h_status[16];
|
||||
EXEC SQL END DECLARE SECTION;
|
||||
h_pid = getl(b, T_PURCHASE_ID);
|
||||
EXEC SQL SELECT status INTO :h_status FROM settlement
|
||||
WHERE purchase_id = :h_pid ORDER BY settlement_id DESC LIMIT 1;
|
||||
if (sqlca.sqlcode < 0) FAIL(b);
|
||||
if (sqlca.sqlcode == 100) strcpy(h_status, "NONE");
|
||||
Bchg(b, T_STATUS, 0, h_status, 0L);
|
||||
OK(b);
|
||||
}
|
||||
|
||||
/* vim: set ts=4 sw=4 et smartindent: */
|
||||
|
|
|
|||
62
app/src/vl/batch/vl_anomaly_batch.pgc
Normal file
62
app/src/vl/batch/vl_anomaly_batch.pgc
Normal file
|
|
@ -0,0 +1,62 @@
|
|||
/*
|
||||
* vl_anomaly_batch.pgc - 이상거래(고액/0원) 탐지 배치 (커서 + XA).
|
||||
*
|
||||
* Cursors over a biz_date's 매입 whose amount exceeds the AMT_MAX rule threshold
|
||||
* or is non-positive, and that is not already flagged; each is written to
|
||||
* vl_suspect (via dbio) under ONE global XA transaction.
|
||||
* Usage: vl_anomaly_batch [YYYY-MM-DD]
|
||||
*/
|
||||
#include <stdio.h>
|
||||
#include <stdlib.h>
|
||||
#include <string.h>
|
||||
#include <atmi.h>
|
||||
#include "vl_dbio.h"
|
||||
|
||||
int main(int argc, char **argv)
|
||||
{
|
||||
const char *bizdate = (argc > 1) ? argv[1] : "2026-07-19";
|
||||
long flagged = 0;
|
||||
char sev[8] = "ERROR";
|
||||
|
||||
EXEC SQL BEGIN DECLARE SECTION;
|
||||
char h_bizdate[16];
|
||||
long h_pid, h_amt, thr = 50000000;
|
||||
EXEC SQL END DECLARE SECTION;
|
||||
|
||||
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; }
|
||||
|
||||
/* 규칙 임계 로드 (dbio) - 없으면 기본값 유지 */
|
||||
if (vldb_get_rule("AMT_MAX", &thr, sev) < 0) { thr = 50000000; strcpy(sev, "ERROR"); }
|
||||
|
||||
EXEC SQL DECLARE vac CURSOR FOR
|
||||
SELECT p.purchase_id, p.amount FROM purchase p
|
||||
WHERE p.biz_date = :h_bizdate
|
||||
AND (p.amount > :thr OR p.amount <= 0)
|
||||
AND NOT EXISTS (SELECT 1 FROM vl_suspect s
|
||||
WHERE s.purchase_id = p.purchase_id AND s.rule_code = 'AMT_MAX')
|
||||
ORDER BY p.purchase_id;
|
||||
EXEC SQL OPEN vac;
|
||||
if (sqlca.sqlcode < 0) { fprintf(stderr, "OPEN FAIL [%d] %s\n", sqlca.sqlcode, sqlca.sqlerrm.sqlerrmc); tpabort(0); return 1; }
|
||||
|
||||
for (;;) {
|
||||
EXEC SQL FETCH vac INTO :h_pid, :h_amt;
|
||||
if (sqlca.sqlcode == 100) break;
|
||||
if (sqlca.sqlcode < 0) { fprintf(stderr, "FETCH FAIL [%d] %s\n", sqlca.sqlcode, sqlca.sqlerrm.sqlerrmc); EXEC SQL CLOSE vac; tpabort(0); return 1; }
|
||||
if (vldb_insert_suspect(h_pid, "AMT_MAX", sev,
|
||||
(h_amt <= 0) ? "0원/음수 금액" : "고액 이상거래", bizdate) < 0) {
|
||||
EXEC SQL CLOSE vac; tpabort(0); return 1;
|
||||
}
|
||||
flagged++;
|
||||
}
|
||||
EXEC SQL CLOSE vac;
|
||||
|
||||
if (tpcommit(0) < 0) { fprintf(stderr, "tpcommit FAIL: %s\n", tpstrerror(tperrno)); tpabort(0); return 1; }
|
||||
printf(">>> vl_anomaly_batch COMMIT: bizdate=%s 임계=%ld 적출=%ld\n", bizdate, thr, flagged);
|
||||
|
||||
tpclose(); tpterm();
|
||||
return 0;
|
||||
}
|
||||
64
app/src/vl/batch/vl_sales_batch.pgc
Normal file
64
app/src/vl/batch/vl_sales_batch.pgc
Normal file
|
|
@ -0,0 +1,64 @@
|
|||
/*
|
||||
* vl_sales_batch.pgc - 매입-정산 대사검증 배치 (커서 JOIN + XA).
|
||||
*
|
||||
* ATMI client: tpinit/tpopen open the ECPG XA RM, tpbegin starts ONE global
|
||||
* transaction, an EXEC SQL cursor JOINs 정산완료(status='S') 매입 against its
|
||||
* settlement rows for a biz_date; any purchase whose settled net != sum of its
|
||||
* settlement net (or with no settlement) is written to vl_suspect (via the
|
||||
* linked dbio lib), then tpcommit drives XA 2PC.
|
||||
* Usage: vl_sales_batch [YYYY-MM-DD]
|
||||
*/
|
||||
#include <stdio.h>
|
||||
#include <stdlib.h>
|
||||
#include <string.h>
|
||||
#include <atmi.h>
|
||||
#include "vl_dbio.h"
|
||||
|
||||
int main(int argc, char **argv)
|
||||
{
|
||||
const char *bizdate = (argc > 1) ? argv[1] : "2026-07-19";
|
||||
long scanned = 0, flagged = 0;
|
||||
|
||||
EXEC SQL BEGIN DECLARE SECTION;
|
||||
char h_bizdate[16];
|
||||
long h_pid, h_pnet, h_snet;
|
||||
EXEC SQL END DECLARE SECTION;
|
||||
|
||||
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; }
|
||||
|
||||
/* 매입(정산완료) LEFT JOIN 정산합계 - 불일치/누락 추출 */
|
||||
EXEC SQL DECLARE vsc CURSOR FOR
|
||||
SELECT p.purchase_id, p.net, coalesce(s.snet, 0)
|
||||
FROM purchase p
|
||||
LEFT JOIN (SELECT purchase_id, sum(net) AS snet FROM settlement
|
||||
GROUP BY purchase_id) s ON s.purchase_id = p.purchase_id
|
||||
WHERE p.biz_date = :h_bizdate AND p.status = 'S'
|
||||
ORDER BY p.purchase_id;
|
||||
EXEC SQL OPEN vsc;
|
||||
if (sqlca.sqlcode < 0) { fprintf(stderr, "OPEN FAIL [%d] %s\n", sqlca.sqlcode, sqlca.sqlerrm.sqlerrmc); tpabort(0); return 1; }
|
||||
|
||||
for (;;) {
|
||||
EXEC SQL FETCH vsc INTO :h_pid, :h_pnet, :h_snet;
|
||||
if (sqlca.sqlcode == 100) break;
|
||||
if (sqlca.sqlcode < 0) { fprintf(stderr, "FETCH FAIL [%d] %s\n", sqlca.sqlcode, sqlca.sqlerrm.sqlerrmc); EXEC SQL CLOSE vsc; tpabort(0); return 1; }
|
||||
scanned++;
|
||||
if (h_snet != h_pnet) {
|
||||
if (vldb_insert_suspect(h_pid, "LEDGER_MIS", "CRIT",
|
||||
"매입-정산 대사 불일치", bizdate) < 0) {
|
||||
EXEC SQL CLOSE vsc; tpabort(0); return 1;
|
||||
}
|
||||
flagged++;
|
||||
}
|
||||
}
|
||||
EXEC SQL CLOSE vsc;
|
||||
|
||||
if (tpcommit(0) < 0) { fprintf(stderr, "tpcommit FAIL: %s\n", tpstrerror(tperrno)); tpabort(0); return 1; }
|
||||
printf(">>> vl_sales_batch COMMIT: bizdate=%s 검사=%ld 적출=%ld\n", bizdate, scanned, flagged);
|
||||
|
||||
tpclose(); tpterm();
|
||||
return 0;
|
||||
}
|
||||
92
app/src/vl/dbio/vl_check_dbio.pgc
Normal file
92
app/src/vl/dbio/vl_check_dbio.pgc
Normal file
|
|
@ -0,0 +1,92 @@
|
|||
/*
|
||||
* vl_check_dbio.pgc - 의심거래 적재 + 검증 로그 기록 (libvldbio.a).
|
||||
* ECPG functions; run on the caller's XA branch. No EXEC SQL CONNECT.
|
||||
*/
|
||||
#include <string.h>
|
||||
#include <userlog.h>
|
||||
#include "vl_dbio.h"
|
||||
|
||||
long vldb_next_suspect_id(void)
|
||||
{
|
||||
EXEC SQL BEGIN DECLARE SECTION;
|
||||
long h_id;
|
||||
EXEC SQL END DECLARE SECTION;
|
||||
|
||||
EXEC SQL SELECT nextval('vl_suspect_seq') INTO :h_id;
|
||||
if (sqlca.sqlcode < 0) {
|
||||
userlog("vldb_next_suspect_id FAIL [%d] %s", sqlca.sqlcode, sqlca.sqlerrm.sqlerrmc);
|
||||
return -1;
|
||||
}
|
||||
return h_id;
|
||||
}
|
||||
|
||||
long vldb_insert_suspect(long purchase_id, const char *rule_code,
|
||||
const char *severity, const char *detail,
|
||||
const char *bizdate)
|
||||
{
|
||||
EXEC SQL BEGIN DECLARE SECTION;
|
||||
long h_id, h_pid = purchase_id;
|
||||
char h_rule[32], h_sev[8], h_detail[128], h_bizdate[16];
|
||||
EXEC SQL END DECLARE SECTION;
|
||||
|
||||
strncpy(h_rule, rule_code, sizeof(h_rule) - 1); h_rule[sizeof(h_rule)-1] = 0;
|
||||
strncpy(h_sev, severity, sizeof(h_sev) - 1); h_sev[sizeof(h_sev)-1] = 0;
|
||||
strncpy(h_detail, detail, sizeof(h_detail) - 1); h_detail[sizeof(h_detail)-1] = 0;
|
||||
strncpy(h_bizdate, bizdate, sizeof(h_bizdate) - 1);h_bizdate[sizeof(h_bizdate)-1]= 0;
|
||||
|
||||
EXEC SQL SELECT nextval('vl_suspect_seq') INTO :h_id;
|
||||
if (sqlca.sqlcode < 0) return -1;
|
||||
|
||||
EXEC SQL INSERT INTO vl_suspect
|
||||
(suspect_id, purchase_id, rule_code, severity, detail, biz_date)
|
||||
VALUES (:h_id, :h_pid, :h_rule, :h_sev, :h_detail, :h_bizdate);
|
||||
if (sqlca.sqlcode < 0) {
|
||||
userlog("vldb_insert_suspect FAIL [%d] %s", sqlca.sqlcode, sqlca.sqlerrm.sqlerrmc);
|
||||
return -1;
|
||||
}
|
||||
return h_id;
|
||||
}
|
||||
|
||||
long vldb_insert_check_log(const char *check_type, long target_id,
|
||||
long result_code, const char *detail,
|
||||
const char *bizdate)
|
||||
{
|
||||
EXEC SQL BEGIN DECLARE SECTION;
|
||||
long h_id, h_target = target_id, h_rc = result_code;
|
||||
char h_type[32], h_detail[128], h_bizdate[16];
|
||||
EXEC SQL END DECLARE SECTION;
|
||||
|
||||
strncpy(h_type, check_type, sizeof(h_type) - 1); h_type[sizeof(h_type)-1] = 0;
|
||||
strncpy(h_detail, detail, sizeof(h_detail) - 1);h_detail[sizeof(h_detail)-1] = 0;
|
||||
strncpy(h_bizdate, bizdate, sizeof(h_bizdate) - 1);h_bizdate[sizeof(h_bizdate)-1]= 0;
|
||||
|
||||
EXEC SQL SELECT nextval('vl_checklog_seq') INTO :h_id;
|
||||
if (sqlca.sqlcode < 0) return -1;
|
||||
|
||||
EXEC SQL INSERT INTO vl_check_log
|
||||
(log_id, check_type, target_id, result_code, detail, biz_date)
|
||||
VALUES (:h_id, :h_type, :h_target, :h_rc, :h_detail, :h_bizdate);
|
||||
if (sqlca.sqlcode < 0) {
|
||||
userlog("vldb_insert_check_log FAIL [%d] %s", sqlca.sqlcode, sqlca.sqlerrm.sqlerrmc);
|
||||
return -1;
|
||||
}
|
||||
return h_id;
|
||||
}
|
||||
|
||||
long vldb_suspect_count(const char *bizdate)
|
||||
{
|
||||
EXEC SQL BEGIN DECLARE SECTION;
|
||||
long h_cnt;
|
||||
char h_bizdate[16];
|
||||
EXEC SQL END DECLARE SECTION;
|
||||
|
||||
strncpy(h_bizdate, bizdate, sizeof(h_bizdate) - 1); h_bizdate[sizeof(h_bizdate)-1] = 0;
|
||||
|
||||
EXEC SQL SELECT count(*) INTO :h_cnt FROM vl_suspect
|
||||
WHERE biz_date = :h_bizdate AND status = 'OPEN';
|
||||
if (sqlca.sqlcode < 0) {
|
||||
userlog("vldb_suspect_count FAIL [%d] %s", sqlca.sqlcode, sqlca.sqlerrm.sqlerrmc);
|
||||
return -1;
|
||||
}
|
||||
return h_cnt;
|
||||
}
|
||||
27
app/src/vl/dbio/vl_dbio.h
Normal file
27
app/src/vl/dbio/vl_dbio.h
Normal file
|
|
@ -0,0 +1,27 @@
|
|||
/*
|
||||
* vl_dbio.h - vl 모듈 (정합성검증/validation) DB 접근 계층 (libvldbio.a).
|
||||
* ECPG (EXEC SQL) functions compiled into a static library and linked into
|
||||
* vl_svr and the vl batches. They run on the caller's XA branch/connection
|
||||
* (no EXEC SQL CONNECT); sqlca is the shared per-thread ECPG SQLCA.
|
||||
* Return convention: 0/>=0 = ok, -1 = SQL error (caller may also inspect sqlca).
|
||||
*/
|
||||
#ifndef VL_DBIO_H
|
||||
#define VL_DBIO_H
|
||||
|
||||
/* 의심거래/검증로그 (vl_check_dbio) */
|
||||
long vldb_next_suspect_id(void);
|
||||
long vldb_insert_suspect(long purchase_id, const char *rule_code,
|
||||
const char *severity, const char *detail,
|
||||
const char *bizdate);
|
||||
long vldb_insert_check_log(const char *check_type, long target_id,
|
||||
long result_code, const char *detail,
|
||||
const char *bizdate);
|
||||
long vldb_suspect_count(const char *bizdate);
|
||||
|
||||
/* 검증규칙 (vl_rule_dbio) */
|
||||
int vldb_get_rule(const char *rule_code, long *threshold_out,
|
||||
char *severity_out /* >= 8 */);
|
||||
int vldb_upsert_rule(const char *rule_code, const char *rule_name,
|
||||
long threshold, const char *severity);
|
||||
|
||||
#endif /* VL_DBIO_H */
|
||||
51
app/src/vl/dbio/vl_rule_dbio.pgc
Normal file
51
app/src/vl/dbio/vl_rule_dbio.pgc
Normal file
|
|
@ -0,0 +1,51 @@
|
|||
/*
|
||||
* vl_rule_dbio.pgc - 검증 규칙 조회 + 등록 (libvldbio.a).
|
||||
* ECPG functions; run on the caller's XA branch. No EXEC SQL CONNECT.
|
||||
*/
|
||||
#include <string.h>
|
||||
#include <userlog.h>
|
||||
#include "vl_dbio.h"
|
||||
|
||||
int vldb_get_rule(const char *rule_code, long *threshold_out, char *severity_out)
|
||||
{
|
||||
EXEC SQL BEGIN DECLARE SECTION;
|
||||
long h_thr;
|
||||
char h_rule[32], h_sev[8];
|
||||
EXEC SQL END DECLARE SECTION;
|
||||
|
||||
strncpy(h_rule, rule_code, sizeof(h_rule) - 1); h_rule[sizeof(h_rule)-1] = 0;
|
||||
|
||||
EXEC SQL SELECT threshold, severity INTO :h_thr, :h_sev
|
||||
FROM vl_rule WHERE rule_code = :h_rule AND status = 'ACTIVE';
|
||||
if (sqlca.sqlcode < 0 || sqlca.sqlcode == 100) {
|
||||
userlog("vldb_get_rule FAIL [%d] %s", sqlca.sqlcode, sqlca.sqlerrm.sqlerrmc);
|
||||
return -1;
|
||||
}
|
||||
*threshold_out = h_thr;
|
||||
strncpy(severity_out, h_sev, 7); severity_out[7] = 0;
|
||||
return 0;
|
||||
}
|
||||
|
||||
int vldb_upsert_rule(const char *rule_code, const char *rule_name,
|
||||
long threshold, const char *severity)
|
||||
{
|
||||
EXEC SQL BEGIN DECLARE SECTION;
|
||||
long h_thr = threshold;
|
||||
char h_rule[32], h_name[64], h_sev[8];
|
||||
EXEC SQL END DECLARE SECTION;
|
||||
|
||||
strncpy(h_rule, rule_code, sizeof(h_rule) - 1); h_rule[sizeof(h_rule)-1] = 0;
|
||||
strncpy(h_name, rule_name, sizeof(h_name) - 1); h_name[sizeof(h_name)-1] = 0;
|
||||
strncpy(h_sev, severity, sizeof(h_sev) - 1); h_sev[sizeof(h_sev)-1] = 0;
|
||||
|
||||
EXEC SQL INSERT INTO vl_rule (rule_code, rule_name, threshold, severity)
|
||||
VALUES (:h_rule, :h_name, :h_thr, :h_sev)
|
||||
ON CONFLICT (rule_code) DO UPDATE
|
||||
SET rule_name = EXCLUDED.rule_name, threshold = EXCLUDED.threshold,
|
||||
severity = EXCLUDED.severity, updated_at = now();
|
||||
if (sqlca.sqlcode < 0) {
|
||||
userlog("vldb_upsert_rule FAIL [%d] %s", sqlca.sqlcode, sqlca.sqlerrm.sqlerrmc);
|
||||
return -1;
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
674
app/src/vl/vl_svr.pgc
Normal file
674
app/src/vl/vl_svr.pgc
Normal file
|
|
@ -0,0 +1,674 @@
|
|||
/*
|
||||
* vl_svr.pgc - vl (정합성검증/validation) MODULE SERVER.
|
||||
*
|
||||
* ONE binary that tpadvertise()s ALL ~30 online vl services (the real Tuxedo
|
||||
* "one module server, many services" pattern). Every service is a distinct
|
||||
* 검증 operation: it SELECTs the target rows, applies a validation branch, and
|
||||
* returns an 에러코드 in T_RC (0=정상, >0=검증 실패). Genuine EXEC SQL directly
|
||||
* or via the linked libvldbio.a dbio layer, plus common math from libacqcommon.a.
|
||||
*
|
||||
* XA: the connection is opened once by tpopen() (ECPG XA switch); there is NO
|
||||
* EXEC SQL CONNECT. Each tpcall runs on this process's XA branch.
|
||||
*/
|
||||
#include <stdio.h>
|
||||
#include <stdlib.h>
|
||||
#include <string.h>
|
||||
#include <atmi.h>
|
||||
#include <ubf.h>
|
||||
#include <userlog.h>
|
||||
#include <ndebug.h>
|
||||
#include "acq.fd.h"
|
||||
#include "vl_dbio.h"
|
||||
#include "acq_common.h"
|
||||
|
||||
/* ----- small UBF helpers ------------------------------------------------- */
|
||||
static long getl(UBFH *b, BFLDID f) { long v = 0; Bget(b, f, 0, (char *)&v, 0L); return v; }
|
||||
static void gets_(UBFH *b, BFLDID f, char *out, int cap)
|
||||
{ BFLDLEN l = cap; out[0] = 0; Bget(b, f, 0, out, &l); }
|
||||
static void setl(UBFH *b, BFLDID f, long v) { Bchg(b, f, 0, (char *)&v, 0L); }
|
||||
|
||||
#define FAIL(b) do { tpreturn(TPFAIL, 0, (char *)(b), 0L, 0L); return; } while (0)
|
||||
#define OK(b) do { tpreturn(TPSUCCESS, 0, (char *)(b), 0L, 0L); return; } while (0)
|
||||
|
||||
/* ----- service prototypes ------------------------------------------------ */
|
||||
void VL_SALES(TPSVCINFO *p); void VL_CARD(TPSVCINFO *p);
|
||||
void VL_LIMIT(TPSVCINFO *p); void VL_AMOUNT(TPSVCINFO *p);
|
||||
void VL_ANOMALY(TPSVCINFO *p); void VL_REQUIRED(TPSVCINFO *p);
|
||||
void VL_CODEVAL(TPSVCINFO *p); void VL_APPR_ACQ(TPSVCINFO *p);
|
||||
void VL_DUP(TPSVCINFO *p); void VL_DATE(TPSVCINFO *p);
|
||||
void VL_FEE(TPSVCINFO *p); void VL_SETTLE(TPSVCINFO *p);
|
||||
void VL_LEDGER(TPSVCINFO *p); void VL_FORMAT(TPSVCINFO *p);
|
||||
void VL_REFINTEG(TPSVCINFO *p); void VL_DUPAUTH(TPSVCINFO *p);
|
||||
void VL_BIN(TPSVCINFO *p); void VL_EXPIRY(TPSVCINFO *p);
|
||||
void VL_TOLERANCE(TPSVCINFO *p); void VL_MERCH(TPSVCINFO *p);
|
||||
void VL_STATUS_CHK(TPSVCINFO *p); void VL_BALANCE(TPSVCINFO *p);
|
||||
void VL_SUMMARY(TPSVCINFO *p); void VL_REPORT(TPSVCINFO *p);
|
||||
void VL_BATCH_CHK(TPSVCINFO *p); void VL_THRESHOLD(TPSVCINFO *p);
|
||||
void VL_RULE(TPSVCINFO *p); void VL_CROSSCHK(TPSVCINFO *p);
|
||||
void VL_STATUS(TPSVCINFO *p); void VL_CONFIRM(TPSVCINFO *p);
|
||||
|
||||
/* ----- advertise table --------------------------------------------------- */
|
||||
static struct { const char *name; void (*fn)(TPSVCINFO *); } SVCS[] = {
|
||||
{"VL_SALES", VL_SALES}, {"VL_CARD", VL_CARD},
|
||||
{"VL_LIMIT", VL_LIMIT}, {"VL_AMOUNT", VL_AMOUNT},
|
||||
{"VL_ANOMALY", VL_ANOMALY}, {"VL_REQUIRED", VL_REQUIRED},
|
||||
{"VL_CODEVAL", VL_CODEVAL}, {"VL_APPR_ACQ", VL_APPR_ACQ},
|
||||
{"VL_DUP", VL_DUP}, {"VL_DATE", VL_DATE},
|
||||
{"VL_FEE", VL_FEE}, {"VL_SETTLE", VL_SETTLE},
|
||||
{"VL_LEDGER", VL_LEDGER}, {"VL_FORMAT", VL_FORMAT},
|
||||
{"VL_REFINTEG", VL_REFINTEG}, {"VL_DUPAUTH", VL_DUPAUTH},
|
||||
{"VL_BIN", VL_BIN}, {"VL_EXPIRY", VL_EXPIRY},
|
||||
{"VL_TOLERANCE", VL_TOLERANCE}, {"VL_MERCH", VL_MERCH},
|
||||
{"VL_STATUS_CHK", VL_STATUS_CHK},{"VL_BALANCE", VL_BALANCE},
|
||||
{"VL_SUMMARY", VL_SUMMARY}, {"VL_REPORT", VL_REPORT},
|
||||
{"VL_BATCH_CHK", VL_BATCH_CHK}, {"VL_THRESHOLD", VL_THRESHOLD},
|
||||
{"VL_RULE", VL_RULE}, {"VL_CROSSCHK", VL_CROSSCHK},
|
||||
{"VL_STATUS", VL_STATUS}, {"VL_CONFIRM", VL_CONFIRM},
|
||||
{NULL, NULL}
|
||||
};
|
||||
|
||||
int tpsvrinit(int argc, char **argv)
|
||||
{
|
||||
int i;
|
||||
if (tpopen() < 0) {
|
||||
userlog("vl_svr: tpopen 실패: %s", tpstrerror(tperrno));
|
||||
return -1;
|
||||
}
|
||||
for (i = 0; SVCS[i].name != NULL; i++) {
|
||||
if (tpadvertise((char *)SVCS[i].name, SVCS[i].fn) < 0) {
|
||||
userlog("vl_svr: tpadvertise(%s) 실패: %s", SVCS[i].name, tpstrerror(tperrno));
|
||||
return -1;
|
||||
}
|
||||
}
|
||||
userlog("vl_svr: vl 검증 서비스 %d개 광고 완료, RM open", i);
|
||||
return 0;
|
||||
}
|
||||
|
||||
void tpsvrdone(void)
|
||||
{
|
||||
tpclose();
|
||||
userlog("vl_svr: tpsvrdone");
|
||||
}
|
||||
|
||||
/* ========================================================================= */
|
||||
/* 1. VL_SALES - 매입 원거래 존재 검증 (RC 1=미존재). */
|
||||
/* ========================================================================= */
|
||||
void VL_SALES(TPSVCINFO *p)
|
||||
{
|
||||
UBFH *b = (UBFH *)p->data;
|
||||
char bizdate[16];
|
||||
long rc = 0;
|
||||
EXEC SQL BEGIN DECLARE SECTION;
|
||||
long h_pid, h_cnt;
|
||||
EXEC SQL END DECLARE SECTION;
|
||||
h_pid = getl(b, T_PURCHASE_ID);
|
||||
gets_(b, T_BIZDATE, bizdate, sizeof(bizdate));
|
||||
EXEC SQL SELECT count(*) INTO :h_cnt FROM purchase WHERE purchase_id = :h_pid;
|
||||
if (sqlca.sqlcode < 0) FAIL(b);
|
||||
if (h_cnt == 0) rc = 1;
|
||||
vldb_insert_check_log("VL_SALES", h_pid, rc, rc ? "매입 미존재" : "OK", bizdate);
|
||||
setl(b, T_RC, rc);
|
||||
OK(b);
|
||||
}
|
||||
|
||||
/* 2. VL_CARD - 카드번호 Luhn 검증 (RC 1=체크섬 불일치). */
|
||||
void VL_CARD(TPSVCINFO *p)
|
||||
{
|
||||
UBFH *b = (UBFH *)p->data;
|
||||
char pan[32];
|
||||
long rc;
|
||||
gets_(b, T_STR1, pan, sizeof(pan));
|
||||
if (pan[0] == 0) { setl(b, T_RC, 2); OK(b); } /* 2=번호 없음 */
|
||||
rc = acq_luhn_ok(pan) ? 0 : 1;
|
||||
setl(b, T_RC, rc);
|
||||
OK(b);
|
||||
}
|
||||
|
||||
/* 3. VL_LIMIT - 매입금액 vs 가맹점 일한도 검증 (RC 1=초과). */
|
||||
void VL_LIMIT(TPSVCINFO *p)
|
||||
{
|
||||
UBFH *b = (UBFH *)p->data;
|
||||
char merch[64];
|
||||
long rc = 0;
|
||||
EXEC SQL BEGIN DECLARE SECTION;
|
||||
long h_amt, h_lim;
|
||||
char h_merch[64];
|
||||
EXEC SQL END DECLARE SECTION;
|
||||
gets_(b, T_MERCHANT, merch, sizeof(merch));
|
||||
h_amt = getl(b, T_AMOUNT);
|
||||
strncpy(h_merch, merch, sizeof(h_merch)-1); h_merch[sizeof(h_merch)-1] = 0;
|
||||
EXEC SQL SELECT daily_limit INTO :h_lim FROM merchant WHERE merchant_id = :h_merch;
|
||||
if (sqlca.sqlcode < 0 || sqlca.sqlcode == 100) { setl(b, T_RC, 3); OK(b); } /* 3=가맹점 없음 */
|
||||
if (h_amt > h_lim) rc = 1;
|
||||
setl(b, T_RC, rc);
|
||||
OK(b);
|
||||
}
|
||||
|
||||
/* 4. VL_AMOUNT - 금액 유효성(0<amt<=규칙 AMT_MAX) 검증 (RC 1=0/음수,2=상한초과). */
|
||||
void VL_AMOUNT(TPSVCINFO *p)
|
||||
{
|
||||
UBFH *b = (UBFH *)p->data;
|
||||
long amt, rc = 0, thr = 50000000;
|
||||
char sev[8];
|
||||
amt = getl(b, T_AMOUNT);
|
||||
if (vldb_get_rule("AMT_MAX", &thr, sev) < 0) thr = 50000000;
|
||||
if (amt <= 0) rc = 1;
|
||||
else if (amt > thr) rc = 2;
|
||||
setl(b, T_RC, rc);
|
||||
setl(b, T_ARG1, thr);
|
||||
OK(b);
|
||||
}
|
||||
|
||||
/* 5. VL_ANOMALY - 이상거래 탐지 후 의심거래 적재 (dbio). */
|
||||
void VL_ANOMALY(TPSVCINFO *p)
|
||||
{
|
||||
UBFH *b = (UBFH *)p->data;
|
||||
char bizdate[16];
|
||||
long rc = 0, sid = 0, thr = 50000000;
|
||||
char sev[8];
|
||||
EXEC SQL BEGIN DECLARE SECTION;
|
||||
long h_pid, h_amt;
|
||||
EXEC SQL END DECLARE SECTION;
|
||||
h_pid = getl(b, T_PURCHASE_ID);
|
||||
gets_(b, T_BIZDATE, bizdate, sizeof(bizdate));
|
||||
EXEC SQL SELECT amount INTO :h_amt FROM purchase WHERE purchase_id = :h_pid;
|
||||
if (sqlca.sqlcode < 0 || sqlca.sqlcode == 100) { setl(b, T_RC, 3); OK(b); }
|
||||
if (vldb_get_rule("AMT_MAX", &thr, sev) < 0) { thr = 50000000; strcpy(sev, "ERROR"); }
|
||||
if (h_amt > thr) {
|
||||
rc = 1;
|
||||
sid = vldb_insert_suspect(h_pid, "AMT_MAX", sev, "고액 이상거래", bizdate);
|
||||
if (sid < 0) FAIL(b);
|
||||
}
|
||||
setl(b, T_RC, rc);
|
||||
setl(b, T_ID1, sid);
|
||||
OK(b);
|
||||
}
|
||||
|
||||
/* 6. VL_REQUIRED - 필수필드(가맹점/금액) 존재 검증 (RC 비트합). */
|
||||
void VL_REQUIRED(TPSVCINFO *p)
|
||||
{
|
||||
UBFH *b = (UBFH *)p->data;
|
||||
char merch[64];
|
||||
long amt, rc = 0;
|
||||
gets_(b, T_MERCHANT, merch, sizeof(merch));
|
||||
amt = getl(b, T_AMOUNT);
|
||||
if (merch[0] == 0) rc |= 1;
|
||||
if (amt == 0) rc |= 2;
|
||||
setl(b, T_RC, rc);
|
||||
OK(b);
|
||||
}
|
||||
|
||||
/* 7. VL_CODEVAL - 공통코드 유효성 검증 vs mm_code (RC 1=미존재). */
|
||||
void VL_CODEVAL(TPSVCINFO *p)
|
||||
{
|
||||
UBFH *b = (UBFH *)p->data;
|
||||
char grp[32], code[32];
|
||||
long rc = 0;
|
||||
EXEC SQL BEGIN DECLARE SECTION;
|
||||
long h_cnt;
|
||||
char h_grp[32], h_code[32];
|
||||
EXEC SQL END DECLARE SECTION;
|
||||
gets_(b, T_STR3, grp, sizeof(grp)); if (grp[0] == 0) strcpy(grp, "CARD_TYPE");
|
||||
gets_(b, T_KEY1, code, sizeof(code));
|
||||
strncpy(h_grp, grp, sizeof(h_grp)-1); h_grp[sizeof(h_grp)-1] = 0;
|
||||
strncpy(h_code, code, sizeof(h_code)-1); h_code[sizeof(h_code)-1] = 0;
|
||||
EXEC SQL SELECT count(*) INTO :h_cnt FROM mm_code
|
||||
WHERE code_group = :h_grp AND code = :h_code AND status = 'ACTIVE';
|
||||
if (sqlca.sqlcode < 0) FAIL(b);
|
||||
if (h_cnt == 0) rc = 1;
|
||||
setl(b, T_RC, rc);
|
||||
OK(b);
|
||||
}
|
||||
|
||||
/* 8. VL_APPR_ACQ - 승인-매입 매칭 검증 (RC 1=승인 없음). */
|
||||
void VL_APPR_ACQ(TPSVCINFO *p)
|
||||
{
|
||||
UBFH *b = (UBFH *)p->data;
|
||||
long rc = 0;
|
||||
EXEC SQL BEGIN DECLARE SECTION;
|
||||
long h_pid, h_cnt;
|
||||
EXEC SQL END DECLARE SECTION;
|
||||
h_pid = getl(b, T_PURCHASE_ID);
|
||||
EXEC SQL SELECT count(*) INTO :h_cnt FROM approval
|
||||
WHERE purchase_id = :h_pid AND approved = true;
|
||||
if (sqlca.sqlcode < 0) FAIL(b);
|
||||
if (h_cnt == 0) rc = 1;
|
||||
setl(b, T_COUNT, h_cnt);
|
||||
setl(b, T_RC, rc);
|
||||
OK(b);
|
||||
}
|
||||
|
||||
/* 9. VL_DUP - 중복 매입 검증 (동일 가맹점/금액/영업일 2건 이상 → RC 1). */
|
||||
void VL_DUP(TPSVCINFO *p)
|
||||
{
|
||||
UBFH *b = (UBFH *)p->data;
|
||||
char merch[64], bizdate[16];
|
||||
long rc = 0;
|
||||
EXEC SQL BEGIN DECLARE SECTION;
|
||||
long h_cnt, h_amt;
|
||||
char h_merch[64], h_bizdate[16];
|
||||
EXEC SQL END DECLARE SECTION;
|
||||
gets_(b, T_MERCHANT, merch, sizeof(merch));
|
||||
gets_(b, T_BIZDATE, bizdate, sizeof(bizdate));
|
||||
h_amt = getl(b, T_AMOUNT);
|
||||
strncpy(h_merch, merch, sizeof(h_merch)-1); h_merch[sizeof(h_merch)-1] = 0;
|
||||
strncpy(h_bizdate, bizdate, sizeof(h_bizdate)-1); h_bizdate[sizeof(h_bizdate)-1] = 0;
|
||||
EXEC SQL SELECT count(*) INTO :h_cnt FROM purchase
|
||||
WHERE merchant_id = :h_merch AND amount = :h_amt AND biz_date = :h_bizdate;
|
||||
if (sqlca.sqlcode < 0) FAIL(b);
|
||||
if (h_cnt > 1) rc = 1;
|
||||
setl(b, T_COUNT, h_cnt);
|
||||
setl(b, T_RC, rc);
|
||||
OK(b);
|
||||
}
|
||||
|
||||
/* 10. VL_DATE - 영업일 유효성(미래일자 금지) 검증 (RC 1=미래). */
|
||||
void VL_DATE(TPSVCINFO *p)
|
||||
{
|
||||
UBFH *b = (UBFH *)p->data;
|
||||
char bizdate[16];
|
||||
long rc = 0;
|
||||
EXEC SQL BEGIN DECLARE SECTION;
|
||||
long h_future;
|
||||
char h_bizdate[16];
|
||||
EXEC SQL END DECLARE SECTION;
|
||||
gets_(b, T_BIZDATE, bizdate, sizeof(bizdate));
|
||||
if (bizdate[0] == 0) { setl(b, T_RC, 2); OK(b); } /* 2=일자 없음 */
|
||||
strncpy(h_bizdate, bizdate, sizeof(h_bizdate)-1); h_bizdate[sizeof(h_bizdate)-1] = 0;
|
||||
EXEC SQL SELECT CASE WHEN :h_bizdate::date > current_date THEN 1 ELSE 0 END INTO :h_future;
|
||||
if (sqlca.sqlcode < 0) { setl(b, T_RC, 3); OK(b); } /* 3=형식 오류 */
|
||||
if (h_future) rc = 1;
|
||||
setl(b, T_RC, rc);
|
||||
OK(b);
|
||||
}
|
||||
|
||||
/* 11. VL_FEE - 수수료 재계산 정합 검증 (저장 fee vs MDR 재계산, RC 1=불일치). */
|
||||
void VL_FEE(TPSVCINFO *p)
|
||||
{
|
||||
UBFH *b = (UBFH *)p->data;
|
||||
long rc = 0, recompute;
|
||||
EXEC SQL BEGIN DECLARE SECTION;
|
||||
long h_pid, h_amount, h_fee, h_bps;
|
||||
char h_merch[64];
|
||||
EXEC SQL END DECLARE SECTION;
|
||||
h_pid = getl(b, T_PURCHASE_ID);
|
||||
EXEC SQL SELECT amount, fee, merchant_id INTO :h_amount, :h_fee, :h_merch
|
||||
FROM purchase WHERE purchase_id = :h_pid;
|
||||
if (sqlca.sqlcode < 0 || sqlca.sqlcode == 100) { setl(b, T_RC, 3); OK(b); }
|
||||
EXEC SQL SELECT mdr_bps INTO :h_bps FROM merchant WHERE merchant_id = :h_merch;
|
||||
if (sqlca.sqlcode < 0 || sqlca.sqlcode == 100) h_bps = 25;
|
||||
recompute = acq_fee(h_amount, h_bps);
|
||||
if (recompute != h_fee) rc = 1;
|
||||
setl(b, T_FEE, recompute);
|
||||
setl(b, T_DELTA, h_fee - recompute);
|
||||
setl(b, T_RC, rc);
|
||||
OK(b);
|
||||
}
|
||||
|
||||
/* 12. VL_SETTLE - 정산금액 정합 검증 (settlement.net vs purchase.net, RC 1=불일치). */
|
||||
void VL_SETTLE(TPSVCINFO *p)
|
||||
{
|
||||
UBFH *b = (UBFH *)p->data;
|
||||
long rc = 0;
|
||||
EXEC SQL BEGIN DECLARE SECTION;
|
||||
long h_pid, h_pnet, h_snet, h_scnt;
|
||||
EXEC SQL END DECLARE SECTION;
|
||||
h_pid = getl(b, T_PURCHASE_ID);
|
||||
EXEC SQL SELECT net INTO :h_pnet FROM purchase WHERE purchase_id = :h_pid;
|
||||
if (sqlca.sqlcode < 0 || sqlca.sqlcode == 100) { setl(b, T_RC, 3); OK(b); }
|
||||
EXEC SQL SELECT count(*), coalesce(sum(net),0) INTO :h_scnt, :h_snet
|
||||
FROM settlement WHERE purchase_id = :h_pid;
|
||||
if (sqlca.sqlcode < 0) FAIL(b);
|
||||
if (h_scnt == 0) rc = 2; /* 정산 미존재 */
|
||||
else if (h_snet != h_pnet) rc = 1; /* 금액 불일치 */
|
||||
setl(b, T_NET, h_pnet);
|
||||
setl(b, T_RC, rc);
|
||||
OK(b);
|
||||
}
|
||||
|
||||
/* 13. VL_LEDGER - 원장 합계 정합 검증 (sum(ledger) vs purchase.net, RC 1=불일치). */
|
||||
void VL_LEDGER(TPSVCINFO *p)
|
||||
{
|
||||
UBFH *b = (UBFH *)p->data;
|
||||
char bizdate[16];
|
||||
long rc = 0;
|
||||
EXEC SQL BEGIN DECLARE SECTION;
|
||||
long h_pid, h_pnet, h_lsum, h_lcnt;
|
||||
EXEC SQL END DECLARE SECTION;
|
||||
h_pid = getl(b, T_PURCHASE_ID);
|
||||
gets_(b, T_BIZDATE, bizdate, sizeof(bizdate));
|
||||
EXEC SQL SELECT net INTO :h_pnet FROM purchase WHERE purchase_id = :h_pid;
|
||||
if (sqlca.sqlcode < 0 || sqlca.sqlcode == 100) { setl(b, T_RC, 3); OK(b); }
|
||||
EXEC SQL SELECT count(*), coalesce(sum(amount),0) INTO :h_lcnt, :h_lsum
|
||||
FROM ledger WHERE purchase_id = :h_pid AND entry_type = 'SETTLELINK';
|
||||
if (sqlca.sqlcode < 0) FAIL(b);
|
||||
if (h_lcnt == 0) rc = 2; /* 원장 미기록 */
|
||||
else if (h_lsum != h_pnet) {
|
||||
rc = 1;
|
||||
vldb_insert_suspect(h_pid, "LEDGER_MIS", "CRIT", "원장 합계 불일치", bizdate);
|
||||
}
|
||||
setl(b, T_GROSS, h_lsum);
|
||||
setl(b, T_RC, rc);
|
||||
OK(b);
|
||||
}
|
||||
|
||||
/* 14. VL_FORMAT - 가맹점ID 형식(길이 4~16) 검증 (RC 1=형식 위반). */
|
||||
void VL_FORMAT(TPSVCINFO *p)
|
||||
{
|
||||
UBFH *b = (UBFH *)p->data;
|
||||
char merch[64];
|
||||
long rc = 0, len;
|
||||
gets_(b, T_MERCHANT, merch, sizeof(merch));
|
||||
len = (long)strlen(merch);
|
||||
if (len < 4 || len > 16) rc = 1;
|
||||
setl(b, T_COUNT, len);
|
||||
setl(b, T_RC, rc);
|
||||
OK(b);
|
||||
}
|
||||
|
||||
/* 15. VL_REFINTEG - 참조무결성(purchase.merchant_id ∈ merchant) 검증. */
|
||||
void VL_REFINTEG(TPSVCINFO *p)
|
||||
{
|
||||
UBFH *b = (UBFH *)p->data;
|
||||
long rc = 0;
|
||||
EXEC SQL BEGIN DECLARE SECTION;
|
||||
long h_pid, h_cnt;
|
||||
EXEC SQL END DECLARE SECTION;
|
||||
h_pid = getl(b, T_PURCHASE_ID);
|
||||
EXEC SQL SELECT count(*) INTO :h_cnt FROM purchase p
|
||||
WHERE p.purchase_id = :h_pid
|
||||
AND NOT EXISTS (SELECT 1 FROM merchant m WHERE m.merchant_id = p.merchant_id);
|
||||
if (sqlca.sqlcode < 0) FAIL(b);
|
||||
if (h_cnt > 0) rc = 1; /* 참조 깨짐 */
|
||||
setl(b, T_RC, rc);
|
||||
OK(b);
|
||||
}
|
||||
|
||||
/* 16. VL_DUPAUTH - 중복 승인 검증 (approval 2건 이상 → RC 1). */
|
||||
void VL_DUPAUTH(TPSVCINFO *p)
|
||||
{
|
||||
UBFH *b = (UBFH *)p->data;
|
||||
long rc = 0;
|
||||
EXEC SQL BEGIN DECLARE SECTION;
|
||||
long h_pid, h_cnt;
|
||||
EXEC SQL END DECLARE SECTION;
|
||||
h_pid = getl(b, T_PURCHASE_ID);
|
||||
EXEC SQL SELECT count(*) INTO :h_cnt FROM approval WHERE purchase_id = :h_pid;
|
||||
if (sqlca.sqlcode < 0) FAIL(b);
|
||||
if (h_cnt > 1) rc = 1;
|
||||
setl(b, T_COUNT, h_cnt);
|
||||
setl(b, T_RC, rc);
|
||||
OK(b);
|
||||
}
|
||||
|
||||
/* 17. VL_BIN - BIN 유효성 검증 vs mm_bin (RC 1=미등록,2=비활성). */
|
||||
void VL_BIN(TPSVCINFO *p)
|
||||
{
|
||||
UBFH *b = (UBFH *)p->data;
|
||||
char bin[16];
|
||||
long rc = 0;
|
||||
EXEC SQL BEGIN DECLARE SECTION;
|
||||
long h_cnt, h_active;
|
||||
char h_bin[16];
|
||||
EXEC SQL END DECLARE SECTION;
|
||||
gets_(b, T_KEY1, bin, sizeof(bin));
|
||||
strncpy(h_bin, bin, sizeof(h_bin)-1); h_bin[sizeof(h_bin)-1] = 0;
|
||||
EXEC SQL SELECT count(*), coalesce(sum(CASE WHEN status='ACTIVE' THEN 1 ELSE 0 END),0)
|
||||
INTO :h_cnt, :h_active FROM mm_bin WHERE bin = :h_bin;
|
||||
if (sqlca.sqlcode < 0) FAIL(b);
|
||||
if (h_cnt == 0) rc = 1;
|
||||
else if (h_active == 0) rc = 2;
|
||||
setl(b, T_RC, rc);
|
||||
OK(b);
|
||||
}
|
||||
|
||||
/* 18. VL_EXPIRY - 카드 유효기간(YYYYMM) vs 영업일 검증 (RC 1=만료). */
|
||||
void VL_EXPIRY(TPSVCINFO *p)
|
||||
{
|
||||
UBFH *b = (UBFH *)p->data;
|
||||
char expiry[8], bizdate[16];
|
||||
long rc = 0, exp_ym, biz_ym, y, m, d;
|
||||
gets_(b, T_STR2, expiry, sizeof(expiry));
|
||||
gets_(b, T_BIZDATE, bizdate, sizeof(bizdate));
|
||||
if (strlen(expiry) < 6 || sscanf(bizdate, "%ld-%ld-%ld", &y, &m, &d) != 3) {
|
||||
setl(b, T_RC, 2); OK(b); /* 2=형식 오류 */
|
||||
}
|
||||
exp_ym = atol(expiry); /* YYYYMM */
|
||||
biz_ym = y * 100 + m;
|
||||
if (exp_ym < biz_ym) rc = 1;
|
||||
setl(b, T_RC, rc);
|
||||
OK(b);
|
||||
}
|
||||
|
||||
/* 19. VL_TOLERANCE - 금액 허용오차(amount == fee+net ± tol) 검증 (RC 1=초과). */
|
||||
void VL_TOLERANCE(TPSVCINFO *p)
|
||||
{
|
||||
UBFH *b = (UBFH *)p->data;
|
||||
long rc = 0, tol, diff;
|
||||
EXEC SQL BEGIN DECLARE SECTION;
|
||||
long h_pid, h_amount, h_fee, h_net;
|
||||
EXEC SQL END DECLARE SECTION;
|
||||
h_pid = getl(b, T_PURCHASE_ID);
|
||||
tol = getl(b, T_ARG1); if (tol < 0) tol = 0;
|
||||
EXEC SQL SELECT amount, fee, net INTO :h_amount, :h_fee, :h_net
|
||||
FROM purchase WHERE purchase_id = :h_pid;
|
||||
if (sqlca.sqlcode < 0 || sqlca.sqlcode == 100) { setl(b, T_RC, 3); OK(b); }
|
||||
diff = h_amount - (h_fee + h_net);
|
||||
if (diff < 0) diff = -diff;
|
||||
if (diff > tol) rc = 1;
|
||||
setl(b, T_DELTA, diff);
|
||||
setl(b, T_RC, rc);
|
||||
OK(b);
|
||||
}
|
||||
|
||||
/* 20. VL_MERCH - 가맹점 상태 검증 (merchant.status != ACTIVE → RC 1). */
|
||||
void VL_MERCH(TPSVCINFO *p)
|
||||
{
|
||||
UBFH *b = (UBFH *)p->data;
|
||||
char merch[64];
|
||||
long rc = 0;
|
||||
EXEC SQL BEGIN DECLARE SECTION;
|
||||
char h_merch[64], h_status[16];
|
||||
EXEC SQL END DECLARE SECTION;
|
||||
gets_(b, T_MERCHANT, merch, sizeof(merch));
|
||||
strncpy(h_merch, merch, sizeof(h_merch)-1); h_merch[sizeof(h_merch)-1] = 0;
|
||||
EXEC SQL SELECT status INTO :h_status FROM merchant WHERE merchant_id = :h_merch;
|
||||
if (sqlca.sqlcode < 0 || sqlca.sqlcode == 100) { setl(b, T_RC, 2); OK(b); }
|
||||
if (strcmp(h_status, "ACTIVE") != 0) rc = 1;
|
||||
Bchg(b, T_STATUS, 0, h_status, 0L);
|
||||
setl(b, T_RC, rc);
|
||||
OK(b);
|
||||
}
|
||||
|
||||
/* 21. VL_STATUS_CHK - 매입 상태 유효값 검증 (허용집합 밖 → RC 1). */
|
||||
void VL_STATUS_CHK(TPSVCINFO *p)
|
||||
{
|
||||
UBFH *b = (UBFH *)p->data;
|
||||
long rc = 0;
|
||||
EXEC SQL BEGIN DECLARE SECTION;
|
||||
long h_pid, h_cnt;
|
||||
char h_status[8];
|
||||
EXEC SQL END DECLARE SECTION;
|
||||
h_pid = getl(b, T_PURCHASE_ID);
|
||||
EXEC SQL SELECT status INTO :h_status FROM purchase WHERE purchase_id = :h_pid;
|
||||
if (sqlca.sqlcode < 0 || sqlca.sqlcode == 100) { setl(b, T_RC, 3); OK(b); }
|
||||
EXEC SQL SELECT count(*) INTO :h_cnt FROM (SELECT :h_status AS s) t
|
||||
WHERE t.s IN ('A','M','S','C','H','V');
|
||||
if (sqlca.sqlcode < 0) FAIL(b);
|
||||
if (h_cnt == 0) rc = 1; /* 허용되지 않은 상태값 */
|
||||
Bchg(b, T_STATUS, 0, h_status, 0L);
|
||||
setl(b, T_RC, rc);
|
||||
OK(b);
|
||||
}
|
||||
|
||||
/* 22. VL_BALANCE - 가맹점 순액 대차 검증 (sum(purchase.net) vs sum(settlement.net)). */
|
||||
void VL_BALANCE(TPSVCINFO *p)
|
||||
{
|
||||
UBFH *b = (UBFH *)p->data;
|
||||
char merch[64], bizdate[16];
|
||||
long rc = 0;
|
||||
EXEC SQL BEGIN DECLARE SECTION;
|
||||
long h_pnet, h_snet;
|
||||
char h_merch[64], h_bizdate[16];
|
||||
EXEC SQL END DECLARE SECTION;
|
||||
gets_(b, T_MERCHANT, merch, sizeof(merch));
|
||||
gets_(b, T_BIZDATE, bizdate, sizeof(bizdate));
|
||||
strncpy(h_merch, merch, sizeof(h_merch)-1); h_merch[sizeof(h_merch)-1] = 0;
|
||||
strncpy(h_bizdate, bizdate, sizeof(h_bizdate)-1); h_bizdate[sizeof(h_bizdate)-1] = 0;
|
||||
EXEC SQL SELECT coalesce(sum(net),0) INTO :h_pnet FROM purchase
|
||||
WHERE merchant_id = :h_merch AND biz_date = :h_bizdate AND status = 'S';
|
||||
if (sqlca.sqlcode < 0) FAIL(b);
|
||||
EXEC SQL SELECT coalesce(sum(net),0) INTO :h_snet FROM settlement
|
||||
WHERE merchant_id = :h_merch AND biz_date = :h_bizdate;
|
||||
if (sqlca.sqlcode < 0) FAIL(b);
|
||||
if (h_pnet != h_snet) rc = 1;
|
||||
setl(b, T_GROSS, h_pnet);
|
||||
setl(b, T_NET, h_snet);
|
||||
setl(b, T_DELTA, h_pnet - h_snet);
|
||||
setl(b, T_RC, rc);
|
||||
OK(b);
|
||||
}
|
||||
|
||||
/* 23. VL_SUMMARY - 당일 미결(OPEN) 의심거래 요약 (dbio). */
|
||||
void VL_SUMMARY(TPSVCINFO *p)
|
||||
{
|
||||
UBFH *b = (UBFH *)p->data;
|
||||
char bizdate[16];
|
||||
long cnt;
|
||||
gets_(b, T_BIZDATE, bizdate, sizeof(bizdate));
|
||||
cnt = vldb_suspect_count(bizdate);
|
||||
if (cnt < 0) FAIL(b);
|
||||
setl(b, T_COUNT, cnt);
|
||||
setl(b, T_RC, cnt > 0 ? 1 : 0);
|
||||
OK(b);
|
||||
}
|
||||
|
||||
/* 24. VL_REPORT - 심각도별 의심거래 건수 리포트. */
|
||||
void VL_REPORT(TPSVCINFO *p)
|
||||
{
|
||||
UBFH *b = (UBFH *)p->data;
|
||||
char sev[8], bizdate[16];
|
||||
EXEC SQL BEGIN DECLARE SECTION;
|
||||
long h_cnt;
|
||||
char h_sev[8], h_bizdate[16];
|
||||
EXEC SQL END DECLARE SECTION;
|
||||
gets_(b, T_STR4, sev, sizeof(sev)); if (sev[0] == 0) strcpy(sev, "CRIT");
|
||||
gets_(b, T_BIZDATE, bizdate, sizeof(bizdate));
|
||||
strncpy(h_sev, sev, sizeof(h_sev)-1); h_sev[sizeof(h_sev)-1] = 0;
|
||||
strncpy(h_bizdate, bizdate, sizeof(h_bizdate)-1); h_bizdate[sizeof(h_bizdate)-1] = 0;
|
||||
EXEC SQL SELECT count(*) INTO :h_cnt FROM vl_suspect
|
||||
WHERE severity = :h_sev AND biz_date = :h_bizdate;
|
||||
if (sqlca.sqlcode < 0) FAIL(b);
|
||||
setl(b, T_COUNT, h_cnt);
|
||||
OK(b);
|
||||
}
|
||||
|
||||
/* 25. VL_BATCH_CHK - 배치 정합(purchase 건수 vs merchant_summary.txn_count). */
|
||||
void VL_BATCH_CHK(TPSVCINFO *p)
|
||||
{
|
||||
UBFH *b = (UBFH *)p->data;
|
||||
char bizdate[16];
|
||||
long rc = 0;
|
||||
EXEC SQL BEGIN DECLARE SECTION;
|
||||
long h_pcnt, h_scnt;
|
||||
char h_bizdate[16];
|
||||
EXEC SQL END DECLARE SECTION;
|
||||
gets_(b, T_BIZDATE, bizdate, sizeof(bizdate));
|
||||
strncpy(h_bizdate, bizdate, sizeof(h_bizdate)-1); h_bizdate[sizeof(h_bizdate)-1] = 0;
|
||||
EXEC SQL SELECT count(*) INTO :h_pcnt FROM purchase WHERE biz_date = :h_bizdate;
|
||||
if (sqlca.sqlcode < 0) FAIL(b);
|
||||
EXEC SQL SELECT coalesce(sum(txn_count),0) INTO :h_scnt FROM merchant_summary
|
||||
WHERE biz_date = :h_bizdate;
|
||||
if (sqlca.sqlcode < 0) FAIL(b);
|
||||
if (h_pcnt != h_scnt) rc = 1; /* 집계 누락/불일치 */
|
||||
setl(b, T_COUNT, h_pcnt);
|
||||
setl(b, T_GROSS, h_scnt);
|
||||
setl(b, T_RC, rc);
|
||||
OK(b);
|
||||
}
|
||||
|
||||
/* 26. VL_THRESHOLD - 규칙 임계치 검사 (dbio get_rule; amount>threshold → RC 1). */
|
||||
void VL_THRESHOLD(TPSVCINFO *p)
|
||||
{
|
||||
UBFH *b = (UBFH *)p->data;
|
||||
char rule[32], sev[8];
|
||||
long amt, thr = 0, rc = 0;
|
||||
gets_(b, T_STR3, rule, sizeof(rule)); if (rule[0] == 0) strcpy(rule, "AMT_MAX");
|
||||
amt = getl(b, T_AMOUNT);
|
||||
if (vldb_get_rule(rule, &thr, sev) < 0) { setl(b, T_RC, 3); OK(b); } /* 3=규칙 없음 */
|
||||
if (thr > 0 && amt > thr) rc = 1;
|
||||
setl(b, T_ARG1, thr);
|
||||
Bchg(b, T_STR4, 0, sev, 0L);
|
||||
setl(b, T_RC, rc);
|
||||
OK(b);
|
||||
}
|
||||
|
||||
/* 27. VL_RULE - 검증 규칙 등록/갱신 (dbio upsert). */
|
||||
void VL_RULE(TPSVCINFO *p)
|
||||
{
|
||||
UBFH *b = (UBFH *)p->data;
|
||||
char rule[32], name[64], sev[8];
|
||||
long thr;
|
||||
gets_(b, T_STR3, rule, sizeof(rule));
|
||||
if (rule[0] == 0) { userlog("VL_RULE: T_STR3(rule_code) 누락"); FAIL(b); }
|
||||
gets_(b, T_STR1, name, sizeof(name)); if (name[0] == 0) strcpy(name, "사용자 규칙");
|
||||
gets_(b, T_STR4, sev, sizeof(sev)); if (sev[0] == 0) strcpy(sev, "WARN");
|
||||
thr = getl(b, T_ARG1);
|
||||
if (vldb_upsert_rule(rule, name, thr, sev) < 0) FAIL(b);
|
||||
OK(b);
|
||||
}
|
||||
|
||||
/* 28. VL_CROSSCHK - 교차검증(매입+가맹점+승인 동시 존재, RC 비트합). */
|
||||
void VL_CROSSCHK(TPSVCINFO *p)
|
||||
{
|
||||
UBFH *b = (UBFH *)p->data;
|
||||
long rc = 0;
|
||||
EXEC SQL BEGIN DECLARE SECTION;
|
||||
long h_pid, h_pcnt, h_mcnt, h_acnt;
|
||||
EXEC SQL END DECLARE SECTION;
|
||||
h_pid = getl(b, T_PURCHASE_ID);
|
||||
EXEC SQL SELECT count(*) INTO :h_pcnt FROM purchase WHERE purchase_id = :h_pid;
|
||||
if (sqlca.sqlcode < 0) FAIL(b);
|
||||
EXEC SQL SELECT count(*) INTO :h_mcnt FROM purchase p
|
||||
JOIN merchant m ON m.merchant_id = p.merchant_id WHERE p.purchase_id = :h_pid;
|
||||
if (sqlca.sqlcode < 0) FAIL(b);
|
||||
EXEC SQL SELECT count(*) INTO :h_acnt FROM approval WHERE purchase_id = :h_pid;
|
||||
if (sqlca.sqlcode < 0) FAIL(b);
|
||||
if (h_pcnt == 0) rc |= 1; /* 매입 없음 */
|
||||
if (h_mcnt == 0) rc |= 2; /* 가맹점 참조 깨짐 */
|
||||
if (h_acnt == 0) rc |= 4; /* 승인 없음 */
|
||||
setl(b, T_RC, rc);
|
||||
OK(b);
|
||||
}
|
||||
|
||||
/* 29. VL_STATUS - 서비스 상태 (전체 OPEN 의심거래 수, RC=0). */
|
||||
void VL_STATUS(TPSVCINFO *p)
|
||||
{
|
||||
UBFH *b = (UBFH *)p->data;
|
||||
EXEC SQL BEGIN DECLARE SECTION;
|
||||
long h_cnt;
|
||||
EXEC SQL END DECLARE SECTION;
|
||||
EXEC SQL SELECT count(*) INTO :h_cnt FROM vl_suspect WHERE status = 'OPEN';
|
||||
if (sqlca.sqlcode < 0) FAIL(b);
|
||||
setl(b, T_COUNT, h_cnt);
|
||||
setl(b, T_RC, 0);
|
||||
Bchg(b, T_MSG, 0, "vl 검증 서비스 정상", 0L);
|
||||
OK(b);
|
||||
}
|
||||
|
||||
/* 30. VL_CONFIRM - 의심거래 종결 확정 (status CLOSED by suspect_id). */
|
||||
void VL_CONFIRM(TPSVCINFO *p)
|
||||
{
|
||||
UBFH *b = (UBFH *)p->data;
|
||||
EXEC SQL BEGIN DECLARE SECTION;
|
||||
long h_sid;
|
||||
EXEC SQL END DECLARE SECTION;
|
||||
h_sid = getl(b, T_ID1);
|
||||
EXEC SQL UPDATE vl_suspect SET status = 'CLOSED'
|
||||
WHERE suspect_id = :h_sid AND status = 'OPEN';
|
||||
if (sqlca.sqlcode < 0 || sqlca.sqlcode == 100) { setl(b, T_RC, 1); OK(b); }
|
||||
setl(b, T_RC, 0);
|
||||
OK(b);
|
||||
}
|
||||
|
||||
/* vim: set ts=4 sw=4 et smartindent: */
|
||||
111
db/schema.d/20-au.sql
Normal file
111
db/schema.d/20-au.sql
Normal file
|
|
@ -0,0 +1,111 @@
|
|||
-- au 모듈 (승인/한도) 스키마 - 승인 원장 / 카드 한도 / 부정거래 / 예비승인 / 집계
|
||||
-- Loaded into PostgreSQL on first boot (docker-entrypoint-initdb.d). Prefix 20.
|
||||
|
||||
-- ---------------------------------------------------------------------------
|
||||
-- 승인 원장 (authorization)
|
||||
-- status: A=승인, D=거절, C=취소, R=역전, H=보류, M=매입확정(capture),
|
||||
-- S=정산반영, V=무효(void)
|
||||
-- ---------------------------------------------------------------------------
|
||||
CREATE SEQUENCE IF NOT EXISTS au_auth_seq;
|
||||
CREATE TABLE IF NOT EXISTS au_authorization (
|
||||
auth_id BIGINT PRIMARY KEY,
|
||||
card_no TEXT NOT NULL,
|
||||
merchant_id TEXT NOT NULL DEFAULT '',
|
||||
amount BIGINT NOT NULL,
|
||||
approval_code TEXT NOT NULL DEFAULT '',
|
||||
auth_type TEXT NOT NULL DEFAULT 'AUTH', -- AUTH/STANDIN/INSTALL/PREAUTH
|
||||
status TEXT NOT NULL,
|
||||
decline_reason TEXT NOT NULL DEFAULT '',
|
||||
biz_date DATE NOT NULL,
|
||||
captured_at TIMESTAMPTZ,
|
||||
created_at TIMESTAMPTZ NOT NULL DEFAULT now(),
|
||||
updated_at TIMESTAMPTZ NOT NULL DEFAULT now()
|
||||
);
|
||||
|
||||
-- ---------------------------------------------------------------------------
|
||||
-- 카드 한도 (card_limit)
|
||||
-- ---------------------------------------------------------------------------
|
||||
CREATE TABLE IF NOT EXISTS card_limit (
|
||||
card_no TEXT PRIMARY KEY,
|
||||
daily_limit BIGINT NOT NULL DEFAULT 1000000,
|
||||
used_amount BIGINT NOT NULL DEFAULT 0,
|
||||
temp_limit BIGINT NOT NULL DEFAULT 0,
|
||||
status TEXT NOT NULL DEFAULT 'ACTIVE',
|
||||
updated_at TIMESTAMPTZ NOT NULL DEFAULT now()
|
||||
);
|
||||
|
||||
-- ---------------------------------------------------------------------------
|
||||
-- 부정거래 로그 (fraud_log)
|
||||
-- ---------------------------------------------------------------------------
|
||||
CREATE SEQUENCE IF NOT EXISTS au_fraud_seq;
|
||||
CREATE TABLE IF NOT EXISTS au_fraud_log (
|
||||
fraud_id BIGINT PRIMARY KEY,
|
||||
auth_id BIGINT NOT NULL DEFAULT 0,
|
||||
card_no TEXT NOT NULL,
|
||||
score BIGINT NOT NULL,
|
||||
reason TEXT NOT NULL DEFAULT '',
|
||||
biz_date DATE NOT NULL,
|
||||
created_at TIMESTAMPTZ NOT NULL DEFAULT now()
|
||||
);
|
||||
|
||||
-- ---------------------------------------------------------------------------
|
||||
-- 블랙리스트 (au_blacklist)
|
||||
-- ---------------------------------------------------------------------------
|
||||
CREATE TABLE IF NOT EXISTS au_blacklist (
|
||||
card_no TEXT PRIMARY KEY,
|
||||
reason TEXT NOT NULL DEFAULT '',
|
||||
created_at TIMESTAMPTZ NOT NULL DEFAULT now()
|
||||
);
|
||||
|
||||
-- ---------------------------------------------------------------------------
|
||||
-- 예비승인 (au_preauth) - status: P=예비, C=확정, X=취소
|
||||
-- ---------------------------------------------------------------------------
|
||||
CREATE SEQUENCE IF NOT EXISTS au_preauth_seq;
|
||||
CREATE TABLE IF NOT EXISTS au_preauth (
|
||||
preauth_id BIGINT PRIMARY KEY,
|
||||
card_no TEXT NOT NULL,
|
||||
merchant_id TEXT NOT NULL DEFAULT '',
|
||||
amount BIGINT NOT NULL,
|
||||
status TEXT NOT NULL DEFAULT 'P',
|
||||
biz_date DATE NOT NULL,
|
||||
created_at TIMESTAMPTZ NOT NULL DEFAULT now()
|
||||
);
|
||||
|
||||
-- ---------------------------------------------------------------------------
|
||||
-- 승인 취소 (au_cancel) - FULL / PARTIAL / VOID
|
||||
-- ---------------------------------------------------------------------------
|
||||
CREATE SEQUENCE IF NOT EXISTS au_cancel_seq;
|
||||
CREATE TABLE IF NOT EXISTS au_cancel (
|
||||
cancel_id BIGINT PRIMARY KEY,
|
||||
auth_id BIGINT NOT NULL,
|
||||
cancel_type TEXT NOT NULL,
|
||||
amount BIGINT NOT NULL,
|
||||
reason TEXT NOT NULL DEFAULT '',
|
||||
biz_date DATE NOT NULL,
|
||||
created_at TIMESTAMPTZ NOT NULL DEFAULT now()
|
||||
);
|
||||
|
||||
-- ---------------------------------------------------------------------------
|
||||
-- 가맹점 일별 승인 집계 (au_summary) - 배치/서비스가 채움
|
||||
-- ---------------------------------------------------------------------------
|
||||
CREATE TABLE IF NOT EXISTS au_summary (
|
||||
merchant_id TEXT NOT NULL,
|
||||
biz_date DATE NOT NULL,
|
||||
auth_count BIGINT NOT NULL DEFAULT 0,
|
||||
approve_count BIGINT NOT NULL DEFAULT 0,
|
||||
decline_count BIGINT NOT NULL DEFAULT 0,
|
||||
auth_amount BIGINT NOT NULL DEFAULT 0,
|
||||
updated_at TIMESTAMPTZ NOT NULL DEFAULT now(),
|
||||
PRIMARY KEY (merchant_id, biz_date)
|
||||
);
|
||||
|
||||
-- Seed card limits + a blacklist entry so AUTH/LIMIT services have counterparties.
|
||||
INSERT INTO card_limit (card_no, daily_limit, used_amount) VALUES
|
||||
('4000123412341234', 1000000, 0),
|
||||
('5100123412341234', 2000000, 0),
|
||||
('3400123412341234', 5000000, 0)
|
||||
ON CONFLICT (card_no) DO NOTHING;
|
||||
|
||||
INSERT INTO au_blacklist (card_no, reason) VALUES
|
||||
('4000999999999999', 'REPORTED_STOLEN')
|
||||
ON CONFLICT (card_no) DO NOTHING;
|
||||
110
db/schema.d/25-py.sql
Normal file
110
db/schema.d/25-py.sql
Normal file
|
|
@ -0,0 +1,110 @@
|
|||
-- py 모듈 (지급) 스키마 - 지급 원장 / 지급계좌 / 지급파일 / 예약 / 조정 / 집계
|
||||
-- Loaded into PostgreSQL on first boot (docker-entrypoint-initdb.d). Prefix 25.
|
||||
|
||||
-- ---------------------------------------------------------------------------
|
||||
-- 지급 원장 (payout)
|
||||
-- status: R=요청, H=보류, A=승인, J=반려, F=파일편성, P=지급완료,
|
||||
-- C=취소, V=역분개, X=마감
|
||||
-- ---------------------------------------------------------------------------
|
||||
CREATE SEQUENCE IF NOT EXISTS py_payout_seq;
|
||||
CREATE TABLE IF NOT EXISTS py_payout (
|
||||
payout_id BIGINT PRIMARY KEY,
|
||||
merchant_id TEXT NOT NULL DEFAULT '',
|
||||
account_no TEXT NOT NULL DEFAULT '',
|
||||
amount BIGINT NOT NULL,
|
||||
fee BIGINT NOT NULL DEFAULT 0,
|
||||
net BIGINT NOT NULL,
|
||||
status TEXT NOT NULL,
|
||||
channel TEXT NOT NULL DEFAULT 'STD', -- STD/SPLIT/BULK/ADVANCE
|
||||
file_id BIGINT NOT NULL DEFAULT 0,
|
||||
retry_count INTEGER NOT NULL DEFAULT 0,
|
||||
reject_reason TEXT NOT NULL DEFAULT '',
|
||||
biz_date DATE NOT NULL,
|
||||
created_at TIMESTAMPTZ NOT NULL DEFAULT now(),
|
||||
updated_at TIMESTAMPTZ NOT NULL DEFAULT now()
|
||||
);
|
||||
|
||||
-- ---------------------------------------------------------------------------
|
||||
-- 지급계좌 (py_account)
|
||||
-- ---------------------------------------------------------------------------
|
||||
CREATE TABLE IF NOT EXISTS py_account (
|
||||
account_no TEXT PRIMARY KEY,
|
||||
merchant_id TEXT NOT NULL DEFAULT '',
|
||||
bank_code TEXT NOT NULL DEFAULT '',
|
||||
holder TEXT NOT NULL DEFAULT '',
|
||||
balance BIGINT NOT NULL DEFAULT 0,
|
||||
status TEXT NOT NULL DEFAULT 'ACTIVE',
|
||||
updated_at TIMESTAMPTZ NOT NULL DEFAULT now()
|
||||
);
|
||||
|
||||
-- ---------------------------------------------------------------------------
|
||||
-- 지급 파일 (py_file) - status: G=생성, C=확정
|
||||
-- ---------------------------------------------------------------------------
|
||||
CREATE SEQUENCE IF NOT EXISTS py_file_seq;
|
||||
CREATE TABLE IF NOT EXISTS py_file (
|
||||
file_id BIGINT PRIMARY KEY,
|
||||
biz_date DATE NOT NULL,
|
||||
file_name TEXT NOT NULL,
|
||||
record_count BIGINT NOT NULL DEFAULT 0,
|
||||
total_amount BIGINT NOT NULL DEFAULT 0,
|
||||
status TEXT NOT NULL DEFAULT 'G',
|
||||
created_at TIMESTAMPTZ NOT NULL DEFAULT now(),
|
||||
confirmed_at TIMESTAMPTZ
|
||||
);
|
||||
|
||||
-- ---------------------------------------------------------------------------
|
||||
-- 지급 예약 (py_schedule) - status: S=예약, D=실행
|
||||
-- ---------------------------------------------------------------------------
|
||||
CREATE SEQUENCE IF NOT EXISTS py_schedule_seq;
|
||||
CREATE TABLE IF NOT EXISTS py_schedule (
|
||||
schedule_id BIGINT PRIMARY KEY,
|
||||
payout_id BIGINT NOT NULL,
|
||||
run_date DATE NOT NULL,
|
||||
status TEXT NOT NULL DEFAULT 'S',
|
||||
created_at TIMESTAMPTZ NOT NULL DEFAULT now()
|
||||
);
|
||||
|
||||
-- ---------------------------------------------------------------------------
|
||||
-- 지급 조정 (py_adjust)
|
||||
-- ---------------------------------------------------------------------------
|
||||
CREATE SEQUENCE IF NOT EXISTS py_adjust_seq;
|
||||
CREATE TABLE IF NOT EXISTS py_adjust (
|
||||
adj_id BIGINT PRIMARY KEY,
|
||||
payout_id BIGINT NOT NULL,
|
||||
delta BIGINT NOT NULL,
|
||||
reason TEXT NOT NULL DEFAULT '',
|
||||
biz_date DATE NOT NULL,
|
||||
created_at TIMESTAMPTZ NOT NULL DEFAULT now()
|
||||
);
|
||||
|
||||
-- ---------------------------------------------------------------------------
|
||||
-- 지급 원장 상세 (py_ledger) - 이체/연계 원장
|
||||
-- ---------------------------------------------------------------------------
|
||||
CREATE SEQUENCE IF NOT EXISTS py_ledger_seq;
|
||||
CREATE TABLE IF NOT EXISTS py_ledger (
|
||||
ledger_id BIGINT PRIMARY KEY,
|
||||
payout_id BIGINT NOT NULL,
|
||||
entry_type TEXT NOT NULL,
|
||||
amount BIGINT NOT NULL,
|
||||
biz_date DATE NOT NULL,
|
||||
created_at TIMESTAMPTZ NOT NULL DEFAULT now()
|
||||
);
|
||||
|
||||
-- ---------------------------------------------------------------------------
|
||||
-- 가맹점 일별 지급 집계 (py_summary) - 배치/서비스가 채움
|
||||
-- ---------------------------------------------------------------------------
|
||||
CREATE TABLE IF NOT EXISTS py_summary (
|
||||
merchant_id TEXT NOT NULL,
|
||||
biz_date DATE NOT NULL,
|
||||
payout_count BIGINT NOT NULL DEFAULT 0,
|
||||
payout_amount BIGINT NOT NULL DEFAULT 0,
|
||||
updated_at TIMESTAMPTZ NOT NULL DEFAULT now(),
|
||||
PRIMARY KEY (merchant_id, biz_date)
|
||||
);
|
||||
|
||||
-- Seed payout accounts so PAY_REMIT/PAY_ACCTCHK have counterparties.
|
||||
INSERT INTO py_account (account_no, merchant_id, bank_code, holder, balance, status) VALUES
|
||||
('110-1234-567890', 'M0001', '020', '클라로 커피', 500000000, 'ACTIVE'),
|
||||
('333-9876-543210', 'M0002', '088', '포지 마트', 500000000, 'ACTIVE'),
|
||||
('456-0000-111222', 'M0003', '004', '클라로 면세점', 900000000, 'ACTIVE')
|
||||
ON CONFLICT (account_no) DO NOTHING;
|
||||
69
db/schema.d/30-rc.sql
Normal file
69
db/schema.d/30-rc.sql
Normal file
|
|
@ -0,0 +1,69 @@
|
|||
-- rc 모듈 (대사) 상세 테이블. Prefix 30 (00-base 다음, 35-st 이전에 로드).
|
||||
-- ECPG 서버/배치가 참조하는 대사 로그/예외/차이/경보/요약 테이블 + 시퀀스.
|
||||
|
||||
-- ---------------------------------------------------------------------------
|
||||
-- 대사 로그 (rc_match_log) - 서비스별 대사 결과 1행
|
||||
-- ---------------------------------------------------------------------------
|
||||
CREATE SEQUENCE IF NOT EXISTS rc_match_seq;
|
||||
CREATE TABLE IF NOT EXISTS rc_match_log (
|
||||
match_id BIGINT PRIMARY KEY,
|
||||
purchase_id BIGINT NOT NULL,
|
||||
match_type TEXT NOT NULL, -- APPR_ACQ/ACQ_DEP/3WAY/PARTIAL/MANUAL/RETURN/DELAY/TOLERANCE
|
||||
amount BIGINT NOT NULL DEFAULT 0,
|
||||
status TEXT NOT NULL DEFAULT 'MATCHED',-- MATCHED/HOLD/EXCEPTION/RELEASED/CONFIRMED/ROLLBACK
|
||||
biz_date DATE NOT NULL,
|
||||
created_at TIMESTAMPTZ NOT NULL DEFAULT now(),
|
||||
updated_at TIMESTAMPTZ NOT NULL DEFAULT now()
|
||||
);
|
||||
|
||||
-- ---------------------------------------------------------------------------
|
||||
-- 대사 예외 (rc_exception)
|
||||
-- ---------------------------------------------------------------------------
|
||||
CREATE SEQUENCE IF NOT EXISTS rc_exc_seq;
|
||||
CREATE TABLE IF NOT EXISTS rc_exception (
|
||||
exc_id BIGINT PRIMARY KEY,
|
||||
purchase_id BIGINT NOT NULL,
|
||||
reason TEXT NOT NULL DEFAULT '',
|
||||
biz_date DATE NOT NULL,
|
||||
resolved BOOLEAN NOT NULL DEFAULT false,
|
||||
created_at TIMESTAMPTZ NOT NULL DEFAULT now()
|
||||
);
|
||||
|
||||
-- ---------------------------------------------------------------------------
|
||||
-- 대사 차이 (rc_diff) - 금액/건수 차이 리포트 라인
|
||||
-- ---------------------------------------------------------------------------
|
||||
CREATE SEQUENCE IF NOT EXISTS rc_diff_seq;
|
||||
CREATE TABLE IF NOT EXISTS rc_diff (
|
||||
diff_id BIGINT PRIMARY KEY,
|
||||
purchase_id BIGINT NOT NULL DEFAULT 0,
|
||||
expected_amt BIGINT NOT NULL DEFAULT 0,
|
||||
actual_amt BIGINT NOT NULL DEFAULT 0,
|
||||
diff_amt BIGINT NOT NULL DEFAULT 0,
|
||||
diff_kind TEXT NOT NULL DEFAULT 'AMT', -- AMT / CNT
|
||||
biz_date DATE NOT NULL,
|
||||
created_at TIMESTAMPTZ NOT NULL DEFAULT now()
|
||||
);
|
||||
|
||||
-- ---------------------------------------------------------------------------
|
||||
-- 대사 경보 (rc_alert)
|
||||
-- ---------------------------------------------------------------------------
|
||||
CREATE SEQUENCE IF NOT EXISTS rc_alert_seq;
|
||||
CREATE TABLE IF NOT EXISTS rc_alert (
|
||||
alert_id BIGINT PRIMARY KEY,
|
||||
purchase_id BIGINT NOT NULL DEFAULT 0,
|
||||
lvl TEXT NOT NULL DEFAULT 'INFO',
|
||||
message TEXT NOT NULL DEFAULT '',
|
||||
biz_date DATE NOT NULL,
|
||||
created_at TIMESTAMPTZ NOT NULL DEFAULT now()
|
||||
);
|
||||
|
||||
-- ---------------------------------------------------------------------------
|
||||
-- 대사 요약 (rc_summary) - 영업일 1행 (배치/RC_SUMMARY가 upsert)
|
||||
-- ---------------------------------------------------------------------------
|
||||
CREATE TABLE IF NOT EXISTS rc_summary (
|
||||
biz_date DATE PRIMARY KEY,
|
||||
matched_cnt BIGINT NOT NULL DEFAULT 0,
|
||||
unmatched_cnt BIGINT NOT NULL DEFAULT 0,
|
||||
gross_amount BIGINT NOT NULL DEFAULT 0,
|
||||
updated_at TIMESTAMPTZ NOT NULL DEFAULT now()
|
||||
);
|
||||
77
db/schema.d/35-st.sql
Normal file
77
db/schema.d/35-st.sql
Normal file
|
|
@ -0,0 +1,77 @@
|
|||
-- st 모듈 (정산/수수료) 상세 테이블. Prefix 35 (30-rc 다음).
|
||||
-- ECPG 서버/배치가 참조하는 정산 상세/수수료율/조정/지급/가맹점집계 테이블 + 시퀀스.
|
||||
|
||||
-- ---------------------------------------------------------------------------
|
||||
-- 정산 상세 (st_settle_dtl) - 수수료 종류별 정산 라인
|
||||
-- ---------------------------------------------------------------------------
|
||||
CREATE SEQUENCE IF NOT EXISTS st_dtl_seq;
|
||||
CREATE TABLE IF NOT EXISTS st_settle_dtl (
|
||||
dtl_id BIGINT PRIMARY KEY,
|
||||
settlement_id BIGINT NOT NULL DEFAULT 0,
|
||||
purchase_id BIGINT NOT NULL DEFAULT 0,
|
||||
merchant_id TEXT NOT NULL DEFAULT '',
|
||||
fee_type TEXT NOT NULL, -- MDR/VAN/RELAY/WHT/VAT/ADJ/DELAYINT/CAP/RATE
|
||||
amount BIGINT NOT NULL DEFAULT 0,
|
||||
biz_date DATE NOT NULL,
|
||||
created_at TIMESTAMPTZ NOT NULL DEFAULT now()
|
||||
);
|
||||
|
||||
-- ---------------------------------------------------------------------------
|
||||
-- 수수료 요율표 (st_fee_rate) - 구간별 요율 (basis points)
|
||||
-- ---------------------------------------------------------------------------
|
||||
CREATE TABLE IF NOT EXISTS st_fee_rate (
|
||||
fee_type TEXT NOT NULL,
|
||||
band_min BIGINT NOT NULL DEFAULT 0, -- 이 금액 이상에 적용
|
||||
rate_bps BIGINT NOT NULL DEFAULT 0,
|
||||
PRIMARY KEY (fee_type, band_min)
|
||||
);
|
||||
|
||||
-- ---------------------------------------------------------------------------
|
||||
-- 정산 조정 (st_adjust) - 가감/상계/취소 조정
|
||||
-- ---------------------------------------------------------------------------
|
||||
CREATE SEQUENCE IF NOT EXISTS st_adj_seq;
|
||||
CREATE TABLE IF NOT EXISTS st_adjust (
|
||||
adj_id BIGINT PRIMARY KEY,
|
||||
settlement_id BIGINT NOT NULL DEFAULT 0,
|
||||
purchase_id BIGINT NOT NULL DEFAULT 0,
|
||||
delta BIGINT NOT NULL DEFAULT 0,
|
||||
reason TEXT NOT NULL DEFAULT '',
|
||||
biz_date DATE NOT NULL,
|
||||
created_at TIMESTAMPTZ NOT NULL DEFAULT now()
|
||||
);
|
||||
|
||||
-- ---------------------------------------------------------------------------
|
||||
-- 지급 (st_payment) - 가맹점 지급 예정/보류/완료
|
||||
-- ---------------------------------------------------------------------------
|
||||
CREATE SEQUENCE IF NOT EXISTS st_pay_seq;
|
||||
CREATE TABLE IF NOT EXISTS st_payment (
|
||||
pay_id BIGINT PRIMARY KEY,
|
||||
merchant_id TEXT NOT NULL,
|
||||
net_amount BIGINT NOT NULL DEFAULT 0,
|
||||
pay_date DATE NOT NULL,
|
||||
status TEXT NOT NULL DEFAULT 'SCHEDULED', -- SCHEDULED/HELD/PAID/CANCELLED
|
||||
biz_date DATE NOT NULL,
|
||||
created_at TIMESTAMPTZ NOT NULL DEFAULT now()
|
||||
);
|
||||
|
||||
-- ---------------------------------------------------------------------------
|
||||
-- 가맹점 정산 집계 (st_merch_settle) - 배치/ST_BYMERCH가 upsert
|
||||
-- ---------------------------------------------------------------------------
|
||||
CREATE TABLE IF NOT EXISTS st_merch_settle (
|
||||
merchant_id TEXT NOT NULL,
|
||||
biz_date DATE NOT NULL,
|
||||
txn_count BIGINT NOT NULL DEFAULT 0,
|
||||
gross_amount BIGINT NOT NULL DEFAULT 0,
|
||||
fee_amount BIGINT NOT NULL DEFAULT 0,
|
||||
net_amount BIGINT NOT NULL DEFAULT 0,
|
||||
updated_at TIMESTAMPTZ NOT NULL DEFAULT now(),
|
||||
PRIMARY KEY (merchant_id, biz_date)
|
||||
);
|
||||
|
||||
-- 수수료 구간 요율 시드 (ST_BANDRATE / ST_RATEAPPLY 가 조회)
|
||||
INSERT INTO st_fee_rate (fee_type, band_min, rate_bps) VALUES
|
||||
('MDR', 0, 25),
|
||||
('MDR', 10000000, 20),
|
||||
('VAN', 0, 10),
|
||||
('RELAY', 0, 5)
|
||||
ON CONFLICT (fee_type, band_min) DO NOTHING;
|
||||
67
db/schema.d/40-lg.sql
Normal file
67
db/schema.d/40-lg.sql
Normal file
|
|
@ -0,0 +1,67 @@
|
|||
-- ---------------------------------------------------------------------------
|
||||
-- lg 모듈 (원장/ledger) - 복식부기 전표/원장/계정잔액 스키마 (prefix 40)
|
||||
-- Loaded after 00-base.sql by docker-entrypoint-initdb.d.
|
||||
-- lg 서비스/배치가 채우는 전표(voucher) + 원장 라인(ledger_line) + 계정잔액 테이블.
|
||||
-- ---------------------------------------------------------------------------
|
||||
|
||||
-- 전표 (voucher) - 하나의 회계 이벤트(매입전기/정산전기/조정/대체 등)의 헤더
|
||||
CREATE SEQUENCE IF NOT EXISTS lg_voucher_seq;
|
||||
CREATE TABLE IF NOT EXISTS lg_voucher (
|
||||
voucher_id BIGINT PRIMARY KEY,
|
||||
voucher_type TEXT NOT NULL, -- POST/DOUBLE/ADJ/REVERSE/TRANSFER/SETTLE/ACQ/FEE
|
||||
ref_id BIGINT NOT NULL DEFAULT 0, -- 연계 매입/정산 ID
|
||||
biz_date DATE NOT NULL,
|
||||
status TEXT NOT NULL DEFAULT 'D', -- D=작성, P=전기, M=대사, S=마감연계, X=취소
|
||||
memo TEXT NOT NULL DEFAULT '',
|
||||
created_at TIMESTAMPTZ NOT NULL DEFAULT now(),
|
||||
updated_at TIMESTAMPTZ NOT NULL DEFAULT now()
|
||||
);
|
||||
|
||||
-- 원장 라인 (ledger_line) - 복식부기 차변(D)/대변(C) 개별 분개
|
||||
CREATE SEQUENCE IF NOT EXISTS lg_line_seq;
|
||||
CREATE TABLE IF NOT EXISTS lg_ledger_line (
|
||||
line_id BIGINT PRIMARY KEY,
|
||||
voucher_id BIGINT NOT NULL,
|
||||
acct TEXT NOT NULL, -- 계정과목: RECV/MERCH_PAY/FEE_INC/CASH/ADJ ...
|
||||
dc TEXT NOT NULL, -- D=차변(debit), C=대변(credit)
|
||||
amount BIGINT NOT NULL,
|
||||
merchant_id TEXT NOT NULL DEFAULT '',
|
||||
biz_date DATE NOT NULL,
|
||||
status TEXT NOT NULL DEFAULT 'posted', -- posted / closed
|
||||
created_at TIMESTAMPTZ NOT NULL DEFAULT now()
|
||||
);
|
||||
CREATE INDEX IF NOT EXISTS ix_lg_line_voucher ON lg_ledger_line(voucher_id);
|
||||
CREATE INDEX IF NOT EXISTS ix_lg_line_acct ON lg_ledger_line(acct, biz_date);
|
||||
|
||||
-- 계정잔액 (account balance) - 계정과목 x 영업일 누계
|
||||
CREATE TABLE IF NOT EXISTS lg_account_bal (
|
||||
acct TEXT NOT NULL,
|
||||
biz_date DATE NOT NULL,
|
||||
debit_total BIGINT NOT NULL DEFAULT 0,
|
||||
credit_total BIGINT NOT NULL DEFAULT 0,
|
||||
balance BIGINT NOT NULL DEFAULT 0, -- 차변 - 대변
|
||||
updated_at TIMESTAMPTZ NOT NULL DEFAULT now(),
|
||||
PRIMARY KEY (acct, biz_date)
|
||||
);
|
||||
|
||||
-- 가맹점 잔액 (merchant balance) - 가맹점 x 영업일 미지급/미수 누계
|
||||
CREATE TABLE IF NOT EXISTS lg_merch_bal (
|
||||
merchant_id TEXT NOT NULL,
|
||||
biz_date DATE NOT NULL,
|
||||
debit_total BIGINT NOT NULL DEFAULT 0,
|
||||
credit_total BIGINT NOT NULL DEFAULT 0,
|
||||
balance BIGINT NOT NULL DEFAULT 0,
|
||||
updated_at TIMESTAMPTZ NOT NULL DEFAULT now(),
|
||||
PRIMARY KEY (merchant_id, biz_date)
|
||||
);
|
||||
|
||||
-- 시산표 (trial balance) - 배치가 계정별로 채움
|
||||
CREATE TABLE IF NOT EXISTS lg_trial_bal (
|
||||
acct TEXT NOT NULL,
|
||||
biz_date DATE NOT NULL,
|
||||
debit_total BIGINT NOT NULL DEFAULT 0,
|
||||
credit_total BIGINT NOT NULL DEFAULT 0,
|
||||
diff BIGINT NOT NULL DEFAULT 0, -- 차변 - 대변 (0 이어야 정상)
|
||||
updated_at TIMESTAMPTZ NOT NULL DEFAULT now(),
|
||||
PRIMARY KEY (acct, biz_date)
|
||||
);
|
||||
51
db/schema.d/45-cl.sql
Normal file
51
db/schema.d/45-cl.sql
Normal file
|
|
@ -0,0 +1,51 @@
|
|||
-- ---------------------------------------------------------------------------
|
||||
-- cl 모듈 (마감/closing) - 일/월/분기/연 마감 로그 + 스냅샷 + 기간잠금 (prefix 45)
|
||||
-- Loaded after 40-lg.sql by docker-entrypoint-initdb.d.
|
||||
-- cl 서비스/배치가 채우는 마감이력(close_log) + 마감스냅샷(snapshot) 테이블.
|
||||
-- ---------------------------------------------------------------------------
|
||||
|
||||
-- 마감 이력 (close_log) - 하나의 마감 실행(일/월/분기/연/중간)의 헤더
|
||||
CREATE SEQUENCE IF NOT EXISTS cl_close_seq;
|
||||
CREATE TABLE IF NOT EXISTS cl_close_log (
|
||||
close_id BIGINT PRIMARY KEY,
|
||||
close_type TEXT NOT NULL, -- DAILY/MONTHLY/QUARTER/YEAREND/MID
|
||||
period_key TEXT NOT NULL, -- YYYY-MM-DD / YYYY-MM / YYYY-Qn / YYYY
|
||||
biz_date DATE NOT NULL,
|
||||
status TEXT NOT NULL DEFAULT 'O', -- O=개시, C=마감완료, R=재마감, A=승인, L=잠금, X=취소
|
||||
txn_count BIGINT NOT NULL DEFAULT 0,
|
||||
total_amount BIGINT NOT NULL DEFAULT 0,
|
||||
memo TEXT NOT NULL DEFAULT '',
|
||||
created_at TIMESTAMPTZ NOT NULL DEFAULT now(),
|
||||
updated_at TIMESTAMPTZ NOT NULL DEFAULT now()
|
||||
);
|
||||
CREATE INDEX IF NOT EXISTS ix_cl_close_period ON cl_close_log(close_type, period_key);
|
||||
|
||||
-- 마감 스냅샷 (snapshot) - 마감 시점 가맹점별 집계 고정
|
||||
CREATE SEQUENCE IF NOT EXISTS cl_snapshot_seq;
|
||||
CREATE TABLE IF NOT EXISTS cl_snapshot (
|
||||
snapshot_id BIGINT PRIMARY KEY,
|
||||
close_id BIGINT NOT NULL,
|
||||
period_key TEXT NOT NULL,
|
||||
biz_date DATE NOT NULL,
|
||||
merchant_id TEXT NOT NULL,
|
||||
gross_amount BIGINT NOT NULL DEFAULT 0,
|
||||
fee_amount BIGINT NOT NULL DEFAULT 0,
|
||||
net_amount BIGINT NOT NULL DEFAULT 0,
|
||||
txn_count BIGINT NOT NULL DEFAULT 0,
|
||||
created_at TIMESTAMPTZ NOT NULL DEFAULT now()
|
||||
);
|
||||
CREATE INDEX IF NOT EXISTS ix_cl_snapshot_close ON cl_snapshot(close_id);
|
||||
|
||||
-- 기간 잠금 (period lock) - 마감된 기간의 추가 정정 차단
|
||||
CREATE TABLE IF NOT EXISTS cl_period_lock (
|
||||
period_key TEXT PRIMARY KEY,
|
||||
locked BOOLEAN NOT NULL DEFAULT true,
|
||||
locked_at TIMESTAMPTZ NOT NULL DEFAULT now()
|
||||
);
|
||||
|
||||
-- 마감 시퀀스 관리 (close_type 별 마지막 순번)
|
||||
CREATE TABLE IF NOT EXISTS cl_seq_reg (
|
||||
close_type TEXT PRIMARY KEY,
|
||||
last_seq BIGINT NOT NULL DEFAULT 0,
|
||||
updated_at TIMESTAMPTZ NOT NULL DEFAULT now()
|
||||
);
|
||||
162
db/schema.d/50-mm.sql
Normal file
162
db/schema.d/50-mm.sql
Normal file
|
|
@ -0,0 +1,162 @@
|
|||
-- mm 모듈 (마스터/master) 상세 스키마
|
||||
-- 가맹점 확장원장 / 수수료율 / BIN / 공통코드 / 한도 / 발급사 / 단말 / MCC /
|
||||
-- 공휴일 / 환율 마스터 + 이력. Loaded on first boot (docker-entrypoint-initdb.d).
|
||||
|
||||
-- ---------------------------------------------------------------------------
|
||||
-- 가맹점 확장 마스터 (mm_merchant_ext) - base merchant를 보강하는 마스터 원장
|
||||
-- ---------------------------------------------------------------------------
|
||||
CREATE TABLE IF NOT EXISTS mm_merchant_ext (
|
||||
merchant_id TEXT PRIMARY KEY,
|
||||
name TEXT NOT NULL,
|
||||
biz_reg_no TEXT NOT NULL DEFAULT '', -- 사업자등록번호
|
||||
grade TEXT NOT NULL DEFAULT 'C', -- 가맹점 등급 (S/A/B/C)
|
||||
settle_acct TEXT NOT NULL DEFAULT '', -- 정산 계좌
|
||||
mcc TEXT NOT NULL DEFAULT '0000', -- 업종코드
|
||||
status TEXT NOT NULL DEFAULT 'ACTIVE',-- ACTIVE / HOLD / DELETED
|
||||
created_at TIMESTAMPTZ NOT NULL DEFAULT now(),
|
||||
updated_at TIMESTAMPTZ NOT NULL DEFAULT now()
|
||||
);
|
||||
|
||||
-- 가맹점 마스터 변경 이력
|
||||
CREATE SEQUENCE IF NOT EXISTS mm_merch_hist_seq;
|
||||
CREATE TABLE IF NOT EXISTS mm_merch_hist (
|
||||
hist_id BIGINT PRIMARY KEY,
|
||||
merchant_id TEXT NOT NULL,
|
||||
action TEXT NOT NULL, -- REG / UPD / DEL / STATUS / GRADE / ACCT
|
||||
before_val TEXT NOT NULL DEFAULT '',
|
||||
after_val TEXT NOT NULL DEFAULT '',
|
||||
memo TEXT NOT NULL DEFAULT '',
|
||||
created_at TIMESTAMPTZ NOT NULL DEFAULT now()
|
||||
);
|
||||
|
||||
-- ---------------------------------------------------------------------------
|
||||
-- 수수료율 마스터 (mm_fee_rate) + 이력
|
||||
-- ---------------------------------------------------------------------------
|
||||
CREATE TABLE IF NOT EXISTS mm_fee_rate (
|
||||
merchant_id TEXT NOT NULL,
|
||||
card_type TEXT NOT NULL DEFAULT 'CREDIT',-- CREDIT / CHECK / PREPAID
|
||||
rate_bps BIGINT NOT NULL DEFAULT 25, -- basis points
|
||||
eff_date DATE NOT NULL DEFAULT current_date,
|
||||
updated_at TIMESTAMPTZ NOT NULL DEFAULT now(),
|
||||
PRIMARY KEY (merchant_id, card_type)
|
||||
);
|
||||
|
||||
CREATE SEQUENCE IF NOT EXISTS mm_feerate_hist_seq;
|
||||
CREATE TABLE IF NOT EXISTS mm_fee_rate_hist (
|
||||
hist_id BIGINT PRIMARY KEY,
|
||||
merchant_id TEXT NOT NULL,
|
||||
card_type TEXT NOT NULL,
|
||||
rate_bps BIGINT NOT NULL,
|
||||
eff_date DATE NOT NULL,
|
||||
created_at TIMESTAMPTZ NOT NULL DEFAULT now()
|
||||
);
|
||||
|
||||
-- ---------------------------------------------------------------------------
|
||||
-- BIN 마스터 (mm_bin)
|
||||
-- ---------------------------------------------------------------------------
|
||||
CREATE TABLE IF NOT EXISTS mm_bin (
|
||||
bin TEXT PRIMARY KEY, -- 6-8 digit BIN
|
||||
issuer_cd TEXT NOT NULL DEFAULT 'UNKNOWN',
|
||||
card_brand TEXT NOT NULL DEFAULT 'LOCAL', -- VISA / MASTER / LOCAL
|
||||
card_type TEXT NOT NULL DEFAULT 'CREDIT',
|
||||
status TEXT NOT NULL DEFAULT 'ACTIVE',
|
||||
created_at TIMESTAMPTZ NOT NULL DEFAULT now()
|
||||
);
|
||||
|
||||
-- ---------------------------------------------------------------------------
|
||||
-- 공통코드 마스터 (mm_code)
|
||||
-- ---------------------------------------------------------------------------
|
||||
CREATE TABLE IF NOT EXISTS mm_code (
|
||||
code_group TEXT NOT NULL,
|
||||
code TEXT NOT NULL,
|
||||
code_name TEXT NOT NULL DEFAULT '',
|
||||
sort_order INTEGER NOT NULL DEFAULT 0,
|
||||
status TEXT NOT NULL DEFAULT 'ACTIVE',
|
||||
created_at TIMESTAMPTZ NOT NULL DEFAULT now(),
|
||||
PRIMARY KEY (code_group, code)
|
||||
);
|
||||
|
||||
-- ---------------------------------------------------------------------------
|
||||
-- 가맹점 한도 마스터 (mm_limit)
|
||||
-- ---------------------------------------------------------------------------
|
||||
CREATE TABLE IF NOT EXISTS mm_limit (
|
||||
merchant_id TEXT PRIMARY KEY,
|
||||
daily_limit BIGINT NOT NULL DEFAULT 100000000,
|
||||
monthly_limit BIGINT NOT NULL DEFAULT 3000000000,
|
||||
per_txn_limit BIGINT NOT NULL DEFAULT 10000000,
|
||||
updated_at TIMESTAMPTZ NOT NULL DEFAULT now()
|
||||
);
|
||||
|
||||
-- ---------------------------------------------------------------------------
|
||||
-- 발급사 마스터 (mm_issuer)
|
||||
-- ---------------------------------------------------------------------------
|
||||
CREATE TABLE IF NOT EXISTS mm_issuer (
|
||||
issuer_cd TEXT PRIMARY KEY,
|
||||
issuer_name TEXT NOT NULL DEFAULT '',
|
||||
country TEXT NOT NULL DEFAULT 'KR',
|
||||
status TEXT NOT NULL DEFAULT 'ACTIVE',
|
||||
created_at TIMESTAMPTZ NOT NULL DEFAULT now()
|
||||
);
|
||||
|
||||
-- ---------------------------------------------------------------------------
|
||||
-- 단말 마스터 (mm_terminal)
|
||||
-- ---------------------------------------------------------------------------
|
||||
CREATE TABLE IF NOT EXISTS mm_terminal (
|
||||
terminal_id TEXT PRIMARY KEY,
|
||||
merchant_id TEXT NOT NULL,
|
||||
model TEXT NOT NULL DEFAULT 'STD',
|
||||
status TEXT NOT NULL DEFAULT 'ACTIVE',
|
||||
created_at TIMESTAMPTZ NOT NULL DEFAULT now()
|
||||
);
|
||||
|
||||
-- ---------------------------------------------------------------------------
|
||||
-- MCC 업종 마스터 (mm_mcc)
|
||||
-- ---------------------------------------------------------------------------
|
||||
CREATE TABLE IF NOT EXISTS mm_mcc (
|
||||
mcc TEXT PRIMARY KEY,
|
||||
mcc_name TEXT NOT NULL DEFAULT '',
|
||||
risk_level INTEGER NOT NULL DEFAULT 1
|
||||
);
|
||||
|
||||
-- ---------------------------------------------------------------------------
|
||||
-- 공휴일 마스터 (mm_holiday)
|
||||
-- ---------------------------------------------------------------------------
|
||||
CREATE TABLE IF NOT EXISTS mm_holiday (
|
||||
holiday_date DATE PRIMARY KEY,
|
||||
holiday_name TEXT NOT NULL DEFAULT '',
|
||||
nation TEXT NOT NULL DEFAULT 'KR'
|
||||
);
|
||||
|
||||
-- ---------------------------------------------------------------------------
|
||||
-- 환율 마스터 (mm_fx_rate)
|
||||
-- ---------------------------------------------------------------------------
|
||||
CREATE TABLE IF NOT EXISTS mm_fx_rate (
|
||||
ccy TEXT PRIMARY KEY,
|
||||
rate_bps BIGINT NOT NULL DEFAULT 100, -- KRW per unit, in bps of 100
|
||||
base_date DATE NOT NULL DEFAULT current_date,
|
||||
updated_at TIMESTAMPTZ NOT NULL DEFAULT now()
|
||||
);
|
||||
|
||||
-- ---------------------------------------------------------------------------
|
||||
-- 등급 통계 (mm_grade_stat) - mm_stat_batch가 채움
|
||||
-- ---------------------------------------------------------------------------
|
||||
CREATE TABLE IF NOT EXISTS mm_grade_stat (
|
||||
stat_date DATE NOT NULL,
|
||||
grade TEXT NOT NULL,
|
||||
merch_cnt BIGINT NOT NULL DEFAULT 0,
|
||||
updated_at TIMESTAMPTZ NOT NULL DEFAULT now(),
|
||||
PRIMARY KEY (stat_date, grade)
|
||||
);
|
||||
|
||||
-- Seed 몇 개 마스터 (테스트/데모용)
|
||||
INSERT INTO mm_code (code_group, code, code_name, sort_order) VALUES
|
||||
('CARD_TYPE', 'CREDIT', '신용카드', 1),
|
||||
('CARD_TYPE', 'CHECK', '체크카드', 2),
|
||||
('GRADE', 'S', '최우수', 1),
|
||||
('GRADE', 'A', '우수', 2)
|
||||
ON CONFLICT (code_group, code) DO NOTHING;
|
||||
|
||||
INSERT INTO mm_issuer (issuer_cd, issuer_name, country) VALUES
|
||||
('BC', 'BC카드', 'KR'),
|
||||
('SHC', '신한카드', 'KR')
|
||||
ON CONFLICT (issuer_cd) DO NOTHING;
|
||||
53
db/schema.d/55-vl.sql
Normal file
53
db/schema.d/55-vl.sql
Normal file
|
|
@ -0,0 +1,53 @@
|
|||
-- vl 모듈 (정합성검증/validation) 상세 스키마
|
||||
-- 검증 규칙 / 의심거래(suspect) / 검증 로그. Loaded on first boot.
|
||||
|
||||
-- ---------------------------------------------------------------------------
|
||||
-- 검증 규칙 마스터 (vl_rule)
|
||||
-- ---------------------------------------------------------------------------
|
||||
CREATE TABLE IF NOT EXISTS vl_rule (
|
||||
rule_code TEXT PRIMARY KEY,
|
||||
rule_name TEXT NOT NULL DEFAULT '',
|
||||
threshold BIGINT NOT NULL DEFAULT 0, -- 규칙별 임계값 (금액/건수 등)
|
||||
severity TEXT NOT NULL DEFAULT 'WARN', -- INFO / WARN / ERROR / CRIT
|
||||
status TEXT NOT NULL DEFAULT 'ACTIVE',
|
||||
created_at TIMESTAMPTZ NOT NULL DEFAULT now(),
|
||||
updated_at TIMESTAMPTZ NOT NULL DEFAULT now()
|
||||
);
|
||||
|
||||
-- ---------------------------------------------------------------------------
|
||||
-- 의심거래 (vl_suspect) - 검증 실패/이상 거래 적재
|
||||
-- ---------------------------------------------------------------------------
|
||||
CREATE SEQUENCE IF NOT EXISTS vl_suspect_seq;
|
||||
CREATE TABLE IF NOT EXISTS vl_suspect (
|
||||
suspect_id BIGINT PRIMARY KEY,
|
||||
purchase_id BIGINT NOT NULL DEFAULT 0,
|
||||
rule_code TEXT NOT NULL,
|
||||
severity TEXT NOT NULL DEFAULT 'WARN',
|
||||
detail TEXT NOT NULL DEFAULT '',
|
||||
status TEXT NOT NULL DEFAULT 'OPEN', -- OPEN / CONFIRMED / CLOSED
|
||||
biz_date DATE NOT NULL,
|
||||
created_at TIMESTAMPTZ NOT NULL DEFAULT now()
|
||||
);
|
||||
|
||||
-- ---------------------------------------------------------------------------
|
||||
-- 검증 로그 (vl_check_log) - 모든 검증 서비스 호출 기록
|
||||
-- ---------------------------------------------------------------------------
|
||||
CREATE SEQUENCE IF NOT EXISTS vl_checklog_seq;
|
||||
CREATE TABLE IF NOT EXISTS vl_check_log (
|
||||
log_id BIGINT PRIMARY KEY,
|
||||
check_type TEXT NOT NULL, -- 서비스명 (VL_SALES 등)
|
||||
target_id BIGINT NOT NULL DEFAULT 0, -- 검증 대상 (purchase_id 등)
|
||||
result_code BIGINT NOT NULL DEFAULT 0, -- 0=정상, >0=검증 실패코드
|
||||
detail TEXT NOT NULL DEFAULT '',
|
||||
biz_date DATE NOT NULL,
|
||||
created_at TIMESTAMPTZ NOT NULL DEFAULT now()
|
||||
);
|
||||
|
||||
-- Seed 기본 검증 규칙
|
||||
INSERT INTO vl_rule (rule_code, rule_name, threshold, severity) VALUES
|
||||
('AMT_MAX', '최대금액 초과', 50000000, 'ERROR'),
|
||||
('AMT_ZERO', '0원/음수 금액', 0, 'ERROR'),
|
||||
('DUP_TXN', '중복 매입', 1, 'WARN'),
|
||||
('LIMIT_OVR', '일한도 초과', 0, 'WARN'),
|
||||
('LEDGER_MIS','원장 불일치', 0, 'CRIT')
|
||||
ON CONFLICT (rule_code) DO NOTHING;
|
||||
119
db/schema.d/60-mg.sql
Normal file
119
db/schema.d/60-mg.sql
Normal file
|
|
@ -0,0 +1,119 @@
|
|||
-- mg 모듈 (전문게이트웨이 / message gateway) 스키마
|
||||
-- ISO8583 전문 로그/라우팅/채번/채널세션/큐/통계 테이블.
|
||||
-- Loaded into PostgreSQL on first boot (docker-entrypoint-initdb.d). Prefix 60.
|
||||
|
||||
-- ---------------------------------------------------------------------------
|
||||
-- 전문 채번 (STAN / 전문 시퀀스) - MG_STAN 이 nextval 로 채번
|
||||
-- ---------------------------------------------------------------------------
|
||||
CREATE SEQUENCE IF NOT EXISTS mg_stan_seq; -- System Trace Audit Number
|
||||
CREATE SEQUENCE IF NOT EXISTS mg_msg_seq; -- 전문 로그 PK
|
||||
CREATE SEQUENCE IF NOT EXISTS mg_route_seq; -- 라우팅 룰 PK
|
||||
CREATE SEQUENCE IF NOT EXISTS mg_queue_seq; -- 송수신 큐 PK
|
||||
|
||||
-- 이름있는 카운터 (세션 시퀀스 등 채번용 - MG_SESSION/MG_KEYEXCH)
|
||||
CREATE TABLE IF NOT EXISTS mg_seq (
|
||||
seq_name TEXT PRIMARY KEY,
|
||||
seq_val BIGINT NOT NULL DEFAULT 0,
|
||||
updated_at TIMESTAMPTZ NOT NULL DEFAULT now()
|
||||
);
|
||||
|
||||
-- ---------------------------------------------------------------------------
|
||||
-- 전문 로그 (mg_msg_log) - 수신/송신 전문의 원장
|
||||
-- ---------------------------------------------------------------------------
|
||||
CREATE TABLE IF NOT EXISTS mg_msg_log (
|
||||
msg_id BIGINT PRIMARY KEY,
|
||||
stan BIGINT NOT NULL, -- 전문 추적번호
|
||||
mti TEXT NOT NULL, -- 전문유형(0200/0210/0800...)
|
||||
channel TEXT NOT NULL, -- 채널ID
|
||||
direction TEXT NOT NULL, -- IN / OUT
|
||||
proc_code TEXT NOT NULL DEFAULT '000000', -- 처리코드
|
||||
bitmap TEXT NOT NULL DEFAULT '', -- 비트맵(hex)
|
||||
amount BIGINT NOT NULL DEFAULT 0,
|
||||
pan TEXT NOT NULL DEFAULT '', -- (마스킹된) 카드번호
|
||||
mac TEXT NOT NULL DEFAULT '', -- 메시지 인증코드
|
||||
rc TEXT NOT NULL DEFAULT '00', -- 응답코드
|
||||
raw_msg TEXT NOT NULL DEFAULT '', -- 원 전문
|
||||
status TEXT NOT NULL DEFAULT 'R', -- R=수신, S=송신, A=ACK, N=NACK, C=확인
|
||||
biz_date DATE NOT NULL,
|
||||
created_at TIMESTAMPTZ NOT NULL DEFAULT now()
|
||||
);
|
||||
CREATE INDEX IF NOT EXISTS ix_mg_msg_log_stan ON mg_msg_log (stan);
|
||||
CREATE INDEX IF NOT EXISTS ix_mg_msg_log_chan ON mg_msg_log (channel, biz_date);
|
||||
|
||||
-- ---------------------------------------------------------------------------
|
||||
-- 라우팅 룰 (mg_route) - 채널/전문유형 -> 목적지 매핑
|
||||
-- ---------------------------------------------------------------------------
|
||||
CREATE TABLE IF NOT EXISTS mg_route (
|
||||
route_id BIGINT PRIMARY KEY,
|
||||
channel TEXT NOT NULL,
|
||||
mti TEXT NOT NULL,
|
||||
dest TEXT NOT NULL, -- 목적지 서비스/노드
|
||||
priority INTEGER NOT NULL DEFAULT 100,
|
||||
active BOOLEAN NOT NULL DEFAULT true,
|
||||
biz_date DATE NOT NULL,
|
||||
created_at TIMESTAMPTZ NOT NULL DEFAULT now()
|
||||
);
|
||||
CREATE INDEX IF NOT EXISTS ix_mg_route_lookup ON mg_route (channel, mti, active);
|
||||
|
||||
-- ---------------------------------------------------------------------------
|
||||
-- 채널 (mg_channel) - 세션/사인온 상태
|
||||
-- ---------------------------------------------------------------------------
|
||||
CREATE TABLE IF NOT EXISTS mg_channel (
|
||||
channel TEXT PRIMARY KEY,
|
||||
name TEXT NOT NULL DEFAULT '',
|
||||
status TEXT NOT NULL DEFAULT 'DOWN', -- UP / DOWN / SIGNON / SIGNOFF
|
||||
session_key TEXT NOT NULL DEFAULT '',
|
||||
last_stan BIGINT NOT NULL DEFAULT 0,
|
||||
signon_at TIMESTAMPTZ,
|
||||
updated_at TIMESTAMPTZ NOT NULL DEFAULT now()
|
||||
);
|
||||
|
||||
-- ---------------------------------------------------------------------------
|
||||
-- 송수신 큐 (mg_queue) - 재전송 대상 관리
|
||||
-- ---------------------------------------------------------------------------
|
||||
CREATE TABLE IF NOT EXISTS mg_queue (
|
||||
queue_id BIGINT PRIMARY KEY,
|
||||
stan BIGINT NOT NULL,
|
||||
channel TEXT NOT NULL,
|
||||
mti TEXT NOT NULL DEFAULT '',
|
||||
payload TEXT NOT NULL DEFAULT '',
|
||||
status TEXT NOT NULL DEFAULT 'Q', -- Q=대기, S=송신, D=완료, F=실패
|
||||
retry_cnt INTEGER NOT NULL DEFAULT 0,
|
||||
biz_date DATE NOT NULL,
|
||||
created_at TIMESTAMPTZ NOT NULL DEFAULT now(),
|
||||
updated_at TIMESTAMPTZ NOT NULL DEFAULT now()
|
||||
);
|
||||
CREATE INDEX IF NOT EXISTS ix_mg_queue_status ON mg_queue (status, biz_date);
|
||||
|
||||
-- ---------------------------------------------------------------------------
|
||||
-- 채널 일별 통계 (mg_stat) - mg_stat_batch 가 채움
|
||||
-- ---------------------------------------------------------------------------
|
||||
CREATE TABLE IF NOT EXISTS mg_stat (
|
||||
channel TEXT NOT NULL,
|
||||
biz_date DATE NOT NULL,
|
||||
in_cnt BIGINT NOT NULL DEFAULT 0,
|
||||
out_cnt BIGINT NOT NULL DEFAULT 0,
|
||||
err_cnt BIGINT NOT NULL DEFAULT 0,
|
||||
amount_sum BIGINT NOT NULL DEFAULT 0,
|
||||
updated_at TIMESTAMPTZ NOT NULL DEFAULT now(),
|
||||
PRIMARY KEY (channel, biz_date)
|
||||
);
|
||||
|
||||
-- ---------------------------------------------------------------------------
|
||||
-- Seed: 채널 + 기본 라우팅 룰
|
||||
-- ---------------------------------------------------------------------------
|
||||
INSERT INTO mg_channel (channel, name, status) VALUES
|
||||
('CH01', 'VAN 게이트웨이', 'DOWN'),
|
||||
('CH02', '매입사 링크', 'DOWN'),
|
||||
('CH03', '해외 네트워크', 'DOWN')
|
||||
ON CONFLICT (channel) DO NOTHING;
|
||||
|
||||
INSERT INTO mg_route (route_id, channel, mti, dest, priority, active, biz_date) VALUES
|
||||
(nextval('mg_route_seq'), 'CH01', '0200', 'ACQUIRE', 10, true, '2026-07-19'),
|
||||
(nextval('mg_route_seq'), 'CH01', '0800', 'MG_ECHO', 20, true, '2026-07-19'),
|
||||
(nextval('mg_route_seq'), 'CH02', '0200', 'ACQ_EDC', 10, true, '2026-07-19')
|
||||
ON CONFLICT DO NOTHING;
|
||||
|
||||
INSERT INTO mg_seq (seq_name, seq_val) VALUES
|
||||
('SESSION', 0), ('KEYVER', 0)
|
||||
ON CONFLICT (seq_name) DO NOTHING;
|
||||
112
db/schema.d/65-cm.sql
Normal file
112
db/schema.d/65-cm.sql
Normal file
|
|
@ -0,0 +1,112 @@
|
|||
-- cm 모듈 (공통 / common) 스키마
|
||||
-- 공통코드/파라미터/휴일/환율/채번/공통로그 + seed 데이터. Prefix 65.
|
||||
-- Loaded into PostgreSQL on first boot (docker-entrypoint-initdb.d).
|
||||
|
||||
-- ---------------------------------------------------------------------------
|
||||
-- 공통코드 (cm_code)
|
||||
-- ---------------------------------------------------------------------------
|
||||
CREATE TABLE IF NOT EXISTS cm_code (
|
||||
code_grp TEXT NOT NULL, -- 코드 그룹 (예: CCY, CHANNEL)
|
||||
code TEXT NOT NULL, -- 코드 값
|
||||
code_name TEXT NOT NULL, -- 코드 명
|
||||
sort_ord INTEGER NOT NULL DEFAULT 0,
|
||||
use_yn TEXT NOT NULL DEFAULT 'Y',
|
||||
updated_at TIMESTAMPTZ NOT NULL DEFAULT now(),
|
||||
PRIMARY KEY (code_grp, code)
|
||||
);
|
||||
|
||||
-- ---------------------------------------------------------------------------
|
||||
-- 공통 파라미터 (cm_param) - 시스템 운영 파라미터 (BIZ_DATE 포함)
|
||||
-- ---------------------------------------------------------------------------
|
||||
CREATE TABLE IF NOT EXISTS cm_param (
|
||||
param_key TEXT PRIMARY KEY,
|
||||
param_val TEXT NOT NULL,
|
||||
descr TEXT NOT NULL DEFAULT '',
|
||||
updated_at TIMESTAMPTZ NOT NULL DEFAULT now()
|
||||
);
|
||||
|
||||
-- ---------------------------------------------------------------------------
|
||||
-- 휴일 (cm_holiday) - 영업일 계산에 사용
|
||||
-- ---------------------------------------------------------------------------
|
||||
CREATE TABLE IF NOT EXISTS cm_holiday (
|
||||
holi_date DATE PRIMARY KEY,
|
||||
holi_name TEXT NOT NULL,
|
||||
nation TEXT NOT NULL DEFAULT 'KR'
|
||||
);
|
||||
|
||||
-- ---------------------------------------------------------------------------
|
||||
-- 환율 (cm_fx_rate) - 통화별 기준일 환율 (bps of 100 = KRW*100/unit)
|
||||
-- ---------------------------------------------------------------------------
|
||||
CREATE TABLE IF NOT EXISTS cm_fx_rate (
|
||||
ccy TEXT NOT NULL,
|
||||
base_date DATE NOT NULL,
|
||||
rate_bps BIGINT NOT NULL, -- KRW per unit * 100
|
||||
updated_at TIMESTAMPTZ NOT NULL DEFAULT now(),
|
||||
PRIMARY KEY (ccy, base_date)
|
||||
);
|
||||
|
||||
-- ---------------------------------------------------------------------------
|
||||
-- 채번 (cm_seq) - 이름있는 공통 시퀀스 (CM_SEQ 서비스)
|
||||
-- ---------------------------------------------------------------------------
|
||||
CREATE TABLE IF NOT EXISTS cm_seq (
|
||||
seq_name TEXT PRIMARY KEY,
|
||||
seq_val BIGINT NOT NULL DEFAULT 0,
|
||||
updated_at TIMESTAMPTZ NOT NULL DEFAULT now()
|
||||
);
|
||||
|
||||
-- ---------------------------------------------------------------------------
|
||||
-- 공통 로그 (cm_log) - CM_LOG 서비스 + cm_purge_batch 대상
|
||||
-- ---------------------------------------------------------------------------
|
||||
CREATE SEQUENCE IF NOT EXISTS cm_log_seq;
|
||||
CREATE TABLE IF NOT EXISTS cm_log (
|
||||
log_id BIGINT PRIMARY KEY,
|
||||
svc_name TEXT NOT NULL DEFAULT '',
|
||||
log_level TEXT NOT NULL DEFAULT 'I', -- I/W/E
|
||||
msg TEXT NOT NULL DEFAULT '',
|
||||
biz_date DATE NOT NULL,
|
||||
created_at TIMESTAMPTZ NOT NULL DEFAULT now()
|
||||
);
|
||||
CREATE INDEX IF NOT EXISTS ix_cm_log_date ON cm_log (biz_date);
|
||||
|
||||
-- ---------------------------------------------------------------------------
|
||||
-- Seed rows
|
||||
-- ---------------------------------------------------------------------------
|
||||
INSERT INTO cm_code (code_grp, code, code_name, sort_ord) VALUES
|
||||
('CCY', 'KRW', '원화', 1),
|
||||
('CCY', 'USD', '미국 달러', 2),
|
||||
('CCY', 'JPY', '일본 엔', 3),
|
||||
('CCY', 'EUR', '유로', 4),
|
||||
('CHANNEL', 'POS', 'POS 단말', 1),
|
||||
('CHANNEL', 'EDC', 'EDC 단말', 2),
|
||||
('CHANNEL', 'EDI', 'EDI 청구', 3),
|
||||
('CHANNEL', 'FOREIGN', '해외 매입', 4),
|
||||
('STATUS', 'A', '접수', 1),
|
||||
('STATUS', 'S', '정산완료', 2),
|
||||
('STATUS', 'C', '취소', 3)
|
||||
ON CONFLICT (code_grp, code) DO NOTHING;
|
||||
|
||||
INSERT INTO cm_param (param_key, param_val, descr) VALUES
|
||||
('BIZ_DATE', '2026-07-19', '현재 영업일'),
|
||||
('VAT_RATE', '10', '부가세율(%)'),
|
||||
('WHT_BPS', '2200', '원천징수율(bps)'),
|
||||
('LOG_KEEP_DAYS','90', '공통로그 보존일수'),
|
||||
('SETTLE_CYCLE','1', '정산주기(일)')
|
||||
ON CONFLICT (param_key) DO NOTHING;
|
||||
|
||||
INSERT INTO cm_holiday (holi_date, holi_name, nation) VALUES
|
||||
('2026-01-01', '신정', 'KR'),
|
||||
('2026-03-01', '삼일절', 'KR'),
|
||||
('2026-05-05', '어린이날', 'KR'),
|
||||
('2026-08-15', '광복절', 'KR'),
|
||||
('2026-12-25', '성탄절', 'KR')
|
||||
ON CONFLICT (holi_date) DO NOTHING;
|
||||
|
||||
INSERT INTO cm_fx_rate (ccy, base_date, rate_bps) VALUES
|
||||
('USD', '2026-07-19', 135000), -- 1350.00 KRW
|
||||
('JPY', '2026-07-19', 900), -- 9.00 KRW
|
||||
('EUR', '2026-07-19', 147000) -- 1470.00 KRW
|
||||
ON CONFLICT (ccy, base_date) DO NOTHING;
|
||||
|
||||
INSERT INTO cm_seq (seq_name, seq_val) VALUES
|
||||
('BATCH', 0), ('SLIP', 0), ('TICKET', 0)
|
||||
ON CONFLICT (seq_name) DO NOTHING;
|
||||
|
|
@ -28,8 +28,10 @@ services:
|
|||
sysctls:
|
||||
fs.mqueue.msg_max: "512"
|
||||
fs.mqueue.msgsize_max: "65536"
|
||||
# ~330 서비스 = 330+ POSIX 큐. 기본 queues_max(256) 초과 → 11 모듈서버 부팅 실패하므로 상향.
|
||||
fs.mqueue.queues_max: "8192"
|
||||
ulimits:
|
||||
msgqueue: 536870912
|
||||
msgqueue: 2147483648
|
||||
nofile: 65536
|
||||
volumes:
|
||||
- ../app:/app
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue